FreeCodeCamp tic tac toe game with AI
Created
October 5, 2016 05:47
-
-
Save JackEdwardLyons/b70b11caddd02c9d46ca35e5b13bf44a to your computer and use it in GitHub Desktop.
tic tac toe
This file contains hidden or 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
<main> | |
<div class="container"> | |
<h1>Tic Tac Toe</h1> | |
<p>Choose your marker and opponent to begin</p> | |
<!-- THIS SHOULD BE ANIMATED --> | |
<div class="player-start"> | |
<div id="player-select-container"> | |
<h3>Choose marker</h3> | |
<button class="js-player-select playerX" id="player-X" value="X">X</button> | |
<button class="js-player-select playerO" id="player-O" value="O">O</button> | |
</div> | |
<div id="opponent-select-container" class="js-hidden"> | |
<h3>Choose opponent</h3> | |
<button class="opponent AI" value="AI">COMPUTER</button> | |
<button class="opponent HUMAN" value="HUMAN">HUMAN</button> | |
</div> | |
<button class="js-reset" id="reset">RESET</button> | |
</div> | |
<!-- END animated div --> | |
<div class="js-gameboard" id="gameboard"></div> | |
<hr style="width: 50%"> | |
<div class="container"> | |
<h2>Things I learnt while building this app:</h2> | |
<ol style="list-style:none"> | |
<li>The awesome power of <a href="https://developer.mozilla.org/en/docs/Web/Guide/HTML/Using_data_attributes" target= | |
"_blank">data-attributes</a> | |
</li> | |
<li>Using CSS pseudo <a href="http://tinyurl.com/hwkvljv" target="_blank">::before</a> and <a href="http://tinyurl.com/hwkvljv" | |
target="_blank">::after</a> elements | |
</li> | |
<li>How to iterate over arrays in <a href= | |
"https://www.airpair.com/javascript/posts/mastering-es6-higher-order-functions-for-arrays" target="_blank">ES6</a> | |
</li> | |
</ol> | |
</div> | |
</div> | |
</main> | |
<footer> | |
<h2>Built by Jack</h2> | |
</footer> | |
<script src="js/index.js"></script><!-- </html> --> |
This file contains hidden or 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
/* TO DO | |
=> Learn the minimax algorithm | |
=> refactor | |
*/ | |
// Set global player variables | |
let opponent, | |
player_mark; | |
/* Set winning tiles */ | |
const winners = [ | |
[0, 1, 2], | |
[3, 4, 5], | |
[6, 7, 8], | |
[0, 3, 6], | |
[1, 4, 7], | |
[2, 5, 8], | |
[0, 4, 8], | |
[6, 4, 2] | |
]; | |
const opponent_button_container = document.getElementById("opponent-select-container"), | |
opponent_buttons = document.querySelectorAll(".opponent"), | |
player_button_container = document.getElementById("player-select-container"), | |
player_buttons = document.querySelectorAll(".js-player-select"), | |
gameboard = document.querySelector("#gameboard"); | |
/* Initialise app */ | |
initialize(); | |
/* * * * * | |
* Logic * | |
* * * * */ | |
function initialize() { | |
/* Create the gameboard and tiles */ | |
const tiles = Array(9).fill(); | |
let player_turn = 1, | |
player_mark_O = [], | |
player_mark_X = []; | |
// Initialise button styles | |
player_button_container.setAttribute("style", "display: inline-block, opacity: 1;"); | |
// opponent_button_container.style.display = "none"; | |
/* CREATE AI | |
=> maybe there could be a ranking based on whether the computer checkmark is within the winner array | |
1. SCENARIO with computer X starting: | |
- X picks tile 6 (i.e) gameboard[6], then the winning arrays include the following indexes: winners[2], winners[3], winners[5]. | |
- I put an O in tile 4 (i.e) gameboard[4]. | |
- Now the computer cannot use winners[5] but the following are still available: winners[2], winners[3]. | |
- Computer X now chooses winners[2] and randomly picks tile 7 or 8 within that array using Math.random() | |
- I select tile 8, making winners[2] and winners[5] impossible and only winners[3] left. | |
- Computer select tile 3 or tile 0 from winners[3] | |
- If I block him now, it's basically a tied game at best, Math.random() all available tiles to finish the game. | |
| | | |
0 | 1 | 2 winners = [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [6,4,2] | |
___|___|___ | |
| | | |
3 | 4 | 5 | |
___|___|___ | |
| | | |
6 | 7 | 8 | |
| | | |
*/ | |
/* | |
* Setup classes and append to div forEach tile | |
*/ | |
tiles.forEach((tile, i, arr) => { | |
tile = document.createElement("div") | |
tile.setAttribute("class", "tile checked active-tile"); | |
// Add each tile to the gameboard | |
gameboard.appendChild(tile); | |
/* | |
* Add noughts and crosses | |
*/ | |
tile.addEventListener("click", function() { | |
// Increment player_turn to see what round we are up to | |
player_turn++; | |
if (opponent === "HUMAN") { | |
// Alternate between X & O | |
if (player_mark === "X") { | |
this.dataset.check = "X"; | |
this.classList.remove("active-tile"); | |
this.classList.add("inactive-tile"); | |
// Push mark into new array | |
player_mark_X.push(i); | |
// Check which tile was clicked to compae with winning tiles | |
console.log(`PLAYER X: ${player_mark_X}`); | |
// this.style.pointerEvents = "none"; | |
player_mark = "O"; | |
} else { | |
this.dataset.check = "O"; | |
this.classList.remove("active-tile"); | |
this.classList.add("inactive-tile"); | |
// Push mark into new array | |
player_mark_O.push(i); | |
console.log(`PLAYER O: ${player_mark_O}`); | |
this.style.pointerEvents = "none"; | |
player_mark = "X"; | |
} | |
} // END human V human if statement | |
/* | |
else { | |
if (player_mark === "X") { | |
this.dataset.check = "X"; | |
// Push mark into new array | |
player_mark_X.push(i); | |
// Check which tile was clicked to compae with winning tiles | |
console.log(`PLAYER X: ${player_mark_X}`); | |
this.style.pointerEvents = "none"; | |
let activeTiles = document.getElementsByClassName("active-tile"); | |
// pick a random tile and fill it | |
var randomTile = gameboard.children[Math.floor(Math.random() * activeTiles.length + 1)]; | |
randomTile.dataset.check = "O"; | |
} | |
} */ | |
/* | |
* Find a winning match | |
*/ | |
winners.forEach((winner) => { | |
// Check winner array against player array | |
function sort_winner(winner, player) { | |
return winner.every((el) => { | |
return player.indexOf(el) !== -1; | |
}); | |
} | |
// Alert the correct winner | |
function alert_winner(player) { | |
swal({ | |
title: `PLAYER ${player} WINS`, | |
type: 'success', | |
}); | |
reset(); | |
} | |
// If a match is found alert and restart | |
if (sort_winner(winner, player_mark_O)) { | |
alert_winner("O"); | |
} else if (sort_winner(winner, player_mark_X)) { | |
alert_winner("X"); | |
} | |
}); | |
// TIE game & restart when all boxes are full | |
player_turn === 10 && reset(); | |
}); // Close tile event listeners | |
}); // Close tiles forEach function | |
} // Close initialize function | |
/* | |
* Fade out buttons | |
*/ | |
function fadeOut(element) { | |
let opacity = 1, | |
speed = 18; | |
// decrease opacity over time | |
function decrease() { | |
opacity -= 0.05; | |
if (opacity <= 0) { | |
//reveal opponent button | |
opponent_button_container.setAttribute("style", "display: block; opacity: 1;"); | |
// complete fadOut for both buttons | |
element.style.display = "none"; | |
return true; | |
} | |
element.style.opacity = opacity; | |
setTimeout(decrease, speed); | |
} | |
decrease(); | |
} | |
/* * * * * * * * * * | |
* Player buttons * | |
* * * * * * * * * */ | |
function player_and_opponent(el, selection, container) { | |
return el.forEach((item) => { | |
item.addEventListener("click", function() { | |
// set global variables (yes, i know this is sketchy :\ ) | |
window[selection] = item.value; | |
fadeOut(container); | |
}); | |
}); | |
} | |
// Call player buttons | |
player_and_opponent(player_buttons, "player_mark", player_button_container); | |
player_and_opponent(opponent_buttons, "opponent", opponent_button_container); | |
console.log("testing", player_mark); | |
/* * * * * * * * | |
* Reset game * | |
* * * * * * * */ | |
document.getElementById("reset").addEventListener("click", reset); | |
/* Reset the game by removing & replacing tiles */ | |
function reset() { | |
// Reinitialize, however this creates 2 boards => see remove_tiles hack below. | |
initialize(); | |
remove_tiles('tile'); | |
// Remove excess tiles to ensure 9 will always show | |
function remove_tiles(className) { | |
var elements = document.getElementsByClassName(className); | |
while (elements.length > 9) { | |
elements[0].parentNode.removeChild(elements[0]); | |
} | |
} | |
} // Close reset function | |
/* * * * * * | |
* THE END * | |
* * * * * */ |
This file contains hidden or 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="https://cdn.jsdelivr.net/sweetalert2/5.1.1/sweetalert2.min.js"></script> |
This file contains hidden or 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
/* Page styles */ | |
html { | |
background: -webkit-linear-gradient(90deg, #dee1e1 10%, #f4f4f4 90%); /* Chrome 10+, Saf5.1+ */ | |
background: -moz-linear-gradient(90deg, #dee1e1 10%, #f4f4f4 90%); /* FF3.6+ */background: linear-gradient(90deg, #dee1e1 10%, #f4f4f4 90%); /* W3C */ | |
height: 100vh; | |
position: relative; | |
} | |
h1, h2, h3, h4, h5, p { | |
margin: 0; | |
} | |
body { | |
font-family: Tahoma; | |
font-size: 20px; | |
margin: 0; /* bottom = footer height */ | |
} | |
footer { | |
bottom: 0; | |
text-align: center; | |
width: 100%; | |
} | |
.container { | |
text-align: center; | |
} | |
/* END Page styles */ | |
/* Buttons */ | |
button { | |
background: white; | |
cursor: pointer; | |
margin: .5em; | |
padding: 1em; | |
} | |
.playerX { | |
margin-right: 1em; | |
} | |
.playerO { | |
margin-left: 1em; | |
} | |
.js-reset { | |
margin: 1em auto; | |
} | |
button:hover, .tile:hover { | |
background: #D7D6D7; | |
cursor: pointer; | |
-webkit-transition: .3s ease; | |
transition: .3s ease; | |
} | |
/* END Buttons */ | |
/* Gameboard */ | |
.js-gameboard { | |
box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.45); | |
height: 14.65em; | |
margin: 0 auto; | |
width: 15em; | |
} | |
.tile { | |
width: 5em; | |
height: 5em; | |
border: 1px solid black; | |
background-color: white; | |
display: inline-block; | |
margin: -1px; | |
margin-bottom: -5px | |
} | |
.checked:after { | |
content: attr(data-check); | |
font-size: 3em; | |
line-height: 1.5em; | |
padding: .4em; | |
} | |
.active-tile { | |
pointer-events: auto; | |
} | |
.inactive-tile { | |
pointer-events: none; | |
} | |
/* Player Start UI */ | |
.player-start { | |
margin: .5em auto; | |
width: 75%; | |
padding: .5em; | |
} | |
#opponent-select-container { | |
display: none; | |
} | |
/* END Player Start UI */ |
This file contains hidden or 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="https://cdn.jsdelivr.net/sweetalert2/5.1.1/sweetalert2.min.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment