I've been racking my brain on this problem for a while now: When I click on the Reset All button for my tic tac toe game, it isn't correctly resetting. When I use console.log on the MYAPP.firstGame function, it adds an additional instance of the MYAPP.game object. This is causing the computer player to be playing multiple times in a row if the player isn't first upon resetting.
I have asked several other programmers and couldn't figure out what was going on. If you can help me, it would be much appreciated.
Here is the link to my code pen.
HTML:
<div class="outer-container">
<button class="hard-reset">Reset All</button>
<div class="player-one-turn">
<p></p>
</div>
<div class="player-two-turn">
<p></p>
</div>
<div class="board-container">
<div class="game-starter">
<p>Would you like to be X or O?</p>
<button class="choose-x">X</button>
<button class="choose-o">O</button>
<button class="back-button"><i class="fa fa-arrow-left"></i> Back</button>
</div>
<div class="game-choice">
<p>How do you want to play?</p>
<button class="one-player">One Player</button>
<button class="two-player">Two Player</button>
</div>
<div class="game-board">
<div class="draw-message">
<p>It was a draw..</p>
</div>
<div class="lose-message">
<p>Uh oh, you lost..</p>
</div>
<div class="win-message">
<p>You Won!!! :)</p>
</div>
<canvas id="myCanvas"></canvas>
<ul class="boxes">
</ul>
</div>
</div>
</div>
CSS:
body,
html {
width: 100%;
height: 100%;
}
li {
list-style: none;
}
.outer-container {
background: rgba(240,180,135,1);
box-shadow: inset -1px 1px 7px rgba(0,0,0,.2), inset 1px -1px 7px rgba(0,0,0,.2), 1px 12px 5px rgba(0,0,0,.4), 4px 3px 8px rgba(0,0,0,.4), 5px 10px 10px rgba(0,0,0,.2), -5px 10px 10px rgba(0,0,0,.4);
position: relative;
border-radius: 10px;
width: 540px;
height: 540px;
margin: 10% auto;
padding: 30px 0;
}
.board-container {
width: 500px;
height: 500px;
background: rgba(40,40,40,1)
-webkit-radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background: rgba(40,40,40,1)
-moz-radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background: rgba(40,40,40,1)
-ms-radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background: rgba(40,40,40,1)
radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background-size: cover;
position: relative;
top: 20px;
left: 20px;
overflow: hidden;
}
.game-board {
width: 440px;
height: 450px;
margin: 20px auto;
position: relative;
}
.boxes {
padding: 0;
}
.boxes li {
width: 33%;
height: 150px;
display: inline-block;
position: relative;
z-index: 1000;
margin: 0;
overflow: hidden;
}
li i {
font-size: 7.5rem;
text-align: center;
display: block;
width: 100%;
height: 99%;
margin-bottom: 0;
margin-left: 5px;
font-style: normal;
font-family: "Architects Daughter", "Helvetica", "sans-serif";
color: rgba(220,220,220,.7);
z-index: 500;
}
/* Canvas Drawing */
#myCanvas {
width: 100%;
height: 456px;
position: absolute;
z-index: 0;
left: 0;
top: 0;
opacity: 0;
}
/* Player/Computer prompt */
.player-one-turn {
background: rgba(0,200,200,1);
left: 15px;
}
.player-two-turn {
background: rgba(200,100,100,1);
right: 15px;
}
.player-one-turn,
.player-two-turn {
position: absolute;
top: 0;
width: 200px;
height: 50px;
z-index: -10;
color: white;
text-align: center;
}
.player-one-turn p,
.player-two-turn p {
font-size: 1.5rem;
margin-top: 10px;
}
/* reset button */
.hard-reset {
position: absolute;
top: 10px;
right: 20px;
background: none;
border: none;
font-family: 'Architects Daughter', sans-serif;
color: rgba(100,60,50,.8);
font-size: 1.2rem;
border-radius: 20px;
border: 2px dashed transparent;
}
.hard-reset:hover {
border: 2px dashed rgba(100,60,50,1);
color: rgba(100,60,50,1);
}
.hard-reset:focus {
outline: none;
}
/* Result Feedback */
span.rotate {
color: rgba(0,200,200,1);
}
i.win {
background: black;
}
.draw-message,
.lose-message,
.win-message {
background: rgba(0,0,0,.8);
width: 530px;
height: 530px;
z-index: 2000;
position: absolute;
display: none;
left: -30px;
top: -35px;
box-sizing: border-box;
margin: 0;
}
.draw-message p,
.lose-message p,
.win-message p {
color: white;
text-align: center;
position: relative;
top: 230px;
font-size: 3rem;
margin: 0;
font-family: 'Architects Daughter', sans-serif;
}
/*============================================
Game Starter
============================================*/
.game-choice,
.game-starter {
background: rgba(40,40,40,1)
-webkit-radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background: rgba(40,40,40,1)
-moz-radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background: rgba(40,40,40,1)
-ms-radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
background: rgba(40,40,40,1)
radial-gradient(center, rgba(40,80,60,1), rgba(0,20,20,.6));
display: block;
width: 100%;
height: 500px;
position: absolute;
top: 0px;
text-align: center;
font-family: 'Architects Daughter', Helvetica, sans-serif;
z-index: 1500;
}
.game-starter {
display: none;
}
.game-choice p,
.game-starter p {
font-size: 2.2rem;
}
.game-choice button,
.game-choice p,
.game-starter button,
.game-starter p {
color: rgba(220,220,220,1);
position: relative;
top: 100px;
margin: 10px auto;
}
.game-choice p,
.game-starter p {
max-width: 60%;
}
.game-choice button,
.game-starter button {
background: none;
border: none;
opacity: .6;
border-radius: 20px;
border: 2px solid transparent;
}
.game-choice button {
font-size: 2rem;
}
.game-starter button {
font-size: 2.8rem;
}
.game-choice button:focus,
.game-starter button:focus {
outline: none;
}
.game-choice button:hover,
.game-starter button:hover {
opacity: 1;
border: 2px dashed rgba(230,230,230,.5);
}
.game-starter button.back-button {
position: absolute;
top: 400px;
right: 200px;
font-size: 1.5rem;
border: none;
}
.game-starter .back-button:hover {
border: none;
}
/*============================
Win/Lose animation
==============================*/
@-webkit-keyframes rotating /* Safari and Chrome */ {
from {
-ms-transform: rotateY(0deg);
-moz-transform: rotateY(0deg);
-webkit-transform: rotateY(0deg);
-o-transform: rotateY(0deg);
transform: rotateY(0deg);
}
to {
-ms-transform: rotateY(360deg);
-moz-transform: rotateY(360deg);
-webkit-transform: rotateY(360deg);
-o-transform: rotateY(360deg);
transform: rotateY(360deg);
}
}
@keyframes rotating {
from {
-ms-transform: rotateY(0deg);
-moz-transform: rotateY(0deg);
-webkit-transform: rotateY(0deg);
-o-transform: rotateY(0deg);
transform: rotateY(0deg);
}
to {
-ms-transform: rotateY(360deg);
-moz-transform: rotateY(360deg);
-webkit-transform: rotateY(360deg);
-o-transform: rotateY(360deg);
transform: rotateY(360deg);
}
}
.rotate {
-webkit-animation: rotating 2s linear infinite;
-moz-animation: rotating 2s linear infinite;
-ms-animation: rotating 2s linear infinite;
-o-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
}
JS/jQuery:
var MYAPP = MYAPP || {};
/*=========================
Display functions
==========================*/
MYAPP.display = {
hideGameStarter: function() {
$('.game-starter').fadeOut();
},
showGameStarter: function(isTwoPlayer) {
var message;
if (isTwoPlayer) {
message = "Player 1 : Would you like X or O?"
}
else {
message = "Would you like to be X or O?";
}
window.setTimeout(function() {
$('.game-starter').fadeIn(500).children('p').text(message);
}, 700);
},
showGameChoice: function() {
$('.game-choice').fadeIn();
},
hideGameChoice: function() {
$('.game-choice').fadeOut(600);
},
showPlayerOnePrompt: function() {
var game = MYAPP.game;
if (game.secondPlayer) {
$('.player-one-turn p').text('Go Player 1!');
}
else {
$('.player-one-turn p').text('Your turn!');
}
$('.player-one-turn').animate({'top': '-45px'}, 500);
},
hidePlayerOnePrompt: function() {
$('.player-one-turn').animate({'top': '0'}, 500);
},
showPlayerTwoPrompt: function() {
var game = MYAPP.game;
if (game.secondPlayer) {
$('.player-two-turn p').text('Go Player 2!');
}
else {
$('.player-two-turn p').text('Computer\'s turn');
}
$('.player-two-turn').animate({'top': '-45px'}, 500);
},
hidePlayerTwoPrompt: function() {
$('.player-two-turn').animate({'top': '0'}, 500);
},
showDrawMessage: function() {
window.setTimeout(function() {
$('.draw-message').fadeIn(500);
}, 1500);
},
hideDrawMessage: function() {
$('.draw-message').fadeOut(1000);
},
showLoseMessage: function() {
window.setTimeout(function() {
$('.lose-message').fadeIn(500);
}, 1500);
},
hideLoseMessage: function() {
$('.lose-message').fadeOut(1000);
},
showWinMessage: function(turn) {
window.setTimeout(function() {
$('.win-message').fadeIn(500).children('p').text("Player " + turn + " wins!! :D ")
}, 1500);
},
hideWinMessage: function() {
$('.win-message').fadeOut(1000);
},
drawBoard: function() {
window.setTimeout(function() {
var c = document.getElementById("myCanvas");
var canvas = c.getContext("2d");
canvas.lineWidth = 1;
canvas.strokeStyle = "#fff";
//vertical lines
canvas.beginPath();
canvas.moveTo(100, 0);
canvas.lineTo(100, $('#myCanvas').height());
canvas.closePath();
canvas.stroke();
canvas.beginPath();
canvas.moveTo(200, 0);
canvas.lineTo(200, $('#myCanvas').height());
canvas.closePath();
canvas.stroke();
// horizontal lines
canvas.lineWidth = .5;
canvas.beginPath();
canvas.moveTo(4, 100.5);
canvas.lineTo(296, 100.5);
canvas.closePath();
canvas.stroke();
canvas.beginPath();
canvas.moveTo(4, 49.5);
canvas.lineTo(296, 49.5);
canvas.closePath();
canvas.stroke();
}, 1500);
},
resetSquares: function() {
$('.boxes').html('');
for (var i = 1; i <= 9; i++) {
var box = '<li class="' + i + '"><i class="letter"><span></span></i></li>';
$(box).appendTo($('.boxes'));
}
}
};
/*=========================
Game Logic
==========================*/
MYAPP.game = {
initialize: function() {
this.winCombos = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[1, 4, 7],
[2, 5, 8],
[3, 6, 9],
[1, 5, 9],
[7, 5, 3]
];
this.numFilledIn = 0;
this.currentBoard = {
1: '',
2: '',
3: '',
4: '',
5: '',
6: '',
7: '',
8: '',
9: ''
};
},
whoStarts: function() {
var random = Math.floor(Math.random() * 2 + 1);
return random;
},
gameSelection: function(item) {
if ($(item).text() === 'One Player') {
// returns what secondPlayer value to set
return false;
}
else {
return true;
}
},
firstGame: function() {
MYAPP.game.playerOneSymbol = $(this).text();
MYAPP.game.playerTwoSymbol = MYAPP.game.playerOneSymbol == 'X' ? 'O' : 'X';
MYAPP.game.turn = MYAPP.game.whoStarts();
MYAPP.display.hideGameStarter();
$('#myCanvas').animate({'opacity': '1'}, 500);
MYAPP.display.resetSquares();
MYAPP.game.gameInPlay = true;
MYAPP.game.play();
},
play: function() {
$('.boxes li').on('click', function() {
MYAPP.game.playerTurn(MYAPP.game, this);
});
window.setTimeout(function(){
if (MYAPP.game.turn === 1) {
MYAPP.display.showPlayerOnePrompt();
}
else if (MYAPP.game.turn === 2) {
MYAPP.display.showPlayerTwoPrompt();
}
}, 1500);
window.setTimeout(function() {
if (MYAPP.game.turn === 2 && !MYAPP.game.secondPlayer) {
MYAPP.game.computerPlay();
}
}, 1200);
},
playerTurn: function(game, square) {
var symbol = game.turn === 1 ? game.playerOneSymbol : game.playerTwoSymbol;
var box = $(square).children('i').children('span');
if (box.text() === '' && game.gameInPlay && (game.turn === 1 || (game.turn === 2 && game.secondPlayer))) {
box.text(symbol);
var number = $(square).attr('class');
game.updateSquare(game, number, symbol);
game.endTurn(symbol);
}
},
computerPlay: function() {
//test computer move suggestion
var game = MYAPP.game;
var boxNumber;
if (computerWhichMove(MYAPP.game)) {
boxNumber = computerWhichMove(MYAPP.game);
var currentBox = $('.' + boxNumber).children('i');
}
var symbol = game.playerTwoSymbol;
window.setTimeout(function() {
currentBox.children('span').text(symbol);
game.updateSquare(game, boxNumber, game.playerTwoSymbol);
game.endTurn(symbol);
}, 1000);
},
endTurn: function(symbol) {
var display = MYAPP.display;
this.numFilledIn = this.numFilledIn + 1;
if (this.gameInPlay) {
if (this.checkWin(symbol)[0]) {
if (this.secondPlayer) {
display.showWinMessage(this.turn);
}
else {
this.turn === 1 ? display.showWinMessage(this.turn) : display.showLoseMessage();
}
this.gameInPlay = false;
this.showWinningCombination();
display.hidePlayerOnePrompt();
display.hidePlayerTwoPrompt();
this.reset();
}
// stop if it is a draw
else if (this.numFilledIn >= 9) {
this.gameInPlay = false;
display.hidePlayerOnePrompt();
display.hidePlayerTwoPrompt();
display.showDrawMessage();
this.reset();
} else {
if (this.turn === 1) {
display.hidePlayerOnePrompt();
display.showPlayerTwoPrompt();
this.turn = 2;
// call computer turn if no second player
if (!this.secondPlayer) {
this.computerPlay();
}
} else if (this.turn === 2) {
display.showPlayerOnePrompt();
display.hidePlayerTwoPrompt();
this.turn = 1;
}
}
}
},
updateSquare: function(game, number, symbol) {
game.currentBoard[number] = symbol;
},
checkWin: function(symbol) {
var currentBoard = this.currentBoard;
var wins = this.winCombos;
var winningCombo = [];
var winner = wins.some(function(combination) {
var winning = true;
for (var i = 0; i < combination.length; i++) {
if (currentBoard[combination[i]] !== symbol) {
winning = false;
}
}
if (winning) {
winningCombo = combination;
}
return winning;
});
return [winner, winningCombo];
},
showWinningCombination: function() {
var symbol = this.turn === 1 ? this.playerOneSymbol : this.playerTwoSymbol;
var combo = this.checkWin(symbol)[1];
for (var i = 0; i < combo.length; i++) {
var currentBox = '.' + combo[i];
// Black box and rotating test for winning combo
$(currentBox).children('i').addClass('win').children('span').addClass('rotate');
}
},
reset: function() {
var game = MYAPP.game;
var display = MYAPP.display;
game.initialize();
window.setTimeout(function() {
display.hideDrawMessage();
display.hideLoseMessage();
display.hideWinMessage();
$('.boxes').fadeOut(500);
}, 5000);
window.setTimeout(function(){
display.resetSquares();
$('.boxes').fadeIn();
game.numFilledIn = 0;
}, 6000);
//Make sure time for next timeout is long enough
//to not cause problems after first game
window.setTimeout(function() {
game.gameInPlay = true;
game.play();
}, 6000);
},
resetGame: function() {
$('#myCanvas').css('opacity', '0');
MYAPP.display.resetSquares();
MYAPP.game.initialize();
MYAPP.game.gameInPlay = false;
MYAPP.game.playerOneSymbol = null;
MYAPP.game.playerTwoSymbol = null;
MYAPP.display.showGameChoice();
}
};
$(document).ready(function() {
MYAPP.display.drawBoard();
MYAPP.game.initialize();
$('.game-choice button').click(function() {
MYAPP.game.secondPlayer = MYAPP.game.gameSelection(this);
MYAPP.display.hideGameChoice();
MYAPP.display.showGameStarter(MYAPP.game.secondPlayer);
$('.game-starter .choose-x, .game-starter .choose-o').on('click', MYAPP.game.firstGame);
$('.back-button').on('click', function() {
MYAPP.display.hideGameStarter();
MYAPP.display.showGameChoice();
});
});
$('.hard-reset').on('click', MYAPP.game.resetGame);
});
/*================================
Computer Move Decisions
=================================*/
function computerWhichMove(game) {
var board = game.currentBoard;
var move = winOrBlockChoice(game, 'win', board)[0];
if (!move) {
move = winOrBlockChoice(game, 'block', board)[0];
}
if (!move) {
move = doubleThreatChoice(game, 'win');
}
if (!move) {
move = doubleThreatChoice(game, 'block');
}
if (!move) {
move = firstPlay(game);
}
if (!move) {
move = emptyCorner(game);
}
if (!move) {
move = emptySide(game);
}
move = (move && game.currentBoard[move]) === '' ? move : false;
return move;
}
function winOrBlockChoice(game, choiceType, board) {
if (choiceType === 'win') {
var currentSymbol = game.playerTwoSymbol;
var opponentSymbol = game.playerOneSymbol;
} else if (choiceType === 'block') {
var currentSymbol = game.playerOneSymbol;
var opponentSymbol = game.playerTwoSymbol;
} else {
return;
}
var moves = [];
game.winCombos.forEach(function(combo) {
var notFound = [];
var notPlayer = true;
for (var i = 0; i < combo.length; i++) {
if (board[combo[i]] !== currentSymbol) {
if (board[combo[i]] === opponentSymbol) {
notPlayer = false;
} else {
notFound.push(combo[i]);
}
}
}
if (notFound.length === 1 && notPlayer) {
var move = notFound[0];
moves.push(move);
}
});
return moves;
}
function doubleThreatChoice(game, choiceType) {
// use winChoice function to test a spot for double threat
var board = game.currentBoard;
var move;
if (choiceType === 'win') {
var currentSymbol = game.playerTwoSymbol;
var opponentSymbol = game.playerOneSymbol;
} else if (choiceType === 'block') {
var currentSymbol = game.playerOneSymbol;
var opponentSymbol = game.playerTwoSymbol;
}
// forced diagonal win on 4th move prevention
if (board[5] === currentSymbol && game.numFilledIn === 3) {
if ((board[1] === opponentSymbol && board[9] === opponentSymbol) || (board[3] === opponentSymbol && board[7] === opponentSymbol)) {
// Play an edge to block double threat
move = emptySide(game);
}
}
if (board[5] === opponentSymbol && game.numFilledIn === 2) {
move = diagonalSecondAttack(game);
}
if (!move) {
// clone current board;
var testBoard = $.extend({}, board);
for (var i = 1; i <= 9; i++) {
testBoard = $.extend({}, board);
if (testBoard[i] === '') {
testBoard[i] = currentSymbol;
if (winOrBlockChoice(game, choiceType, testBoard).length >= 2) {
move = i;
}
}
}
}
return move || false;
}
function diagonalSecondAttack(game) {
var board = game.currentBoard;
var comp = game.playerTwoSymbol;
var corners = [1,3,7,9];
for (var i = 0; i < corners.length; i++) {
if (board[corners[i]] === comp) {
return 10 - corners[i];
}
}
}
function firstPlay(game) {
var board = game.currentBoard;
var corners = [1, 3, 7, 9];
var move;
if (game.numFilledIn === 1) {
// player plays center
if (board[5] === game.playerOneSymbol) {
var cornerNum = Math.floor(Math.random() * 4 + 1);
move = [1, 3, 7, 9][cornerNum];
}
//player plays corner, play opposite corner
else {
for (var i = 0; i < corners.length; i++) {
if (game.currentBoard[corners[i]] === game.playerOneSymbol) {
move = 5;
}
}
}
} else if (game.numFilledIn === 0) {
var cornerNum = Math.floor(Math.random() * corners.length + 1);
move = corners[cornerNum];
}
return move ? move : false;
}
function emptyCorner(game) {
var board = game.currentBoard;
var corners = [1, 3, 7, 9];
var move;
for (var i = 0; i < corners.length; i++) {
if (board[corners[i]] === '') {
move = corners[i];
}
}
return move || false;
}
function emptySide(game) {
var sides = [2, 4, 6, 8];
for (var i = 0; i < sides.length; i++) {
if (game.currentBoard[sides[i]] === '') {
return sides[i];
}
}
return false;
}
/* End Computer Move Decisions */