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>
</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 binding – A simple way to bind UI elements to data model
- Dependency tracking– Automatically track dependencies and updates the UI when data model changes
- Templating – Allows to create sophisticated nested UIs with data model
MVVM - 3 key parts
- View — HTML part (UI elements with data-bind attribute)
- ViewModel — JavaScript (contains observable items and and client side logic)
- Model — JSon (data from the server)
History
Try this fiddle[^]