diff --git a/.gitignore b/.gitignore index 6a7d6d8..2a8bc67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +lib/Minimax.md +lib/ProjectExplanation.md + # Logs logs *.log diff --git a/app.js b/app.js deleted file mode 100644 index fe93fbf..0000000 --- a/app.js +++ /dev/null @@ -1,35 +0,0 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; -import { showLocalNetworkIP } from './shownet.js'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const PORT = process.env.PORT || 8080; -showLocalNetworkIP(PORT); - -import fastify from 'fastify'; - -const app = fastify({ - logger: false, -}); - -import fastifyStatic from '@fastify/static'; - -app.register(fastifyStatic.default, { - root: path.join(__dirname, 'public'), -}); - -app.get('/register', (req, res) => { - res.sendFile('index.html'); -}); - -const start = async () => { - try { - await app.listen({ port: PORT, host: '::' }); - } catch (err) { - app.log.error(err); - process.exit(1); - } -}; -start(); diff --git a/dev.js b/dev.js new file mode 100644 index 0000000..7ed7e12 --- /dev/null +++ b/dev.js @@ -0,0 +1,52 @@ +/** This file is only added to run a development static server. */ + +import path from 'path'; +import { fileURLToPath } from 'url'; +import os from 'os'; +import fastify from 'fastify'; +import fastifyStatic from '@fastify/static'; + +const networkInterfaces = os.networkInterfaces(); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const PORT = process.env.PORT || 3000; +const app = fastify({ + logger: false, +}); + +app.register(fastifyStatic.default, { + root: path.join(__dirname, 'lib'), +}); + +app.get('/register', (req, res) => { + res.sendFile('index.html'); +}); + +start(); +showlocalNetAddress(PORT); + +function showlocalNetAddress(PORT) { + console.log(`local : localhost:${PORT}/`); + + const localNetAddress = + networkInterfaces?.wlp2s0?.[0]?.address || + networkInterfaces?.enp3s0f1?.[0]?.address || + networkInterfaces?.['Wi-Fi']?.[1]?.address || + networkInterfaces?.Ethernet?.[1]?.address || + null; + + if (localNetAddress) { + console.log(`network : ${localNetAddress}:${PORT}/`); + } else { + console.log('network : Not Available'); + } +} + +async function start () { + try { + await app.listen({ port: PORT, host: '::' }); + } catch (err) { + app.log.error(err); + process.exit(1); + } +}; diff --git a/public/TicTacToe.js b/lib/TicTacToe.js similarity index 77% rename from public/TicTacToe.js rename to lib/TicTacToe.js index 96b4095..24e98aa 100644 --- a/public/TicTacToe.js +++ b/lib/TicTacToe.js @@ -20,8 +20,12 @@ function randomInteger(min, max) { return Math.round(Math.random() * (max - min) + min); } -/** A single thread tic-tac-toe game representation that can uses - * `minimax` algorithm to search for the best possible move. +function intSum(min, max) { + return ((max - min) + 1) * (min + max) / 2; +} + +/** A single thread, blocking, **tic-tac-toe** class game representation that + * can use the **`minimax`** algorithm to search for the best possible move. */ class TicTacToe { constructor(options = { gridLength, winCount, player }) { @@ -51,7 +55,7 @@ class TicTacToe { this.computerAutoPlay = this.computerAutoPlay.bind(this); this.generateMoves = this.generateMoves.bind(this); this.minimax = this.minimax.bind(this); - this.checkWinner = this.checkWinner.bind(this); + this.evaluate = this.evaluate.bind(this); this.isFinish = this.isFinish.bind(this); this.display = this.display.bind(this); @@ -68,7 +72,7 @@ class TicTacToe { * @description **WARNING**: This method will always **make a move** on an **NA square** * regardless **even** if the **game has already ended** with a winning player or a draw. * - * It is advice to use `this.checkWinner()` and/or `this.isFinish()` first before calling this method. + * It is advice to use `this.evaluate()` and/or `this.isFinish()` first before calling this method. * @param {Number} i row index of the board. * @param {Number} j column index of the row. * @returns `MOVE_INVALID = 0`, `MOVE_SUCCESS = 1`. */ @@ -119,7 +123,7 @@ class TicTacToe { const moves = this.generateMoves(); const bestMove = { score: 0, idx_i: null, idx_j: null }; - const winning = this.checkWinner(); + const winning = this.evaluate(); if (winning === P1) { bestMove.score = 1 * (this.turns <= 0 ? 1 : this.turns); return bestMove; @@ -191,7 +195,7 @@ class TicTacToe { * In other board game engines like; chess and GO the evaluation function might be separated. * @returns no winner `0` | player X `1` | player O `2`. */ - checkWinner() { + evaluate() { // check row - for (let i = 0; i < this.grid; ++i) { let samePiece = 1; @@ -231,34 +235,72 @@ class TicTacToe { } // check diag \ - let samePieceSecondLastDiag = 1; - for (let i = 0; i < this.grid - 1; ++i) { - if ( - this.board[i * this.grid + i] === this.board[(i + 1) * this.grid + (i + 1)] && - this.board[i * this.grid + i] !== NA - ) { - samePieceSecondLastDiag++; - if (samePieceSecondLastDiag === this.pieceWinCount) { - this.winner = this.board[i * this.grid + i]; - return this.board[i * this.grid + i]; + for (let i = 0; i < this.grid; ++i) { + let samePiece = 1, prevPiece = null; + for (let j = 0; j < i + 1; ++j) { + const CURRENT_INDEX = (this.grid - i) + ((this.grid + 1) * j) - 1; + if (prevPiece === this.board[CURRENT_INDEX] && this.board[CURRENT_INDEX] !== NA) { + samePiece++; + if (samePiece === this.pieceWinCount) { + this.winner = this.board[CURRENT_INDEX]; + return this.board[CURRENT_INDEX]; + } + } else { + samePiece = 1; } - } else { - samePieceSecondLastDiag = 1; + prevPiece = this.board[CURRENT_INDEX]; + } + } + + for (let i = 1; i < this.grid; ++i) { + let samePiece = 1, prevPiece = null; + for (let j = 0; this.grid - i - j > 0; ++j) { + const CURRENT_INDEX = (i * this.grid) + ((this.grid + 1) * j); + if (prevPiece === this.board[CURRENT_INDEX] && this.board[CURRENT_INDEX] !== NA) { + samePiece++; + if (samePiece === this.pieceWinCount) { + this.winner = this.board[CURRENT_INDEX]; + return this.board[CURRENT_INDEX]; + } + } else { + samePiece = 1; + } + prevPiece = this.board[CURRENT_INDEX]; } } // check diag / - const diagStep = this.grid - 1; - let samePieceLastDiag = 1; + for (let i = 0; i < this.grid; ++i) { + let samePiece = 1, prevPiece = null; + for (let j = 0; j < i + 1; ++j) { + const CURRENT_INDEX = i + (j * this.grid) - j; + if (prevPiece === this.board[CURRENT_INDEX] && this.board[CURRENT_INDEX] !== NA) { + samePiece++; + if (samePiece === this.pieceWinCount) { + this.winner = this.board[CURRENT_INDEX]; + return this.board[CURRENT_INDEX]; + } + } else { + samePiece = 1; + } + prevPiece = this.board[CURRENT_INDEX]; + } + } + for (let i = 1; i < this.grid; ++i) { - if (this.board[i * diagStep] === this.board[(i + 1) * diagStep] && this.board[i * diagStep] !== NA) { - samePieceLastDiag++; - if (samePieceLastDiag === this.pieceWinCount) { - this.winner = this.board[i * diagStep]; - return this.board[i * diagStep]; + let samePiece = 1, prevPiece = null; + for (let j = 0; this.grid - i - j > 0; ++j) { + const CURRENT_INDEX = ((this.grid * (i + 1)) - 1) + ((this.grid - 1) * j); + if (prevPiece === this.board[CURRENT_INDEX] && this.board[CURRENT_INDEX] !== NA) { + samePiece++; + if (samePiece === this.pieceWinCount) { + this.winner = this.board[CURRENT_INDEX]; + return this.board[CURRENT_INDEX]; + } + } else { + samePiece = 1; } - } else { - samePieceLastDiag = 1; + prevPiece = this.board[CURRENT_INDEX]; } } @@ -290,7 +332,7 @@ class TicTacToe { * **This method will also update the `this.winner` member when called**. * @returns `true` if game ended, `false` if not. */ isFinish() { - const hasWinner = this.checkWinner(); + const hasWinner = this.evaluate(); if (hasWinner === P1 || hasWinner === P2) { return true; } @@ -346,7 +388,7 @@ class TicTacToe { } /** This test will only work for a 3x3 initialized board. */ - testCheckWinner(boardStates, winners) { + testEvaluation(boardStates, winners) { let failedTests = 0; for (let i = 0; i < boardStates.length; ++i) { @@ -354,7 +396,7 @@ class TicTacToe { this.board[j] = boardStates[i][j]; } - if (this.checkWinner() === winners[i]) { + if (this.evaluate() === winners[i]) { console.log('test ', i + 1, ' : PASSED'); } else { console.log('test ', i + 1, ' : FAILED'); diff --git a/public/index.html b/lib/index.html similarity index 92% rename from public/index.html rename to lib/index.html index f83c614..cf4b85c 100644 --- a/public/index.html +++ b/lib/index.html @@ -1,3 +1,5 @@ + +
@@ -53,7 +55,7 @@