65.9K
CodeProject is changing. Read more.
Home

Random Number Guessing Game using HTML5, CSS3 and JavaScript (knockoutjs)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4 votes)

Jul 17, 2014

CPOL

1 min read

viewsIcon

24634

downloadIcon

368

A simple JavaScript program "Random Number Guessing Game" using knockoutjs

Best viewed with Firefox / Chrome

Introduction

This article will give you some tips to write a simple Javascript program using knockoutjs. The program allows the player to select the game level and it generates a (n) digit random number based on the level. The player will be given (n) number of attempts depending on the level to guess the random number.

Background

The code is written using HTML5, CSS3 and Javascript (knockoutjs). knockoutjs is a JavaScript framework that offers dynamic data binding, dependency tracking and templating features and also it supports MVVM pattern.

Code

JavaScript - ViewModel

   var RandomNumberGameViewModel = function () {
            var self = this;

            Level = function (id, identifier) {
                return {
                    id: ko.observable(id),
                    identifier: ko.observable(identifier)
                };
            }

            self.GenerateRandomNumber = function () {
                var number = '';
                for (var i = 0; i < self.digitsLimit() ; i++) {
                    var randomNumber = Math.floor((Math.random() * self.digitsLimit()) + 1);
                    number += randomNumber;
                }
                return number;
            }

            self.GetAttemptsLimit = function (levelValue) {
                return levelValue == 2 ? 7 :
                       levelValue == 3 ? 8 : 5;
            }

            self.GetDigitsLimit = function (levelValue) {
                return levelValue == 2 ? 7 :
                       levelValue == 3 ? 9 : 4;
            }

            self.checkInput = function (data, event) {
                return (event.charCode >= 49 &&
                   event.charCode < 49 + self.digitsLimit()) || event.charCode == 0;
            }

            self.GetDashboard = function (resultArray) {
                var dashboardArray = [];
                if (!resultArray) {
                    for (var i = 0; i < self.digitsLimit() ; i++) {
                        dashboardArray.push('X');
                    }
                }
                else {
                    for (var j = 0; j < self.digitsLimit() ; j++) {
                        if (resultArray[j].flag() == true) {
                            dashboardArray.push(resultArray[j].number);
                        }
                        else {
                            dashboardArray.push('X');
                        }
                    }
                }

                return dashboardArray;
            }

            self.Result = function (indexValue, numberValue, flagValue) {
                return {
                    index: ko.observable(indexValue),
                    number: ko.observable(numberValue),
                    flag: ko.observable(flagValue)
                };
            }

            self.Results = function (attemptValue, inputNumberValue, resultArrayValue) {
                return {
                    attempt: attemptValue,
                    number: inputNumberValue,
                    result: resultArrayValue
                };
            }

            self.GetResult = function (randomNumber, userInput) {
                var arrayOfRandomNumber = randomNumber.split('');
                var arrayOfUserInput = userInput.split('');

                var result = [];
                for (var index = 0; index < arrayOfRandomNumber.length; index++) {
                    var flag = arrayOfRandomNumber[index] == arrayOfUserInput[index];
                    var number = arrayOfRandomNumber[index];
                    result.push(new self.Result(index, number, flag));
                }

                return result;
            }

            self.RestartGame = function (gameLevel) {
                self.attemptsLimit(self.GetAttemptsLimit(gameLevel));
                self.digitsLimit(self.GetDigitsLimit(gameLevel));
                self.randomNumber = self.GenerateRandomNumber();
                self.inputNumber('');
                self.attempts(self.attemptsLimit());
                self.results([]);
                self.dashboard(self.GetDashboard(''));
            }

            self.OnEnterClick = function () {
                var resultArray = self.GetResult(
                    self.randomNumber, self.inputNumber());
                var digitsCorrectCount = 0;
                var resultArrayIndex = '';
                if (resultArray.length > 0) {
                    for (var i = 0; i < resultArray.length; i++) {
                        if (resultArray[i].flag() == true) {
                            var index = i + 1;
                            digitsCorrectCount++;
                            if (!resultArrayIndex)
                                resultArrayIndex = index;
                            else {
                                appendValue = ',' + index;
                                resultArrayIndex += appendValue;
                            }
                        }
                    }

                    if (resultArrayIndex.length == 0)
                        resultArrayIndex = 'none';

                    var newResults = new self.Results(
                            self.results().length + 1,
                            self.inputNumber(),
                            resultArrayIndex
                            );

                    self.results.push(newResults);

                    var attemptsRemaining = self.attempts() - 1;
                    self.inputNumber('');
                    self.attempts(attemptsRemaining);
                    self.dashboard(self.GetDashboard(resultArray));

                    if (digitsCorrectCount == self.digitsLimit()) {
                        alert('you guessed it correct... hurray!!!!');
                        self.RestartGame(self.selectedLevel());
                    }
                    else if (self.attempts() == 0 &&
                            digitsCorrectCount < self.digitsLimit()) {
                        alert('you missed it... Sorry... better luck next time...');
                        self.RestartGame(self.selectedLevel());
                    }
                }

                self.inputFocus(true);
            }

            self.levels = ko.observableArray([new Level(1, 'Level 1'), 
                                              new Level(2, 'Level 2'),
                                              new Level(3, 'Level 3')]);
            self.selectedLevel = ko.observable();
            self.attemptsLimit = ko.observable(0);
            self.digitsLimit = ko.observable(0);
            self.randomNumber = 0;
            self.dashboard = ko.observableArray(self.GetDashboard(''));
            self.inputNumber = ko.observable('');
            self.inputFocus = ko.observable(true);
            self.enableEnter = ko.computed(function () {
                return self.inputNumber().length == self.digitsLimit();
            }, self);
            self.attempts = ko.observable(self.attemptsLimit());
            self.results = ko.observableArray([]);

            self.selectedLevel.subscribe(function (newValue) {
                ko.utils.arrayForEach(self.levels(), function (item) {
                    if (item.id() === newValue) {
                        self.RestartGame(item.id());
                    }
                });
            });
        }

   $(function () {
    ko.applyBindings(new RandomNumberGameViewModel());
   });

HTML - View

 <p class="heading">Welcome to Random Number Guessing Game</p>
    <div class="bodycontainer">
        <h2><b>Level</b></h2>
        <div>
            <select title="Choose Level" name="level" data-bind="options: levels,  value: selectedLevel , optionsText: 'identifier', optionsValue: 'id'"></select>
        </div>
        <br />
        <h2><b>Instruction</b></h2>
        <p class="Entrypannelinputs">Computer generated a <span data-bind="text: digitsLimit"></span>digit random number. For each digit, the number is chosen between 1 - <span data-bind="text: digitsLimit"></span>. Numbers can repeat.</p>
        <h2><b>Dashboard</b></h2>
        <div class="Entrypannelinputs">
            <div data-bind="foreach: dashboard">
                <a class="randomNumber" data-bind="text: $data"></a>&nbsp;
            </div>
        </div>
        <p class="attempts">You have <b><span data-bind="text: attempts"></span></b>attempts remaining</p>
        <div class="entryPanel">
            <div class="Entrypannelinputs">
                <h2>Guess the <span data-bind="text: digitsLimit"></span>digit random number</h2>
                <input data-bind='value: inputNumber, valueUpdate: "afterkeydown", hasfocus: inputFocus, event: { keypress: checkInput }' />
                <input type="button" data-bind="click: OnEnterClick, enable: enableEnter" value="Enter" />
            </div>
        </div>
        <br />
        <div class="resultPanel" data-bind="visible: results().length > 0">
            <p class="result"><b>Result</b></p>
            <div data-bind="foreach: results.slice(0).reverse()">
                <p>
                    <span class="Valuedata">Attempt: </span><span class="Valuedataans" data-bind="text: $data.attempt"></span>
                    <br />
                    <span class="Valuedata">Your Guess: </span><span class="Valuedataans" data-bind="text: $data.number"></span>
                    <br />
                    <span class="Valuedata">Digit(s) in place  </span><span class="Valuedataans" data-bind="text: $data.result"></span>correct
                </p>
            </div>
        </div>

CSS - Styling

Thanks to Ajeesh (my colleague) for helping me out with the css part
body {
    font-family: segoe ui, verdana;
    margin: 0;
    padding: 0;
}

p.heading {
    color: #ffffff;
    margin: 0;
    padding: 10px;
    margin-bottom: 10px;
    background: rgb(59,103,158);
    background: rgba(59,103,158,1);
    text-align: center;
}

.randomNumber {
    border-radius: 2px;
    border: 1px solid #4D6B8B;
    display: inline-block;
    color: #ffffff;
    font-family: arial;
    font-size: 18px;
    padding: 3px 7px;
    text-decoration: none;
    background: rgba(59,103,158,1);
}

.bodycontainer {
    width: 95%;
    margin: 0 auto;
}

select {
    width: 100px;
    border-radius: 5px;
    border: 1px solid #4D6B8B;
    background: #CDDFF3;
    padding: 3px;
}

p {
    font-size: 14px;
    color: #094C94;
}

    p.attempts span {
        color: #F50000;
    }

    p.attempts {
        color: #094C94;
    }

.Entrypannelinputs {
    width: 95%;
    padding: 5px;
    background: #eeeeee;
    border: 1px solid #4D6B8B;
    font-size: 13px;
}

    .Entrypannelinputs input[type="text"] {
        width: 80%;
        border: 1px solid #357797;
        padding: 6px 5px;
    }

    .Entrypannelinputs input[type="button"] {
        background: #CDDFF3;
    }

h2 {
    margin: 0;
    padding: 0;
    font-size: 12px;
    font-weight: normal;
    margin-bottom: 5px;
    color: #094C94;
}

p span.Valuedata {
    width: 30%;
    float: left;
}

p span.Valuedataans {
    font-weight: bold;
}

p.result {
    margin: 0;
    border-bottom: 1px solid #cccccc;
    padding-bottom: 9px;
}

Points of Interest

We can write simple, readable and dynamic JavaScript using knockoutjs and MVVM pattern.

knockoutjs - 3 Key features

  • Data bind­ing – A sim­ple way to bind UI ele­ments to data model
  • Depen­dency track­ing– Auto­mat­i­cally track depen­den­cies and updates the UI when data model changes
  • Tem­plat­ing – Allows to cre­ate sophis­ti­cated nested UIs with data model

MVVM - 3 key parts

  • View — HTML part (UI ele­ments with data-bind attribute)
  • View­Model — JavaScript (con­tains observ­able items and and client side logic)
  • Model — JSon (data from the server)

History

Try this fiddle[^]