Skip to content

Instantly share code, notes, and snippets.

@JackEdwardLyons
Created October 5, 2016 05:47
Show Gist options
  • Save JackEdwardLyons/b70b11caddd02c9d46ca35e5b13bf44a to your computer and use it in GitHub Desktop.
Save JackEdwardLyons/b70b11caddd02c9d46ca35e5b13bf44a to your computer and use it in GitHub Desktop.
tic tac toe
<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> -->
/* 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 *
* * * * * */
<script src="https://cdn.jsdelivr.net/sweetalert2/5.1.1/sweetalert2.min.js"></script>
/* 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 */
<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