123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- console.log('JavaScript Beginner Projects: Tic Tac Toe!');
- window.addEventListener('DOMContentLoaded', () => {
- _Setup();
- });
- const BOARD_STATE = {
- player: 1,
- ai: 2,
- blank: 3,
- draw: 4,
- };
- let _GAMESTATE = null;
- function _Setup() {
- _CreateBoard();
- _InitializeState();
- }
- function _CreateBoard() {
- const rows = document.getElementById('rows');
- for (let x = 0; x < 3; x++) {
- const curRow = document.createElement('div');
- curRow.id = 'row' + x;
- curRow.className = 'row';
- rows.appendChild(curRow);
- for (let y = 0; y < 3; y++) {
- const node = document.createElement('img');
- node.className = 'square';
- node.id = x + '.' + y;
- node.onclick = _HandlePlayerClick;
- curRow.appendChild(node);
- }
- }
- }
- function _InitializeState() {
- _GAMESTATE = {
- turn: 'player',
- active: true,
- };
- }
- function _HandlePlayerClick(evt) {
- const isBlank = !evt.target.src.length;
- if (isBlank &&
- _GAMESTATE.active &&
- _GAMESTATE.turn == 'player') {
- evt.target.src = 'x.png';
- _CheckGameOver();
- _AISelectMove();
- }
- }
- function _CheckGameOver() {
- const winner = _EvaluateBoard(_GetBoardStates());
- if (winner == null) {
- return;
- }
- _GAMESTATE.active = false;
- let desc = '';
- if (winner == BOARD_STATE.ai) {
- desc = 'You lose!';
- } else if (winner == BOARD_STATE.player) {
- desc = 'You win!';
- } else {
- desc = 'Tie game, try again.'
- }
- document.getElementById('description').innerText = desc;
- }
- function _GetBoardStates() {
- const boardStates = [];
- for (let x = 0; x < 3; x++) {
- const row = [];
- for (let y = 0; y < 3; y++) {
- const node = document.getElementById(x + '.' + y);
- if (node.src.includes('x.png')) {
- row.push(BOARD_STATE.player);
- } else if (node.src.includes('o.png')) {
- row.push(BOARD_STATE.ai);
- } else {
- row.push(BOARD_STATE.blank);
- }
- }
- boardStates.push(row);
- }
- return boardStates;
- }
- function _GetSquareElementNodes() {
- const nodes = [];
- for (let x = 0; x < 3; x++) {
- for (let y = 0; y < 3; y++) {
- nodes.push(document.getElementById(x + '.' + y))
- }
- }
- return nodes;
- }
- function _HighlightSquares(blinks) {
- if (blinks === undefined) {
- blinks = 10;
- }
- const nodes = _GetSquareElementNodes();
- for (const n of nodes) {
- n.className = 'square';
- }
- if (blinks >= 0) {
- setTimeout(() => {
- _AISelectMove(blinks - 1);
- }, 100);
- const x = Math.floor(Math.random() * 3);
- const y = Math.floor(Math.random() * 3);
- const node = document.getElementById(x + '.' + y);
- node.className = 'square highlight';
- return true;
- }
- return false;
- }
- function _AISelectMove(blinks) {
- _GAMESTATE.turn = 'ai';
- if (_HighlightSquares(blinks)) {
- return;
- }
- const boardStates = _GetBoardStates();
- const [_, choice] = _Minimax(boardStates, BOARD_STATE.ai);
- if (choice != null) {
- const [x, y] = choice;
- document.getElementById(x + '.' + y).src = 'o.png';
- }
- _CheckGameOver();
- _GAMESTATE.turn = 'player';
- }
- function _EvaluateBoard(boardStates) {
- const winningStates = [
- // Horizontals
- [[0, 0], [0, 1], [0, 2]],
- [[1, 0], [1, 1], [1, 2]],
- [[2, 0], [2, 1], [2, 2]],
- [[0, 0], [1, 0], [2, 0]],
- [[0, 1], [1, 1], [2, 1]],
- [[0, 2], [1, 2], [2, 2]],
- // Diagonals
- [[0, 0], [1, 1], [2, 2]],
- [[2, 0], [1, 1], [0, 2]],
- ];
- for (const possibleState of winningStates) {
- let curPlayer = null;
- let isWinner = true;
- for (const [x, y] of possibleState) {
- const occupant = boardStates[x][y];
- if (curPlayer == null && occupant != BOARD_STATE.blank) {
- curPlayer = occupant;
- } else if (curPlayer != occupant) {
- isWinner = false;
- }
- }
- if (isWinner) {
- return curPlayer;
- }
- }
- let hasMoves = false;
- for (let x = 0; x < 3; x++) {
- for (let y = 0; y < 3; y++) {
- if (boardStates[x][y] == BOARD_STATE.blank) {
- hasMoves = true;
- }
- }
- }
- if (!hasMoves) {
- return BOARD_STATE.draw;
- }
- return null;
- }
- function _Minimax(boardStates, player) {
- // First check if the game has already been won.
- const winner = _EvaluateBoard(boardStates);
- if (winner == BOARD_STATE.ai) {
- return [1, null];
- } else if (winner == BOARD_STATE.player) {
- return [-1, null];
- }
- let move, moveScore;
- if (player == BOARD_STATE.ai) {
- [moveScore, move] = _Minimax_Maximize(boardStates);
- } else {
- [moveScore, move] = _Minimax_Minimize(boardStates);
- }
- if (move == null) {
- moveScore = 0;
- }
- // No move, so it's a draw
- return [moveScore, move];
- }
- function _Minimax_Maximize(boardStates) {
- let moveScore = Number.NEGATIVE_INFINITY;
- let move = null;
- for (let x = 0; x < 3; x++) {
- for (let y = 0; y < 3; y++) {
- if (boardStates[x][y] == BOARD_STATE.blank) {
- const newBoardStates = boardStates.map(r => r.slice());
- newBoardStates[x][y] = BOARD_STATE.ai;
- const [newMoveScore, _] = _Minimax(
- newBoardStates, BOARD_STATE.player);
- if (newMoveScore > moveScore) {
- move = [x, y];
- moveScore = newMoveScore;
- }
- }
- }
- }
- return [moveScore, move];
- }
- function _Minimax_Minimize(boardStates) {
- let moveScore = Number.POSITIVE_INFINITY;
- let move = null;
- for (let x = 0; x < 3; x++) {
- for (let y = 0; y < 3; y++) {
- if (boardStates[x][y] == BOARD_STATE.blank) {
- const newBoardStates = boardStates.map(r => r.slice());
- newBoardStates[x][y] = BOARD_STATE.player;
- const [newMoveScore, _] = _Minimax(
- newBoardStates, BOARD_STATE.ai);
- if (newMoveScore < moveScore) {
- move = [x, y];
- moveScore = newMoveScore;
- }
- }
- }
- }
- return [moveScore, move];
- }
- function _Minimax2(boardStates, aiTurn) {
- // First check if the game has already been won.
- const winner = _EvaluateBoard(boardStates);
- if (winner == BOARD_STATE.ai) {
- return [1, null];
- } else if (winner == BOARD_STATE.player) {
- return [-1, null];
- }
- let moveCost = Number.NEGATIVE_INFINITY;
- if (!aiTurn) {
- moveCost = Number.POSITIVE_INFINITY;
- }
- let move = null;
- for (let x = 0; x < 3; x++) {
- for (let y = 0; y < 3; y++) {
- if (boardStates[x][y] == BOARD_STATE.blank) {
- const newBoardStates = boardStates.map(r => r.slice());
- if (aiTurn) {
- newBoardStates[x][y] = BOARD_STATE.ai;
- } else {
- newBoardStates[x][y] = BOARD_STATE.player;
- }
- const [newMoveCost, _] = _Minimax(newBoardStates, !aiTurn);
- if (aiTurn) {
- if (newMoveCost > moveCost) {
- move = [x, y];
- moveCost = newMoveCost;
- }
- } else {
- if (newMoveCost < moveCost) {
- move = [x, y];
- moveCost = newMoveCost;
- }
- }
- }
- }
- }
- if (move != null) {
- return [moveCost, move];
- }
- // No move, so it's a draw
- return [0, null];
- }
|