A Pen by Brian Puschell on CodePen.
Created
January 25, 2017 02:29
-
-
Save lordmalvern/7b16c0dc2dd1bfa734ded3f77fffbbe2 to your computer and use it in GitHub Desktop.
Zipline Tic Tac Toe
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
<div class="modalDialog" id="select"> | |
<div> | |
<div class="text-center" id="innerModal"> | |
<h1>X or O?</h1> | |
<h3> | |
<a href="#select" id="X">X</a> | |
<a href="#select" id="O">O</a> | |
</h3> | |
</div> | |
</div> | |
</div> | |
<div class="text-center"> | |
<div class="page-header"> | |
<h1>Tic-Tac-Toe</h1> | |
</div> | |
<div class="grid"> | |
<div class="cell"> | |
<div class="content" id="0"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="3"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="6"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="1"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="4"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="7"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="2"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="5"> | |
</div> | |
</div> | |
<div class="cell"> | |
<div class="content" id="8"> | |
</div> | |
</div> | |
</div> | |
</div> |
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
function Board() { | |
//Tic Tac Toe 3x3 grid represented by 1D array. Index 0 is top left, index 8 is bottom right, and index 4 is center. | |
this.grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]; | |
//human player | |
this.humPlayer = 0; | |
//AI player | |
this.aiPlayer = 0; | |
//array of potential winning indices | |
this.wins = [ | |
[0, 1, 2], | |
[3, 4, 5], | |
[6, 7, 8], | |
[0, 3, 6], | |
[1, 4, 7], | |
[2, 5, 8], | |
[0, 4, 8], | |
[2, 4, 6] | |
]; | |
} | |
//allows human player to choose X or O. | |
Board.prototype.setPlayer = function(choice) { | |
this.humPlayer = choice; | |
this.aiPlayer = this.humPlayer === X ? O : X; | |
}; | |
Board.prototype.update = function() { | |
this.grid.forEach(function(val, i) { | |
if (val === O) | |
$("#" + i).text("O"); | |
else if (val === X) | |
$("#" + i).text("X"); | |
else | |
$("#" + i).empty(); | |
}); | |
}; | |
Board.prototype.nextTurn = function() { | |
this.turn = this.turn === X ? O : X; | |
}; | |
Board.prototype.move = function(player, index) { | |
this.grid.splice(index, 1, player); | |
}; | |
Board.prototype.resetGrid = function() { | |
this.grid.fill(0); | |
this.update(); | |
}; | |
Board.prototype.reset = function() { | |
this.grid.fill(0); | |
this.humPlayer = 0; | |
this.aiPlayer = 0; | |
}; | |
Board.prototype.isWon = function() { | |
var g = this.grid; | |
for (var i = 0; i < this.wins.length; i++) { | |
var p = 0; | |
for (var j = 0; j < 3; j++) { | |
var winIndex = this.wins[i][j]; | |
p += g[winIndex]; | |
} | |
if (p >= 3 || p <= -3) { | |
//console.log("isWon is true"); | |
return true; | |
} | |
} | |
return false; | |
}; | |
Board.prototype.isFull = function() { | |
return this.grid.indexOf(0) === -1; | |
}; | |
Board.prototype.isEmpty = function() { | |
var blankSpaces = 0; | |
this.grid.forEach(function(val) { | |
if (val === 0) | |
blankSpaces++; | |
}); | |
return blankSpaces === 9; | |
}; | |
function AIPlayer() { | |
"use strict"; | |
//Generates list of potential moves for AIPlayer to go through using spaces open in Board b. | |
var optimalMoves = [4, 0, 2, 6, 8, 1, 3, 5, 7]; | |
function genMoveList(b) { | |
//console.log("Generating move list"); | |
var moves = []; | |
if (b.isWon()) { | |
console.log("moves is empty"); | |
return moves; | |
} | |
moves = optimalMoves.slice(); | |
//console.log("Accessing grid"); | |
b.grid.forEach(function(val, i) { | |
if (val !== 0) { | |
var index = moves.indexOf(i); | |
moves.splice(index, 1); | |
} | |
//console.log("index " + i + " added to moves"); | |
}); | |
//console.log("moves: " + moves); | |
return moves; | |
} | |
//Inserts a player's value into the grid array of Board b. | |
/*function move(player, index, b) { | |
b.grid.splice(index, 1, player); | |
}*/ | |
//Calculates the total score of the board. | |
function evalScore(player, b) { | |
//console.log("Evaluating score"); | |
var score = 0; | |
var win = b.wins; | |
//console.log("Iterating through win array"); | |
win.forEach(function(w) { | |
var p = 0; | |
for (var i = 0; i < w.length; i++) { | |
var winIndex = w[i]; | |
p += b.grid[winIndex]; | |
} | |
//console.log("p = " + p); | |
if (p === 3 || p === -3) { | |
score += 1000 * p; | |
//console.log("Score incremented by " + 1000 * p); | |
} else if (p === 2 || p === -2) { | |
score += 100 * p; | |
//console.log("Score incremented by " + 100 * p); | |
} else { | |
score += 10 * p; | |
//console.log("Score incremented by " + 10 * p); | |
} | |
}); | |
//console.log("score = " + score); | |
return score; | |
} | |
function minimax(depth, player, b) { | |
//console.log("Initiating minimax at depth " + depth); | |
var nextMoves = genMoveList(b); | |
//console.log(nextMoves); | |
var bestScore = player === 1 ? Number.MIN_SAFE_INTEGER : Number.MAX_SAFE_INTEGER, | |
score, bestIndex = -1; | |
if (nextMoves.length === 0 || depth === 0) { | |
bestScore = evalScore(player, b); | |
//console.log("bestScore = " + bestScore); | |
} else { | |
nextMoves.forEach(function(val) { | |
//console.log("Iterating through nextMoves"); | |
b.move(player, val); | |
//console.log("Considering possible player " + player + " move at index " + val); | |
if (player === 1) { | |
score = minimax(depth - 1, -1, b)[0]; | |
if (score > bestScore) { | |
bestScore = score; | |
bestIndex = val; | |
} | |
} else if (player === -1) { | |
score = minimax(depth - 1, 1, b)[0]; | |
if (score < bestScore) { | |
bestScore = score; | |
bestIndex = val; | |
} | |
} | |
b.move(0, val); | |
//console.log("Move undone at index " + val); | |
}); | |
} | |
//console.log("[" + bestScore + ", " + bestIndex + "]"); | |
return [bestScore, bestIndex]; | |
} | |
this.takeTurn = function(b) { | |
if (b.isEmpty()) { | |
var rand = Math.floor(Math.random() * 3); | |
b.move(b.aiPlayer, optimalMoves[rand]); | |
turn = b.humPlayer; | |
} else if (b.isWon()) { | |
$("#innerModal").empty(); | |
$("#innerModal").append("<h2> Tic Tac Toe, Three in a Row! </h2>"); | |
$(".modalDialog").css("opacity", 1); | |
window.setTimeout(function(){board.resetGrid();}, 400); | |
window.setTimeout(function() { | |
turn = b.humPlayer; | |
$(".modalDialog").css("opacity", 0); | |
}, 5000); | |
} else { | |
var best = minimax(2, b.aiPlayer, b); | |
//console.log("Best array: [" + best[0] + ", " + best[1] + "]"); | |
if (b.grid[best[1]] === 0) { | |
b.move(b.aiPlayer, best[1]); | |
} | |
console.log("board.isWon() = " + board.isWon()); | |
turn = b.humPlayer; | |
} | |
}; | |
} | |
var X = -1; | |
var O = 1; | |
var board = new Board(); | |
var ai = new AIPlayer(); | |
var turn = 1; | |
//console.log(ai instanceof AIPlayer); | |
//console.log(AIPlayer.constructor); | |
//console.log(board.isEmpty()); | |
$("#X").click(function() { | |
board.setPlayer(X); | |
}); | |
$("#O").click(function() { | |
board.setPlayer(O); | |
turn = X; | |
}); | |
$(".cell").click(function() { | |
var index = $(this).children(".content").attr("id"); | |
if (turn === board.humPlayer && board.grid[index] === 0) { | |
board.move(board.humPlayer, index); | |
$(this).children(".content").text(board.humPlayer === X ? "X" : "O"); | |
//console.log("board.isWon() = " + board.isWon()); | |
turn = board.aiPlayer; | |
} | |
}); | |
function gameLoop() { | |
if (turn === board.aiPlayer) { | |
ai.takeTurn(board); | |
board.update(); | |
} | |
if (board.isWon()) { | |
turn = board.aiPlayer; | |
} | |
if (board.isFull()) { | |
$("#innerModal").empty(); | |
$("#innerModal").append("<h2> Cat's Cradle! </h2>"); | |
$(".modalDialog").css("opacity", 1); | |
window.setTimeout(function() { | |
board.resetGrid(); | |
}, 400); | |
window.setTimeout(function() { | |
$(".modalDialog").css("opacity", 0); | |
}, 5000); | |
} | |
} | |
window.setInterval(gameLoop, 1000 / 60); |
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
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> |
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
/* Background by fuzzimo.com */ | |
@import url(https://fonts.googleapis.com/css?family=Crafty+Girls); | |
body { | |
font-family: 'Crafty Girls', cursive; | |
background: url(https://lordmalvern.github.io/img/fzm-seamless.notebook.texture-01.jpg) | |
} | |
.modalDialog { | |
position: fixed; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
background: rgba(0, 0, 0, 0.8); | |
z-index: 99999; | |
opacity: 1; | |
-webkit-transition: opacity 400ms ease-in; | |
-moz-transition: opacity 400ms ease-in; | |
transition: opacity 400ms ease-in; | |
pointer-events: auto; | |
} | |
.modalDialog:target{ | |
opacity: 0; | |
pointer-events: none; | |
} | |
#innerModal { | |
width: 700px; | |
height: 800px; | |
position: relative; | |
margin: 0% auto; | |
padding: 5px 20px 13px 20px; | |
background: url(https://lordmalvern.github.io/img/fzm-seamless.notebook.texture-14.png) | |
} | |
.grid { | |
width: 30%; | |
padding-bottom: 30%; | |
margin: 0 auto; | |
} | |
.cell { | |
float: left; | |
width: 30%; | |
padding-bottom: 30%; | |
position: relative; | |
display: inline-block; | |
overflow: hidden; | |
border-style: solid; | |
} | |
.content { | |
position: absolute; | |
width: 90%; | |
height: 80%; | |
padding: 10% 5%; | |
font-size: 5em; | |
} |
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
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment