Created
August 1, 2013 23:22
-
-
Save gabeno/6136236 to your computer and use it in GitHub Desktop.
A simple game of tic-tac-toe written in JavaScript - practice
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* program for tic-tac-toe game | |
* it starts with a randomly placed X | |
*/ | |
var ticTacToe = { | |
// board layout | |
board: [], | |
// board positions | |
boardPos: [ [1,2,3], [4,5,6], [7,8,9], // rows | |
[1,4,7], [2,5,8], [3,6,9], // columns | |
[1,5,9], [3,5,7] ], // diagonals | |
// count moves played on the board | |
counter: 0, | |
msgContainer: document.getElementById('msg'), | |
inputs: document.getElementsByTagName('input'), | |
init: function() { | |
this.initializeBoard(); | |
this.attachKeyEvent(); | |
}, | |
initializeBoard: function() { | |
this.board = []; | |
this.counter = 0; | |
for ( var i = 0; i < this.inputs.length; i++ ) { | |
if (this.inputs[i].getAttribute('value') != null) { | |
this.inputs[i].removeAttribute('value'); | |
} | |
} | |
var pos = this._random(); | |
var cell = this.inputs[pos - 1].setAttribute('value','X'); | |
var btn = document.getElementById('start-btn'); | |
btn.textContent = 'Reset Game'; | |
this.updateBoard( pos, 'X' ); | |
}, | |
attachKeyEvent: function() { | |
for ( var i = 0; i < this.inputs.length; i++ ) { | |
this.inputs[i].onkeypress = this.getChar; | |
} | |
}, | |
getChar: function( e ) { | |
var str; | |
var self = ticTacToe; | |
if ( e.which == null ) { | |
str = String.fromCharCode(e.keyCode); | |
} else if ( e.which != 0 && e.charCode != 0 ) { | |
str = String.fromCharCode(e.which); | |
} | |
// allow only letter 'o' to mimic X-O letter combination for the game | |
if (str !== 'o') { | |
alert('Only letter o allowed!'); | |
return false; | |
} | |
str = str.toUpperCase(); | |
this.setAttribute( 'value', str ); | |
var pos = this.getAttribute('id').slice( 5 ); | |
console.log( self.checkGame() ); | |
if (!self.checkGame()) { | |
// record move for competitor | |
self.updateBoard( parseInt(pos), str ); | |
// trigger nerd to play | |
self.play(); | |
} | |
return false; | |
}, | |
updateBoard: function( pos, val ) { | |
this.board.push({'pos': pos, 'val': val}); | |
}, | |
play: function() { | |
// time for nerd to play | |
if ( this.board.length % 2 === 0 ) { | |
console.log('wait while nerd thinks!'); | |
var nextPos = this.getNextXPos( this.getPeers( this.getXPos() ) ); | |
console.log('Play at: ', nextPos) | |
this.inputs[nextPos - 1].setAttribute('value','X'); | |
this.updateBoard( nextPos, 'X' ); | |
} | |
}, | |
getXPos: function() { | |
var x_values = this.board.filter( function(x) { return x['val'] === 'X' }); | |
var x_pos = x_values.map( function(x) { return x['pos'] }); // array of x positions | |
return x_pos | |
}, | |
getPeers: function( arr ) { | |
var peers = [], | |
self = this; | |
arr.forEach( function( x ) { | |
self.boardPos.forEach( function( unit, i ) { | |
unit.forEach( function( w ) { | |
if (w === x) | |
peers.push( unit ); | |
}); | |
}); | |
}); | |
return peers = this._uniqBy(peers, JSON.stringify); | |
}, | |
getNextXPos: function( peers ) { | |
var countEmpty, | |
pos, | |
qtyOfX, | |
xPos = [], | |
self = this, | |
best_units = []; | |
peers.forEach( function( unit ) { | |
unit.forEach( function( val ) { | |
xPos.push( val ); | |
}); | |
}); | |
var uniqXPos = xPos.filter( function( val, i, arr ) { | |
return arr.indexOf( val ) == i | |
}); | |
var emptyXPos = uniqXPos.filter( function( val ) { | |
return self._isEmpty( val ); | |
}); | |
for (var i = 0; i < peers.length; i++) { | |
countEmpty = this._countEmpty( peers[i] ); | |
qtyOfX = this._countX(peers[i]); | |
if ( countEmpty === 1 && qtyOfX === 2 ) { | |
best_units.push( peers[i] ); | |
} else if ( countEmpty === 2 && qtyOfX === 1 ) { | |
best_units.push( peers[i] ); | |
} | |
} | |
best_units.reverse(); | |
for (var m = 0; m < emptyXPos.length; m++) { | |
for (var u in best_units) { | |
for (var n in best_units[u]) { | |
if ( emptyXPos[m] === best_units[u][n] ) { | |
pos = emptyXPos[m]; | |
break; | |
} | |
} | |
} | |
} | |
return pos | |
}, | |
// !!! | |
checkGame: function() { | |
var self = this, | |
flag = false, | |
len = this.boardPos.length; | |
while (len) { | |
for (var i = 0; i < len; i++) { | |
if ( !self._countEmpty( this.boardPos[i] ) && | |
self._countX(this.boardPos[i]) === 3 ) { | |
self._announceGameStatus('I reign!'); | |
flag = this.boardPos[i].every( function( pos ) { | |
return self._hasX( pos ) | |
}); | |
break; | |
} else if (!self._countEmpty( this.boardPos[i] ) && | |
!self._countX(this.boardPos[i] )) { | |
self._announceGameStatus('Cool! You win.'); | |
flag = this.boardPos[i].every( function( pos ) { | |
return self._hasO( pos ) | |
}); | |
break; | |
} | |
} | |
len = len - 1; | |
} | |
console.log(flag) | |
return flag | |
}, | |
_random: function() { | |
return Math.ceil( Math.random() * 9 ); | |
}, | |
_announceGameStatus: function( str ) { | |
this.msgContainer.textContent = str; | |
}, | |
_isEmpty: function( pos ) { | |
var arr = this.board.filter( function( item ) { | |
return (item['pos'] === pos) | |
}); | |
// return 0 for empty pos, return bool true thus isEmpty? true. | |
return !arr.length | |
}, | |
_hasX: function( pos ) { | |
var arr = this.board.filter( function( item ) { | |
return (item['val'] === 'X' && item['pos'] === pos ) | |
}); | |
return arr.length | |
}, | |
_hasO: function( pos ) { | |
var arr = this.board.filter( function( item ) { | |
return (item['val'] === 'O' && item['pos'] === pos ) | |
}); | |
return arr.length | |
}, | |
_checkBoard: function() { // for debugging | |
console.log( 'Game State: ',this.board ); | |
console.log( 'Moves made: ',this.board.length ); | |
}, | |
_countEmpty: function( arr ) { // [1,2,3] | |
var count = 0, | |
self = this; | |
arr.forEach( function( val ) { | |
if ( self._isEmpty( val ) ) | |
count++; | |
}); | |
return count; | |
}, | |
_countX: function( arr ) { // [1,2,3] | |
var count = 0, | |
self = this; | |
arr.forEach( function( val ) { | |
if ( self._hasX( val ) ) | |
count++; | |
}); | |
return count; | |
}, | |
_getEmptyPos: function ( arr ) { // [1,2,3] | |
var self = this; | |
var empty = arr.filter( function( val, i ) { | |
return self._isEmpty( val ); | |
}); | |
return empty; | |
}, | |
_uniqBy: function(ary, key) { | |
var seen = {}; | |
return ary.filter(function(elem) { | |
var k = key(elem); | |
return (seen[k] === 1) ? 0 : seen[k] = 1; | |
}) | |
} | |
}; | |
// accompanying HTML | |
/* | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Game of Tic-Tac-Toe</title> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
</head> | |
<body> | |
<div> | |
<h1>Let's play a game of tic-tact-toe</h1> | |
<h2>Instructions:</h2> | |
<p>You play against me, the nerd!</p> | |
<p>Insert letter o in any cell.</p> | |
<p>First person to align his letter either across, or vertically, or diagonally wins the game..</p> | |
</div> | |
<table class="board"> | |
<tr id="rowA"> | |
<td><input type="text" id="cell-1" ></td> | |
<td><input type="text" id="cell-2" ></td> | |
<td><input type="text" id="cell-3" ></td> | |
</tr> | |
<tr id="rowB"> | |
<td><input type="text" id="cell-4" ></td> | |
<td><input type="text" id="cell-5" ></td> | |
<td><input type="text" id="cell-6" ></td> | |
</tr> | |
<tr id="rowC"> | |
<td><input type="text" id="cell-7" ></td> | |
<td><input type="text" id="cell-8" ></td> | |
<td><input type="text" id="cell-9" ></td> | |
</tr> | |
</table> | |
<button id="start-btn" onclick="ticTacToe.init();" >Start game</button> | |
<p id="msg"></p> | |
<script src="3t.js"></script> | |
</body> | |
</html> | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@todo
Debug
Test suite