Skip to content

Instantly share code, notes, and snippets.

@gabeno
Created August 1, 2013 23:22
Show Gist options
  • Save gabeno/6136236 to your computer and use it in GitHub Desktop.
Save gabeno/6136236 to your computer and use it in GitHub Desktop.
A simple game of tic-tac-toe written in JavaScript - practice
/*
* 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>
*/
@gabeno
Copy link
Author

gabeno commented Aug 1, 2013

@todo
Debug
Test suite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment