Skip to content

Instantly share code, notes, and snippets.

@lordmalvern
Created January 25, 2017 02:29
Show Gist options
  • Save lordmalvern/7b16c0dc2dd1bfa734ded3f77fffbbe2 to your computer and use it in GitHub Desktop.
Save lordmalvern/7b16c0dc2dd1bfa734ded3f77fffbbe2 to your computer and use it in GitHub Desktop.
Zipline Tic Tac Toe
<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>
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);
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
/* 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;
}
<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