![]() |
Preview: We're making a tic tac toe game today. |
Create a folder called Tic-Tac-Toe-Game as the project workspace, and place all of the project files inside.
1. index.html
Let's make a file called index.html and add the following code to it:
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<title>Tic Tac Toe - JavaScript</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<div class="container"> | |
<canvas id="cvs" width="450" height="450"></canvas> | |
<div class="options"> | |
<h1>Tic Tac Toe</h1> | |
<h2>Play Versus</h2> | |
<div class="computer">COMPUTER</div> | |
<div class="friend">FRIEND</div> | |
<h2>Symbol</h2> | |
<div class="x">X</div> | |
<div class="o">O</div> | |
<div class="play">PLAY</div> | |
</div> | |
<div class="gameover hide"></div> | |
</div> | |
<script src="options.js"></script> | |
<script src="game.js"></script> | |
</body> | |
</html> |
2. style.css
Let's make a CSS file called style.css and add the CSS code below to it:
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
@import url("https://fonts.googleapis.com/css2?family=Diplomata+SC&display=swap"); | |
@font-face { | |
font-family: "Fascinate Inline"; | |
font-style: normal; | |
font-weight: 400; | |
font-display: swap; | |
src: local("Fascinate Inline"), local("FascinateInline-Regular"), | |
url(font/jVyR7mzzB3zc-jp6QCAu60poNqIy5grIfA.woff2) format("woff2"); | |
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, | |
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, | |
U+FEFF, U+FFFD; | |
} | |
.container { | |
width: 450px; | |
height: 450px; | |
display: block; | |
margin: 25px auto; | |
position: relative; | |
} | |
#cvs { | |
position: absolute; | |
background: rgb(226, 255, 254); | |
border: 1px solid #000; | |
} | |
.options { | |
position: absolute; | |
width: 450px; | |
height: 450px; | |
} | |
.options h1 { | |
text-align: center; | |
font-size: 45px; | |
font-family: "Diplomata SC", cursive; | |
color: red; | |
margin: 30px 0 20px 0; | |
} | |
.options h2 { | |
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif; | |
text-align: center; | |
font-weight: bold; | |
} | |
.options div { | |
display: inline-block; | |
} | |
.options .computer, | |
.x { | |
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif; | |
padding: 5px; | |
background-color: #fff; | |
color: #000; | |
font-size: 1.5em; | |
border-radius: 5px; | |
margin-left: 70px; | |
width: 150px; | |
text-align: center; | |
cursor: pointer; | |
border: 1px solid #000; | |
} | |
.options .friend, | |
.o { | |
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif; | |
padding: 5px; | |
background-color: #fff; | |
color: #000; | |
font-size: 1.5em; | |
border-radius: 5px; | |
margin-left: 10px; | |
width: 150px; | |
text-align: center; | |
cursor: pointer; | |
border: 1px solid #000; | |
} | |
.options .friend:hover, | |
.computer:hover, | |
.x:hover, | |
.o:hover { | |
background-color: #150127; | |
color: #fff; | |
} | |
.options .play { | |
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif; | |
font-size: 1.5em; | |
width: 70px; | |
display: block; | |
margin: 25px auto; | |
border-radius: 5px; | |
border: 1px solid #150127; | |
text-align: center; | |
padding: 10px; | |
background-color: #150127; | |
color: #fff; | |
cursor: pointer; | |
transition: 100ms width ease-in; | |
} | |
.options .play:hover { | |
width: 110px; | |
} | |
.active { | |
background-color: #150127 !important; | |
color: #fff !important; | |
} | |
.hide { | |
display: none; | |
} | |
.gameover { | |
position: absolute; | |
width: 450px; | |
height: 450px; | |
background-color: rgba(0, 0, 0, 0.95); | |
} | |
.gameover h1 { | |
text-align: center; | |
font-size: 50px; | |
font-family: "Fascinate Inline", cursive; | |
color: #fff; | |
margin: 40px 0 20px 0; | |
} | |
.gameover .winner-img { | |
display: block; | |
margin: 20px auto; | |
} | |
.gameover .play { | |
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif; | |
font-size: 1.5em; | |
width: 150px; | |
display: block; | |
margin: 25px auto; | |
border-radius: 5px; | |
border: 1px solid #fff; | |
text-align: center; | |
padding: 10px; | |
background-color: #fff; | |
color: #000; | |
cursor: pointer; | |
transition: 100ms width ease-in; | |
} | |
.gameover .play:hover { | |
width: 200px; | |
} |
3. options.js
Let's make a JavaScript file named options.js and add following JavaScript code to it:
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
// SELECT START ELEMENT | |
const options = document.querySelector(".options"); | |
// SELECT BUTTONS | |
const computerBtn = document.querySelector(".computer"); | |
const friendBtn = document.querySelector(".friend"); | |
const xBtn = document.querySelector(".x"); | |
const oBtn = document.querySelector(".o"); | |
const playBtn = document.querySelector(".play"); | |
// GAME OVER ELEMENT | |
const gameOverElement = document.querySelector(".gameover"); | |
const player = new Object; | |
let OPPONENT; | |
oBtn.addEventListener("click", function(){ | |
player.man = "O"; | |
player.computer = "X"; | |
player.friend = "X"; | |
switchActive(xBtn, oBtn); | |
}); | |
xBtn.addEventListener("click", function(){ | |
player.man = "X"; | |
player.computer = "O"; | |
player.friend = "O"; | |
switchActive(oBtn, xBtn); | |
}); | |
computerBtn.addEventListener("click", function(){ | |
OPPONENT = "computer"; | |
switchActive(friendBtn, computerBtn); | |
}); | |
friendBtn.addEventListener("click", function(){ | |
OPPONENT = "friend"; | |
switchActive(computerBtn, friendBtn); | |
}); | |
playBtn.addEventListener("click", function(){ | |
if( !OPPONENT){ | |
computerBtn.style.backgroundColor = "red"; | |
friendBtn.style.backgroundColor = "red"; | |
return; | |
} | |
if( !player.man ){ | |
oBtn.style.backgroundColor = "red"; | |
xBtn.style.backgroundColor = "red"; | |
return; | |
} | |
init(player, OPPONENT); | |
options.classList.add("hide"); | |
}); | |
function switchActive(off, on){ | |
off.classList.remove("active"); | |
on.classList.add("active"); | |
} |
4. game.js
Let's make a JavaScript file named game.js and add following JavaScript code to it:
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 init(player, OPPONENT){ | |
// SELECT CANAVS | |
const canvas = document.getElementById("cvs"); | |
const ctx = canvas.getContext("2d"); | |
// BOARD VARIABLES | |
let board = []; | |
const COLUMN = 3; | |
const ROW = 3; | |
const SPACE_SIZE = 150; | |
// STORE PLAYER'S MOVES | |
let gameData = new Array(9); | |
// By default the first player to play is the human | |
let currentPlayer = player.man; | |
// load X & O images | |
const xImage = new Image(); | |
xImage.src = "img/X.png"; | |
const oImage = new Image(); | |
oImage.src = "img/O.png"; | |
// Win combinations | |
const COMBOS = [ | |
[0, 1, 2], | |
[3, 4, 5], | |
[6, 7, 8], | |
[0, 3, 6], | |
[1, 4, 7], | |
[2, 5, 8], | |
[0, 4, 8], | |
[2, 4, 6] | |
]; | |
// FOR GAME OVER CHECK | |
let GAME_OVER = false; | |
// DRAW THE BOARD | |
function drawBoard(){ | |
// WE give every space a unique id | |
// So we know exactly where to put the player's move on the gameData Array | |
let id = 0 | |
for(let i = 0; i < ROW; i++){ | |
board[i] = []; | |
for(let j = 0; j < COLUMN; j++){ | |
board[i][j] = id; | |
id++; | |
// draw the spaces | |
ctx.strokeStyle = "#000"; | |
ctx.strokeRect(j * SPACE_SIZE, i * SPACE_SIZE, SPACE_SIZE, SPACE_SIZE); | |
} | |
} | |
} | |
drawBoard(); | |
// ON PLAYER'S CLICK | |
canvas.addEventListener("click", function(event){ | |
// IF IT's A GAME OVER? EXIT | |
if(GAME_OVER) return; | |
// X & Y position of mouse click relative to the canvas | |
let X = event.clientX - canvas.getBoundingClientRect().x; | |
let Y = event.clientY - canvas.getBoundingClientRect().y; | |
// WE CALCULATE i & j of the clicked SPACE | |
let i = Math.floor(Y/SPACE_SIZE); | |
let j = Math.floor(X/SPACE_SIZE); | |
// Get the id of the space the player clicked on | |
let id = board[i][j]; | |
// Prevent the player to play the same space twice | |
if(gameData[id]) return; | |
// store the player's move to gameData | |
gameData[id] = currentPlayer; | |
// draw the move on board | |
drawOnBoard(currentPlayer, i, j); | |
// Check if the play wins | |
if(isWinner(gameData, currentPlayer)){ | |
showGameOver(currentPlayer); | |
GAME_OVER = true; | |
return; | |
} | |
// check if it's a tie game | |
if(isTie(gameData)){ | |
showGameOver("tie"); | |
GAME_OVER = true; | |
return; | |
} | |
if( OPPONENT == "computer"){ | |
// get id of space using minimax algorithm | |
let id = minimax( gameData, player.computer ).id; | |
// store the player's move to gameData | |
gameData[id] = player.computer; | |
// get i and j of space | |
let space = getIJ(id); | |
// draw the move on board | |
drawOnBoard(player.computer, space.i, space.j); | |
// Check if the play wins | |
if(isWinner(gameData, player.computer)){ | |
showGameOver(player.computer); | |
GAME_OVER = true; | |
return; | |
} | |
// check if it's a tie game | |
if(isTie(gameData)){ | |
showGameOver("tie"); | |
GAME_OVER = true; | |
return; | |
} | |
}else{ | |
// GIVE TURN TO THE OTHER PLAYER | |
currentPlayer = currentPlayer == player.man ? player.friend : player.man; | |
} | |
}); | |
// MINIMAX | |
function minimax(gameData, PLAYER){ | |
// BASE | |
if( isWinner(gameData, player.computer) ) return { evaluation : +10 }; | |
if( isWinner(gameData, player.man) ) return { evaluation : -10 }; | |
if( isTie(gameData) ) return { evaluation : 0 }; | |
// LOOK FOR EMTY SPACES | |
let EMPTY_SPACES = getEmptySpaces(gameData); | |
// SAVE ALL MOVES AND THEIR EVALUATIONS | |
let moves = []; | |
// LOOP OVER THE EMPTY SPACES TO EVALUATE THEM | |
for( let i = 0; i < EMPTY_SPACES.length; i++){ | |
// GET THE ID OF THE EMPTY SPACE | |
let id = EMPTY_SPACES[i]; | |
// BACK UP THE SPACE | |
let backup = gameData[id]; | |
// MAKE THE MOVE FOR THE PLAYER | |
gameData[id] = PLAYER; | |
// SAVE THE MOVE'S ID AND EVALUATION | |
let move = {}; | |
move.id = id; | |
// THE MOVE EVALUATION | |
if( PLAYER == player.computer){ | |
move.evaluation = minimax(gameData, player.man).evaluation; | |
}else{ | |
move.evaluation = minimax(gameData, player.computer).evaluation; | |
} | |
// RESTORE SPACE | |
gameData[id] = backup; | |
// SAVE MOVE TO MOVES ARRAY | |
moves.push(move); | |
} | |
// MINIMAX ALGORITHM | |
let bestMove; | |
if(PLAYER == player.computer){ | |
// MAXIMIZER | |
let bestEvaluation = -Infinity; | |
for(let i = 0; i < moves.length; i++){ | |
if( moves[i].evaluation > bestEvaluation ){ | |
bestEvaluation = moves[i].evaluation; | |
bestMove = moves[i]; | |
} | |
} | |
}else{ | |
// MINIMIZER | |
let bestEvaluation = +Infinity; | |
for(let i = 0; i < moves.length; i++){ | |
if( moves[i].evaluation < bestEvaluation ){ | |
bestEvaluation = moves[i].evaluation; | |
bestMove = moves[i]; | |
} | |
} | |
} | |
return bestMove; | |
} | |
// GET EMPTY SPACES | |
function getEmptySpaces(gameData){ | |
let EMPTY = []; | |
for( let id = 0; id < gameData.length; id++){ | |
if(!gameData[id]) EMPTY.push(id); | |
} | |
return EMPTY; | |
} | |
// GET i AND j of a SPACE | |
function getIJ(id){ | |
for(let i = 0; i < board.length; i++){ | |
for(let j = 0; j < board[i].length; j++){ | |
if(board[i][j] == id) return { i : i, j : j} | |
} | |
} | |
} | |
// check for a winner | |
function isWinner(gameData, player){ | |
for(let i = 0; i < COMBOS.length; i++){ | |
let won = true; | |
for(let j = 0; j < COMBOS[i].length; j++){ | |
let id = COMBOS[i][j]; | |
won = gameData[id] == player && won; | |
} | |
if(won){ | |
return true; | |
} | |
} | |
return false; | |
} | |
// Check for a tie game | |
function isTie(gameData){ | |
let isBoardFill = true; | |
for(let i = 0; i < gameData.length; i++){ | |
isBoardFill = gameData[i] && isBoardFill; | |
} | |
if(isBoardFill){ | |
return true; | |
} | |
return false; | |
} | |
// SHOW GAME OVER | |
function showGameOver(player){ | |
let message = player == "tie" ? "Oops No Winner" : "The Winner is"; | |
let imgSrc = `img/${player}.png`; | |
gameOverElement.innerHTML = ` | |
<h1>${message}</1> | |
<img class="winner-img" src=${imgSrc} </img> | |
<div class="play" onclick="location.reload()">Play Again!</div> | |
`; | |
gameOverElement.classList.remove("hide"); | |
} | |
// draw on board | |
function drawOnBoard(player, i, j){ | |
let img = player == "X" ? xImage : oImage; | |
// the x,y positon of the image are the x,y of the clicked space | |
ctx.drawImage(img, j * SPACE_SIZE, i * SPACE_SIZE); | |
} | |
} |
imgs and font :- https://github.com/official-sharmaji/Tic-Tac-Toe-JavaScript
Comments
Post a Comment