-
-
Save VasilSlavchev/d9dbd24cd45a0a5c1b01475602df0e78 to your computer and use it in GitHub Desktop.
Single File Tic Tac Toe against the Computer
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
<!DOCTYPE html> | |
<!-- | |
Single file Tic Tac Toe against the Computer | |
https://gist.github.com/wiseley/9458565/ | |
Author: Matt Wiseley | |
License: GPL - http://www.gnu.org/licenses/gpl.html | |
--> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<style> | |
body { font-family: arial; font-size: 1.2em; } | |
#board { | |
width: 306px; | |
height: 306px; | |
border: 1px solid #ccc; | |
} | |
#board div { | |
float: left; | |
height: 100px; | |
width: 100px; | |
border: 1px solid #aaa; | |
text-align: center; | |
color: #ddd; | |
} | |
#board div span { | |
font-family: arial; | |
font-size: 70px; | |
margin-top: 10px; | |
color: black; | |
display: block; | |
} | |
.winner { background-color: lightgreen; } | |
#status { font-size: 1.2em; font-weight: bold; } | |
</style> | |
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> | |
<script> | |
var computer = 'O'; // the computer is X | |
var player = 'X'; // you are O | |
var turn = player; // the player goes first | |
var xwins = 0, owins = 0; // win tallies | |
var turns = 0; | |
/* =============================== */ | |
/* GAME BOARD NAVIGATION FUNCTIONS */ | |
// test each cell in each row with a function and return the index of | |
// the first whose values causes the function to return true; | |
// return -1 if no row returns true | |
function iterateCols(func) { | |
return findInGrid(true, func); | |
} | |
function iterateRows(func) { | |
return findInGrid(false, func); | |
} | |
function findInGrid(isRow, func) { | |
for (var i=0; i<3; i++) { | |
var a = isRow ? val(i,0) : val(0,i); | |
var b = isRow ? val(i,1) : val(1,i); | |
var c = isRow ? val(i,2) : val(2,i); | |
if (func(a,b,c,i)) return i; | |
} | |
return -1; | |
} | |
// returns 0 if diag starting at 0,0 matches, 2 if starting at 2,0 matches, else -1 | |
function iterateDiags(func) { | |
if (func(val(0,0), val(1,1), val(2,2),0)) return 0; | |
else if (func(val(2,0), val(1,1), val(0,2),2)) return 2; | |
else return -1; | |
} | |
// from top, left to right diag is 0, right to left is 2. | |
// convert the [a,b,c] index of the diagonal based which way its going | |
function convertDiag(iterateVal, ix) { | |
//These: 00 01 02 20 21 22 | |
//become: 00 11 22 20 11 02 | |
if (iterateVal==0) { | |
if (ix==0) return [0,0]; | |
if (ix==1) return [1,1]; | |
if (ix==2) return [2,2]; | |
} | |
else { | |
if (ix==0) return [2,0]; | |
if (ix==1) return [1,1]; | |
if (ix==2) return [0,2]; | |
} | |
} | |
// get the value of the square at x,y coords | |
function val(x,y,newVal) { | |
if (typeof newVal == 'undefined') { | |
return $("#s"+x+""+y+" span").text(); | |
} | |
else { | |
$("#s"+x+""+y).html("<span>"+newVal+"</span>"); | |
} | |
} | |
/* =================== */ | |
/* GAME PLAY FUNCTIONS */ | |
function init() { | |
// register event handler for player turns | |
$(document).ready(function() { | |
$("#board div").click(function() { | |
if (turn==player) { | |
if (!$(this).find("span").length>0) { | |
$(this).html("<span>"+player+"</span>"); | |
processTurn(); | |
} | |
} | |
}); | |
}); | |
} | |
// check the puzzle for a win and swap turns | |
function processTurn() { | |
turns++; | |
// winner requires 3 in a row of same value with no blanks | |
var winnerTest = function(a,b,c) { | |
return a==b & a==c && a!=''; | |
}; | |
// check straight across | |
var y = iterateCols(winnerTest); | |
if (y > -1) { | |
finish(val(y,0), [[y,0],[y,1],[y,2]]); | |
return; | |
} | |
// check up and down | |
var x = iterateRows(winnerTest); | |
if (x > -1) { | |
finish(val(0,x), [[0,x],[1,x],[2,x]]); | |
return; | |
} | |
// check diagonals | |
var d = iterateDiags(winnerTest); | |
if (d == 0) { | |
finish(val(0,0), [[0,0],[1,1],[2,2]]); | |
return; | |
} | |
else if (d == 2) { | |
finish(val(2,0), [[2,0],[1,1],[0,2]]); | |
return; | |
} | |
// if there are no blanks, finish with a draw | |
if (iterateCols(function(a,b,c) { return a=='' || b=='' || c==''; }) == -1) { | |
finish(); | |
} | |
// otherwise proceed to the next turn | |
else { | |
if (turn==computer) { | |
turn = player; | |
} | |
else if (turn==player) { | |
turn = computer; | |
computerTurn(); | |
processTurn(); | |
} | |
// else: the game is over, do nothing | |
} | |
} | |
// looks for an imminent opponent win and prevents it | |
function strategyAvertLoss() { | |
var isPlayer = function(val) { return val==player; } | |
var isBlank = function(val) { return val==''; } | |
var findImminentLoss = function(a,b,c,i){ | |
if (matches([a,b,c], isPlayer)==2 && matches([a,b,c], isBlank)==1) { | |
ret = findInArray([a,b,c], isBlank); | |
return true; | |
} else return false; | |
}; | |
var ret = -1; | |
var ix = -1; | |
// search rows | |
ix = iterateRows(findImminentLoss); | |
if (ix>-1 && ret>-1) return [ret, ix]; | |
// search columns | |
ix = iterateCols(findImminentLoss); | |
if (ix>-1 && ret>-1) return [ix, ret]; | |
// search diagonals | |
ix = iterateDiags(findImminentLoss); | |
if (ix>-1 && ret>-1) return convertDiag(ix, ret); | |
else return false; | |
} | |
// take the middle if its available | |
function strategyMiddle() { | |
if (val(1,1)=='') return [1,1]; | |
else return false; | |
} | |
// take a corner if the middle was taken first turn | |
function strategyMiddleDefense() { | |
// if middle is taken, take the first corner | |
if (turns==1 && val(1,1)==player) return [0,0]; | |
if (turns==3 && val(1,1)==player) { | |
if (val(2,0)=='') return [2,0]; | |
if (val(0,2)=='') return [0,2]; | |
if (val(2,2)=='') return [2,2]; | |
} | |
return false; | |
} | |
// defense against the corner strategy | |
function strategyCornerDefense() { | |
if (val(0,1)==player && val(1,0)==player && val(0,0)=='') return [0,0]; | |
if (val(0,1)==player && val(1,2)==player && val(0,2)=='') return [0,2]; | |
if (val(1,2)==player && val(2,1)==player && val(2,2)=='') return [2,2]; | |
if (val(2,1)==player && val(1,0)==player && val(2,0)=='') return [2,0]; | |
return false; | |
} | |
// looks for a rol/col/diag that poses | |
// an opportunity for a win or nearest win | |
function strategyBestOpportunity() { | |
var ix = -1, y = -1, x = -1, d = -1; | |
var count = 0, countY = 0, countX = 0, countD = 0; | |
// look for the best opportunity, to be passed to iterateRows/Cols | |
var seekOpportunity = function(a,b,c,i){ | |
var match = 0; | |
var opponent = 0; | |
var blank = 0; | |
if (a==computer) match++; else if (a==player) opponent++; else if (a=='') blank++; | |
if (b==computer) match++; else if (b==player) opponent++; else if (b=='') blank++; | |
if (c==computer) match++; else if (c==player) opponent++; else if (c=='') blank++; | |
if (match>0 && match<3 && opponent==0) { | |
if (match > count) { | |
count = match; | |
ix = i; | |
} | |
} | |
}; | |
// get the best row | |
iterateRows(seekOpportunity); | |
countY = count; | |
y = ix; | |
ix = -1; count = 0; | |
// get the best column | |
iterateCols(seekOpportunity); | |
countX = count; | |
x = ix; | |
ix = -1; count = 0; | |
// get the best diagonal | |
iterateDiags(seekOpportunity); | |
countD = count; | |
d = ix; | |
ix = -1; count = 0; | |
// determine the winning strategy (col, row or diag) | |
var turnX = -1, turnY = -1; | |
if (countX >= countY && countX >= countD) { | |
turnX = x; | |
turnY = findInArray([val(x,0), val(x,1), val(x,2)], | |
function(v) { if (v=='') return true; } | |
); | |
} | |
else if (countY >= countX && countY >= countD) { | |
turnY = y; | |
turnX = findInArray([val(0,y), val(1,y), val(2,y)], | |
function(v) { if (v=='') return true; } | |
); | |
} | |
else if (countD >= countX && countD >= countY) { | |
if (val(1,1)=='') {turnX = 1; turnY = 1;} | |
else if (d == 0) { | |
if (val(0,0)=='') {turnX = 0; turnY = 0;} | |
else {turnX = 2; turnY = 2;} | |
} | |
else { | |
if (val(2,0)=='') {turnX = 2; turnY = 0;} | |
else {turnX = 0; turnY = 2;} | |
} | |
} | |
if (turnX==-1 || turnY==-1) return false; | |
else return [turnX,turnY]; | |
} | |
// take the win if it is available | |
function strategyWin() { | |
var isComputer = function(val) { return val==computer; } | |
var isBlank = function(val) { return val==''; } | |
// look for 2 computer plays and one blank, indicating an immediate win | |
var canWin = function(a,b,c) { | |
if (matches([a,b,c], isComputer)==2 && | |
matches([a,b,c], isBlank)==1) { | |
ret = findInArray([a,b,c], isBlank); | |
return true; | |
} | |
}; | |
var ret = -1; | |
var ix = -1; | |
// search rows | |
ix = iterateRows(canWin); | |
if (ix>-1 && ret>-1) return [ret, ix]; | |
// search columns | |
ix = iterateCols(canWin); | |
if (ix>-1 && ret>-1) return [ix, ret]; | |
// search diagonals | |
ix = iterateDiags(canWin); | |
if (ix>-1 && ret>-1) return convertDiag(ix, ret); | |
else return false; | |
} | |
// go in a randomly selected blank space | |
function strategyRandom() { | |
// gather all the blank spots in an array | |
var blanks = []; | |
for (var x=0; x<3; x++) { | |
for (var y=0; y<3; y++) { | |
if (val(x,y)=='') blanks.push([x,y]); | |
} | |
} | |
// return a random entry in the array of blanks | |
if (blanks.length>0) { | |
var r = Math.floor((Math.random()*blanks.length)); | |
return blanks[r]; | |
} | |
else return false; | |
} | |
// no strategy - just take the first blank space | |
function strategySequential() { | |
// just take the first available space | |
for (var x=0; x<3; x++) { | |
for (var y=0; y<3; y++) { | |
if (val(x,y)=='') return [x,y]; | |
} | |
} | |
return false; | |
} | |
// computer takes its turn | |
function computerTurn() { | |
var strategies = []; | |
if (option('win')) strategies.push(strategyWin); | |
if (option('avertLoss')) strategies.push(strategyAvertLoss); | |
if (option('middleDefense')) strategies.push(strategyMiddleDefense); | |
if (option('middle')) strategies.push(strategyMiddle); | |
if (option('cornerDefense')) strategies.push(strategyCornerDefense); | |
if (option('bestOpportunity')) strategies.push(strategyBestOpportunity); | |
if (option('random')) strategies.push(strategyRandom); | |
if (option('sequential')) strategies.push(strategySequential); | |
for (var i=0; i<strategies.length; i++) { | |
var turn = strategies[i](); | |
if (!turn) continue; | |
console.log(i,turn); | |
val(turn[0], turn[1], computer); | |
break; | |
} | |
} | |
// highlight the square at x,y coords, a: [[x,y],..] | |
function highlightWinner(a) { | |
for (var i=0; i<a.length; i++) { | |
var coord = a[i]; | |
var x = coord[0], y = coord[1]; | |
var sel = "#s"+''+x+''+y; | |
$(sel).addClass('winner'); | |
} | |
} | |
// finish the game | |
function finish(p, highlight) { | |
if (typeof p != 'undefined') { | |
$("#status").text(p + ' is the winner!'); | |
} | |
else { | |
$("#status").text('The game ended with a draw.'); | |
} | |
turn = ''; | |
if (typeof highlight != 'undefined') { | |
highlightWinner(highlight); | |
} | |
if (p=='X') xwins++; | |
else if (p=='O') owins++; | |
$("#xwins").text(xwins); | |
$("#owins").text(owins); | |
} | |
// new game | |
function newGame() { | |
$("#board div").find("span").remove(); | |
$(".winner").removeClass("winner"); | |
turn = player; | |
$("#status").empty(); | |
turns = 0; | |
} | |
/* ================= */ | |
/* UTILITY FUNCTIONS */ | |
// gets checkbox option true/false status | |
function option(name) { | |
return $("input[name='"+name+"']")[0].checked; | |
} | |
// returns the count of matching array members | |
function matches(a, func) { | |
var c = 0; | |
for (var i=0; i<a.length; i++) { | |
if (func(a[i])) c++; | |
} | |
return c; | |
} | |
// returns first index of matching value in array, or -1 | |
function findInArray(a, func) { | |
for (var i=0; i<a.length; i++) { | |
if (func(a[i])) return i; | |
} | |
return -1; | |
} | |
init(); | |
</script> | |
</head> | |
<body> | |
<h1>Tic Tac Toe</h1> | |
<div id="board"> | |
<div id="s00"></div> | |
<div id="s10"></div> | |
<div id="s20"></div> | |
<div id="s01"></div> | |
<div id="s11"></div> | |
<div id="s21"></div> | |
<div id="s02"></div> | |
<div id="s12"></div> | |
<div id="s22"></div> | |
</div> | |
<p id="status"></p> | |
<p>X wins: <span id="xwins">0</span><br/> | |
O wins: <span id="owins">0</span></p> | |
<p><button onclick="newGame()">New Game</button></p> | |
<p> | |
Strategies:<br/> | |
<label><input type="checkbox" name="win" val="1" checked/> Win</label><br/> | |
<label><input type="checkbox" name="avertLoss" val="1" checked/> Avert Loss</label><br/> | |
<label><input type="checkbox" name="middleDefense" val="1" checked/> Middle Defense</label><br/> | |
<label><input type="checkbox" name="middle" val="1" checked/> Middle</label><br/> | |
<label><input type="checkbox" name="cornerDefense" val="1" checked/> Corner Defense</label><br/> | |
<label><input type="checkbox" name="bestOpportunity" val="1" checked/> Best Opportunity</label><br/> | |
<label><input type="checkbox" name="random" value="1" checked/> Random</label><br/> | |
<label><input type="checkbox" name="sequential" val="1"/> Sequential</label><br/> | |
</p> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment