Created
October 5, 2020 18:58
-
-
Save jose-almir/7e78ed3e3aefbcbcc051108d6a962679 to your computer and use it in GitHub Desktop.
Tic Tac Toe game with HTML/JS
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="pt-br"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>TicTacToe</title> | |
<style> | |
body { | |
margin: 0; | |
box-sizing: border-box; | |
} | |
#board { | |
position: absolute; | |
border: 2px black solid; | |
border-radius: 5px; | |
top: calc(50% - 300px/2); | |
left: calc(50% - 300px/2); | |
cursor: pointer; | |
} | |
</style> | |
</head> | |
<body onload="load()"> | |
<canvas id="board" width="300px" height="300px"> | |
<p>Cannot display canvas!</p> | |
</canvas> | |
<script type="text/javascript" src="tic_tac_toe.js"></script> | |
</body> | |
</html> |
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
let canvas = document.getElementById('board'); | |
let context = canvas.getContext('2d'); | |
let canvasMaxWidth = canvas.width; | |
let canvasMaxHeight = canvas.height; | |
let board; | |
let gameRules; | |
let x_score = 0; | |
let y_score = 0; | |
const X = 1, O = 2; | |
let symbols = [X, O]; | |
let current_total_turns = 1; | |
let segments = [ | |
getFirstLine, | |
getSecondLine, | |
getThirdLine, | |
getFirstCol, | |
getSecondCol, | |
getThirdCol, | |
getFirstDiagonal, | |
getSecondDiagonal | |
]; | |
let paused = false; | |
canvas.addEventListener('click', (e) => { | |
if(!paused){ | |
board.notifyAllListeners(new MouseClickEvent(e.layerX, e.layerY, nextTurn())); | |
} | |
update(); | |
draw(); | |
}); | |
class MouseClickEvent { | |
constructor(x, y, symbol) { | |
this.x = x; | |
this.y = y; | |
this.symbol = symbol; | |
} | |
} | |
class Board { | |
constructor() { | |
this.cells = []; | |
} | |
createCells() { | |
let gap = getCellsGap(); | |
for (let i = 0; i < 3; ++i) { | |
for (let j = 0; j < 3; ++j) { | |
let cell = new CellBoard(j * gap, i * gap, gap); | |
this.cells.push(cell); | |
} | |
} | |
} | |
drawCells() { | |
this.cells.forEach((cell) => cell.draw()); | |
} | |
drawGrid() { | |
for (let i = 0; i < 2; ++i) { | |
let gap = getCellsGap(); | |
let spacing = gap + i * gap; | |
drawLine(spacing, 0, spacing, canvasMaxHeight, 2.0); | |
drawLine(0, spacing, canvasMaxWidth, spacing, 2.0); | |
} | |
} | |
notifyAllListeners(mouseClickEvent) { | |
this.cells.forEach((cell) => cell.update(mouseClickEvent)); | |
} | |
draw() { | |
this.drawCells(); | |
this.drawGrid(); | |
} | |
} | |
class CellBoard { | |
constructor(x, y, d) { | |
this.x = x; | |
this.y = y; | |
this.d = d; | |
this.hasClicked = false; | |
}; | |
update(mouseClickEvent) { | |
if (!this.hasClicked && this.clickWasInside(mouseClickEvent)) { | |
this.hasClicked = true; | |
this.symbol = new CellSymbol(this.getCenter().x, this.getCenter().y, mouseClickEvent.symbol); | |
} | |
} | |
draw() { | |
this.symbol?.draw(); | |
} | |
clickWasInside(mouseClickEvent) { | |
return mouseClickEvent.x >= this.x && | |
mouseClickEvent.x <= this.x + getCellsGap() && | |
mouseClickEvent.y >= this.y && | |
mouseClickEvent.y <= this.y + getCellsGap(); | |
} | |
getCenter() { | |
return { x: this.x + this.d / 2, y: this.y + this.d / 2 }; | |
} | |
} | |
class CellSymbol { | |
constructor(x, y, value) { | |
this.x = x; | |
this.y = y; | |
this.value = value; | |
this.color = 'black'; | |
} | |
draw() { | |
switch (this.value) { | |
case O: | |
drawSymbolO(this.x, this.y, getCellsGap() / 2 - 10, this.color); | |
break; | |
case X: | |
drawSymbolX(getCellsGap() - 15, 10, this.x, this.y, this.color); | |
break; | |
} | |
} | |
} | |
function drawSymbolO(x, y, rad, color) { | |
context.beginPath(); | |
context.strokeStyle = color; | |
context.lineWidth = 10.0; | |
context.arc(x, y, rad, 0, 2 * Math.PI); | |
context.stroke(); | |
} | |
function drawSymbolX(w, h, x, y, color) { | |
context.save(); | |
context.fillStyle = color; | |
context.beginPath(); | |
context.translate(x, y); | |
context.rotate((Math.PI / 180) * 45); | |
context.translate(-x, -y); | |
context.rect(x - w / 2, y - h / 2, w, h); | |
context.rect(x - h / 2, y - w / 2, h, w); | |
context.fill(); | |
context.restore(); | |
} | |
class WinGameEvent { | |
constructor(segment, symbol) { | |
this.segment = segment; | |
this.symbol = symbol; | |
} | |
} | |
class GameRules { | |
boardHasAllCellsFilled() { | |
return board.cells.every((cell) => cell.hasClicked); | |
} | |
checkIfHasWinner() { | |
for (let symbol of symbols) { | |
for (let segment of segments) { | |
if (this.symbolWinInSegment(segment(board.cells), symbol)) { | |
return new WinGameEvent(segment(board.cells), symbol); | |
} | |
} | |
} | |
} | |
symbolWinInSegment(segment, symbol) { | |
return segment.every((cell) => cell.symbol?.value == symbol); | |
} | |
} | |
function getFirstDiagonal(arr) { | |
return [arr[0], arr[4], arr[8]]; | |
} | |
function getSecondDiagonal(arr) { | |
return [arr[2], arr[4], arr[6]]; | |
} | |
function getFirstCol(arr) { | |
return [arr[0], arr[3], arr[6]]; | |
} | |
function getSecondCol(arr) { | |
return [arr[1], arr[4], arr[7]]; | |
} | |
function getThirdCol(arr) { | |
return [arr[2], arr[5], arr[8]]; | |
} | |
function getFirstLine(arr) { | |
return arr.slice(0, 3); | |
} | |
function getSecondLine(arr) { | |
return arr.slice(3, 6); | |
} | |
function getThirdLine(arr) { | |
return arr.slice(6, 9); | |
} | |
function getIndex(row, col) { | |
return row * 3 + col; | |
} | |
function cellHasSymbol(cell, symbol) { | |
return cell.value == symbol; | |
} | |
function drawLine(x0, y0, x1, y1, width, color) { | |
context.beginPath(); | |
context.strokeStyle = color ?? 'black'; | |
context.lineWidth = width; | |
context.lineTo(x0, y0); | |
context.lineTo(x1, y1); | |
context.stroke(); | |
} | |
function getCellsGap() { | |
return canvasMaxWidth / 3; | |
} | |
function nextTurn() { | |
current_total_turns++; | |
return current_total_turns % 2 + 1; | |
} | |
function load() { | |
paused = false; | |
board = new Board(); | |
board.createCells(); | |
gameRules = new GameRules(); | |
draw(); | |
} | |
function update() { | |
let event = gameRules.checkIfHasWinner(); | |
if (event) { | |
event.segment.forEach((cell) => cell.symbol.color = 'green'); | |
paused = true; | |
scheduleReload(); | |
} else if (gameRules.boardHasAllCellsFilled()) { | |
board.cells.forEach((cell) => cell.symbol.color = 'red'); | |
paused = true; | |
scheduleReload(); | |
} | |
} | |
function scheduleReload() { | |
setTimeout(load, 1000); | |
} | |
function draw() { | |
clear(); | |
board.draw(); | |
} | |
function clear() { | |
context.clearRect(0, 0, canvasMaxWidth, canvasMaxHeight); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment