Skip to content

Instantly share code, notes, and snippets.

@jose-almir
Created October 5, 2020 18:58
Show Gist options
  • Save jose-almir/7e78ed3e3aefbcbcc051108d6a962679 to your computer and use it in GitHub Desktop.
Save jose-almir/7e78ed3e3aefbcbcc051108d6a962679 to your computer and use it in GitHub Desktop.
Tic Tac Toe game with HTML/JS
<!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>
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