Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Game Four in a Row with KnockoutJS

5.00/5 (3 votes)
27 May 2014CPOL 21.6K   244  
Sample game Four in a row with KnockoutJS

Image 1

Introduction

Game Four in a row using KnockoutJS. This tip shows how useful KnockoutJS is for binding data collections with DOM elements.

Background

You need to know JavaScript but not necessarily knockoutJS, you can use this code in order to learn KnockoutJS.

The sample focuses on basic declarative methods from KnockoutJS and in arrays binding as well. The AI of the game is separated in a JavaScript class, you can optionally check it.

Using the Code

The tip details the KnockoutJS code. To run the game, download the zip file.

HTML
    <div class="content">
        <table class="table table-bordered">
            <!--
                Throw buttons. Notice the biding to userThrow method (when click)
                and throwEnabled (full column)
            -->
            <thead>
               <tr>
                    <td><button data-bind="click: function(){userThrow(0);}, 
                    enable: throwEnabled(0)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(1);}, 
                    enable: throwEnabled(1)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(2);}, 
                    enable: throwEnabled(2)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(3);}, 
                    enable: throwEnabled(3)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(4);}, 
                    enable: throwEnabled(4)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(5);}, 
                    enable: throwEnabled(5)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(6);}, 
                    enable: throwEnabled(6)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(7);}, 
                    enable: throwEnabled(7)" type="button" />&#9660;</td>
                    <td><button data-bind="click: function(){userThrow(8);}, 
                    enable: throwEnabled(8)" type="button" />&#9660;</td>
                </tr>
            </thead>
             <!-- 
                foreach bind render an array from modelView 
                For more information, check knockout documentation: 
                http://knockoutjs.com/documentation/foreach-binding.html
                Notice the nested foreach. One for rows and the other one for columns
             -->
            <tbody data-bind="foreach: { data: board, as: 'row' }">
                <tr data-bind="foreach: cols">
                    <td><span data-bind="text: $data, attr:{'class': $data}" /></td>
                </tr>
            </tbody>
        </table>
        
        <!-- 
            Binding for play again button and winner text when game ends 
        -->
        <input type="button" data-bind="click: newGame, 
        visible: playAgainVisible" value="Play again" />
        <label class="endGame" data-bind="text:winner" />
   </div>

<!-- you must link knockout library and the viewmodel after declare DOM binding -->
<script src="knockout-2.1.0.js"></script>
<script src="fourInARowVM.js"></script>

The ViewModel is as follows:

JavaScript
/*
    Knockout ViewModel binding sample with array
    To check KnockOut documentation visit: http://knockoutjs.com/documentation/introduction.html
    Sample focuses on array binding,check documentation. 
    http://knockoutjs.com/documentation/foreach-binding.html
 */

//The model object. File fourInARow.js
//This class defines the structure (board array) and implements the logic (AI) of the game
var fourInARow = new FourInARow();

//The View Model class
function AppViewModel() {
    var self = this;

    //Define observable properties
    //The board: It's an array
    self.board = ko.observableArray(fourInARow.newBoard());
    //Label to winner text
    self.winner = ko.observable();
    //We use Ko.computed as a readonly property
    self.playAgainVisible = ko.computed(function () { return (self.winner()!=null); }, self);

    //self.board() it's a binding property
    //To get the array model we need to map it by using utils arrayMap
    self.getDashboard = function () {
        var dashboard = ko.utils.arrayMap(self.board(), function (item) {
            return ({
                index: item.index
                , cols: ko.utils.arrayMap(item.cols, function (item) {
                    return (item);
                })
            });
        });

        return (dashboard);
    };

    //Method new game. Clear board and starts new game
    self.newGame = function () {
        self.board(fourInARow.newBoard());
        self.winner(null);
    }
    
    //Method userThrow. Called when user click the button of column
    self.userThrow = function (column) {
        var row = self.throw(column, "O");
        if (fourInARow.checkThrowWinner(self.getDashboard(), column, row, "O")) {
            self.winner("YOU WIN!!");
            return;
        }

        if (!fourInARow.checkItemsFree(self.getDashboard())) {
            self.winner("DRAWN");
            return;
        }

        self.computerThrow();
    }
    
    //Method computerThrow. Called from userThrow
    self.computerThrow = function () {
        var computerThrow = fourInARow.nextComputerThrow(self.getDashboard());
        var row = self.throw(computerThrow, "X");
        if (fourInARow.checkThrowWinner(self.getDashboard(), computerThrow, row, "X")) {
            self.winner("PC WIN");
        }
        if (!fourInARow.checkItemsFree(self.getDashboard())) {
            self.winner("DRAWN");
            return;
        }
    };
    
    //Throw action. It sets the state of the dashboard, then KnockOut refresh DOM
    self.throw = function (column, ficha) {
        var dashboard = self.getDashboard();
        var row = fourInARow.getLastRowFree(dashboard, column);

        dashboard[row].cols[column] = ficha;
        self.board(dashboard);

        return (row);
    };

    //Throw enabled property to enable/disable column throw
    self.throwEnabled = function (column) {
        if (self.winner() != null) return (false);

        for (var i = 0; i <= (self.getDashboard().length - 1) ; i++) {
            if (self.getDashboard()[i].cols[column] == null) {
                return (true);
            }
        }

        return (false);
    };
}

//Apply binding to KnockOut
ko.applyBindings(new AppViewModel());

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)