From ea96a49238e890ed18276c03d5e34ef18cd8da77 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:12:58 +0000 Subject: [PATCH 01/44] implement changes --- package-lock.json | 31 +-- src/client/scripts/esm/chess/logic/changes.js | 232 ++++++++++++++++++ .../scripts/esm/chess/logic/movepiece.js | 84 +++---- .../scripts/esm/chess/logic/specialmove.js | 37 ++- .../scripts/esm/chess/logic/specialundo.js | 122 --------- .../scripts/esm/game/chess/movesequence.js | 62 +++++ .../scripts/esm/game/chess/selection.js | 8 +- .../scripts/esm/game/gui/guinavigation.js | 15 +- 8 files changed, 371 insertions(+), 220 deletions(-) create mode 100644 src/client/scripts/esm/chess/logic/changes.js delete mode 100644 src/client/scripts/esm/chess/logic/specialundo.js create mode 100644 src/client/scripts/esm/game/chess/movesequence.js diff --git a/package-lock.json b/package-lock.json index b8d0d3bc9..3b6ca56ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2801,19 +2801,21 @@ } }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -3572,9 +3574,9 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -3582,7 +3584,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3614,9 +3616,10 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } diff --git a/src/client/scripts/esm/chess/logic/changes.js b/src/client/scripts/esm/chess/logic/changes.js new file mode 100644 index 000000000..44c31798b --- /dev/null +++ b/src/client/scripts/esm/chess/logic/changes.js @@ -0,0 +1,232 @@ +import organizedlines from "./organizedlines"; +import gamefileutility from "../util/gamefileutility"; +import jsutil from "../../util/jsutil"; + +/** + * @typedef {import('./gamefile.js').gamefile} gamefile + * @typedef {import()} + */ + +/** + * @typedef {Object} Piece + * @property {string} type - The type of the piece (e.g. `queensW`). + * @property {number[]} coords - The coordinates of the piece: `[x,y]` + * @property {number} index - The index of the piece within the gamefile's piece list. + */ + +/** + * @typedef {Object} BoardChange + * @property {string} action + * @property {Piece} piece + */ + +const changeFuncs = { + "add": addPiece, + "delete": deletePiece, + "movePiece": movePiece, + "addRights": addRights, + "deleteRights": deleteRights, + "setPassant": setPassant, +}; + +const undoFuncs = { + "delete": addPiece, + "add": deletePiece, + "movePiece": returnPiece, + "addRights": revertRights, + "deleteRights": revertRights, + "setPassant": revertPassant, +}; + +/** + * @param {Array} changes + * @param {*} type + * @param {*} coords + * @param {*} desiredIndex + */ +function queueAddPiece(changes, type, coords, desiredIndex) { + changes.push({action: 'add', piece: {type: type, coords: coords, index: desiredIndex}}); +}; + +/** + * + * @param {Array} changes + * @param {*} type + * @param {*} coords + * @param {*} index + */ +function queueDeletePiece(changes, type, coords, index) { + changes.push({action: 'delete', piece: {type: type, coords: coords, index: index}}); +} + +/** + * + * @param {Array} changes + * @param {*} piece + * @param {*} endCoords + */ +function queueMovePiece(changes, piece, endCoords) { + changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); +} + +function queueAddSpecialRights(changes, piece, curRights) { + changes.push({action: "addRights", piece: piece, curRights: curRights}); +} + +function queueSetEnPassant(changes, curPassant, newPassant) { + changes.push({action: "setPassant", curPassant: curPassant, newPassant: newPassant}); +} + +/** + * + * @param {gamefile} gamefile + * @param {Array} changes + */ +function applyChanges(gamefile, changes) { + for (const c of changes) { + changeFuncs[c.type](gamefile, c); + } +} + +/** + * + * @param {gamefile} gamefile + * @param {Array} changes + */ +function undoChanges(gamefile, changes) { + for (const c of changes) { + undoFuncs[c.type](gamefile, c); + } +} + +/** + * Most basic add-a-piece method. Adds it the gamefile's piece list, + * organizes the piece in the organized lists, and updates its mesh data. + * @param {gamefile} gamefile - The gamefile + * @param {BoardChange} change - the data of the piece to be added + */ +function addPiece(gamefile, change) { // desiredIndex optional + const piece = change.piece; + + const list = gamefile.ourPieces[piece.type]; + + // If no index specified, make the default the first undefined in the list! + if (piece.index == null) change.piece.index = list.undefineds[0]; + + if (piece.index == null) { + list.push(piece.coords); + } else { // desiredIndex specified + + const isPieceAtCoords = gamefileutility.getPieceTypeAtCoords(gamefile, piece.coords) != null; + if (isPieceAtCoords) throw new Error("Can't add a piece on top of another piece!"); + + // Remove the undefined from the undefineds list + const deleteSuccussful = jsutil.deleteValueFromOrganizedArray(gamefile.ourPieces[piece.type].undefineds, piece.index) !== false; + if (!deleteSuccussful) throw new Error("Index to add a piece has an existing piece on it!"); + + list[piece.index] = piece.coords; + } + + organizedlines.organizePiece(piece.type, piece.coords, gamefile); +} + +/** + * Most basic delete-a-piece method. Deletes it from the gamefile's piece list, + * from the organized lists, and deletes its mesh data (overwrites with zeros). + * @param {gamefile} gamefile - The gamefile + * @param {BoardChange} change + */ +function deletePiece(gamefile, change) { // piece: { type, index } + const piece = change.piece; + + const list = gamefile.ourPieces[piece.type]; + gamefileutility.deleteIndexFromPieceList(list, piece.index); + + // Remove captured piece from organized piece lists + organizedlines.removeOrganizedPiece(gamefile, piece.coords); +} + +/** + * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, + * reorganizes the piece in the organized lists, and updates its mesh data. + * @param {gamefile} gamefile - The gamefile + * @param {BoardChange} change - + */ +function movePiece(gamefile, change) { + const piece = change.piece; + const endCoords = change.endCoords; + + // Move the piece, change the coordinates + gamefile.ourPieces[piece.type][piece.index] = endCoords; + + // Remove selected piece from all the organized piece lists (piecesOrganizedByKey, etc.) + organizedlines.removeOrganizedPiece(gamefile, piece.coords); + + // Add the piece to organized lists with new destination + organizedlines.organizePiece(piece.type, endCoords, gamefile); +} + +/** + * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, + * reorganizes the piece in the organized lists, and updates its mesh data. + * @param {gamefile} gamefile - The gamefile + * @param {BoardChange} change + */ +function returnPiece(gamefile, change) { + const piece = change.piece; + const endCoords = change.endCoords; + + // Move the piece, change the coordinates + gamefile.ourPieces[piece.type][piece.index] = piece.coords; + + // Remove selected piece from all the organized piece lists (piecesOrganizedByKey, etc.) + organizedlines.removeOrganizedPiece(gamefile, endCoords); + + // Add the piece to organized lists with old destination + organizedlines.organizePiece(piece.type, piece.coords, gamefile); +} + +/** + * + * @param {gamefile} gamefile + * @param {*} change + */ +function addRights(gamefile, change) { + gamefile.specialRights[change.piece.coords] = true; +} + +function deleteRights(gamefile, change) { + delete gamefile.specialRights[change.piece.coords]; +} + +function revertRights(gamefile, change) { + if (change.curRights === undefined) { + delete gamefile.specialRights[change.piece.coords]; + } else { + gamefile.specialRights[change.piece.coords] = change.curRights; + } +} + +/** + * + * @param {gamefile} gamefile + * @param {*} change + */ +function setPassant(gamefile, change) { + gamefile.enpassant = change.newPassant; +} + +function revertPassant(gamefile, change) { + i + gamefile.enpassant = change.curPassant; +} + +export default { + queueAddPiece, + queueDeletePiece, + queueMovePiece, + queueAddSpecialRights, + queueSetEnPassant, + applyChanges, + undoChanges, +}; \ No newline at end of file diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index ad61f9b83..a3457e657 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -3,7 +3,7 @@ import legalmoves from './legalmoves.js'; import gamefileutility from '../util/gamefileutility.js'; import specialdetect from './specialdetect.js'; -import arrows from '../../game/rendering/arrows.js'; +import changes from './changes.js'; import clock from './clock.js'; import guiclock from '../../game/gui/guiclock.js'; import organizedlines from './organizedlines.js'; @@ -18,9 +18,6 @@ import colorutil from '../util/colorutil.js'; import jsutil from '../../util/jsutil.js'; import coordutil from '../util/coordutil.js'; import frametracker from '../../game/rendering/frametracker.js'; -import stats from '../../game/gui/stats.js'; -import onlinegame from '../../game/misc/onlinegame.js'; -import game from '../../game/chess/game.js'; // Import End /** @@ -33,43 +30,17 @@ import game from '../../game/chess/game.js'; // Custom type definitions... -/** - * TODO: Move this type definition to a new pieceutil TYPESCRIPT, - * and make the coordinates only length-2. - * - * The Piece Object. - * @typedef {Object} Piece - * @property {string} type - The type of the piece (e.g. `queensW`). - * @property {number[]} coords - The coordinates of the piece: `[x,y]` - * @property {number} index - The index of the piece within the gamefile's piece list. - */ + /** Here lies the universal methods for moving pieces, forward or rewinding. */ -/** - * **Universal** function for executing forward (not rewinding) moves. - * Called when we move the selected piece, receive our opponent's move, - * or need to simulate a move within the checkmate algorithm. - * @param {gamefile} gamefile - The gamefile - * @param {Move} move - The move to make, with the properties `startCoords`, `endCoords`, and any special move flags, all other properties of the move will be added within. CRUCIAL: If `simulated` is true and `recordMove` is false, the move passed in MUST be one of the moves in the gamefile's move list! Otherwise we'll have trouble undo'ing the simulated move without messing up the mesh. - * @param {Object} options - An object containing various options (ALL of these are default *true*, EXCEPT `simulated` which is default *false*): - * - `flipTurn`: Whether to flip the `whosTurn` property of the gamefile. Most of the time this will be true, except when hitting the rewind/forward buttons. - * - `recordMove`: Whether to record the move in the gamefile's move list. Should be false when rewinding/fast-forwarding the game. - * - `pushClock`: Whether to push the clock. If we're in an online game we NEVER push the clock anyway, only the server does. - * - `doGameOverChecks`: Whether to perform game-over checks, such as checkmate or other win conditions. - * - `concludeGameIfOver`: If true, and `doGameOverChecks` is true, then if this move ends the game, we will not stop the clocks, darken the board, display who won, or play a sound effect. - * - `animate`: Whether to animate this move. - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh after this. - * - `updateProperties`: Whether to update gamefile properties that game-over algorithms rely on, such as the 50-move-rule's status, or 3-Check's check counter. - * - `simulated`: Whether you plan on undo'ing this move. If true, the `rewindInfo` property will be added to the `move` for easy restoring of the gamefile's properties when undo'ing the move. - */ -function makeMove(gamefile, move, { flipTurn = true, recordMove = true, pushClock = true, doGameOverChecks = true, concludeGameIfOver = true, animate = true, updateData = true, updateProperties = true, simulated = false } = {}) { +function generateMove(gamefile, move) { const piece = gamefileutility.getPieceAtCoords(gamefile, move.startCoords); if (!piece) throw new Error(`Cannot make move because no piece exists at coords ${move.startCoords}.`); move.type = piece.type; const trimmedType = colorutil.trimColorExtensionFromType(move.type); // "queens" - storeRewindInfoOnMove(gamefile, move, piece.index, { simulated }); // Keep track if important stuff to remember, for rewinding the game if we undo moves + storeRewindInfoOnMove(gamefile, move, piece.index); // Keep track if important stuff to remember, for rewinding the game if we undo moves // Do this before making the move, so that if its a pawn double push, enpassant can be reinstated and not deleted. if (recordMove || updateProperties) deleteEnpassantAndSpecialRightsProperties(gamefile, move.startCoords, move.endCoords); @@ -77,33 +48,43 @@ function makeMove(gamefile, move, { flipTurn = true, recordMove = true, pushCloc let specialMoveMade; if (gamefile.specialMoves[trimmedType]) specialMoveMade = gamefile.specialMoves[trimmedType](gamefile, piece, move, { updateData, animate, updateProperties, simulated }); if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move, { updateData, recordMove, animate, simulated }); // Move piece regularly (no special tag) +} + +function makeMove(gamefile, move) { const wasACapture = move.captured != null; + changes.applyChanges(gamefile, move.changes); + gamefile.moveIndex++; - if (recordMove) gamefile.moves.push(move); + gamefile.moves.push(move); + // The "check" property will be added inside updateInCheck()... // The "mate" property will be added inside our game conclusion checks... if (updateProperties) incrementMoveRule(gamefile, piece.type, wasACapture); - if (flipTurn) flipWhosTurn(gamefile, { pushClock, doGameOverChecks }); - // ALWAYS DO THIS NOW, no matter what. updateInCheck(gamefile, recordMove); - if (doGameOverChecks) { - gamefileutility.doGameOverChecks(gamefile); - if (!simulated && concludeGameIfOver && gamefile.gameConclusion && !onlinegame.areInOnlineGame()) game.concludeGame(); - } - - if (updateData) { - guinavigation.update_MoveButtons(); - stats.setTextContentOfMoves(); // Making a move should change the move number in the stats - frametracker.onVisualChange(); - } - - if (!simulated) arrows.clearListOfHoveredPieces(); } +/** + * **Universal** function for executing forward (not rewinding) moves. + * Called when we move the selected piece, receive our opponent's move, + * or need to simulate a move within the checkmate algorithm. + * @param {gamefile} gamefile - The gamefile + * @param {Move} move - The move to make, with the properties `startCoords`, `endCoords`, and any special move flags, all other properties of the move will be added within. CRUCIAL: If `simulated` is true and `recordMove` is false, the move passed in MUST be one of the moves in the gamefile's move list! Otherwise we'll have trouble undo'ing the simulated move without messing up the mesh. + * @param {Object} options - An object containing various options (ALL of these are default *true*, EXCEPT `simulated` which is default *false*): + * - `flipTurn`: Whether to flip the `whosTurn` property of the gamefile. Most of the time this will be true, except when hitting the rewind/forward buttons. + * - `recordMove`: Whether to record the move in the gamefile's move list. Should be false when rewinding/fast-forwarding the game. + * - `pushClock`: Whether to push the clock. If we're in an online game we NEVER push the clock anyway, only the server does. + * - `doGameOverChecks`: Whether to perform game-over checks, such as checkmate or other win conditions. + * - `concludeGameIfOver`: If true, and `doGameOverChecks` is true, then if this move ends the game, we will not stop the clocks, darken the board, display who won, or play a sound effect. + * - `animate`: Whether to animate this move. + * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh after this. + * - `updateProperties`: Whether to update gamefile properties that game-over algorithms rely on, such as the 50-move-rule's status, or 3-Check's check counter. + * - `simulated`: Whether you plan on undo'ing this move. If true, the `rewindInfo` property will be added to the `move` for easy restoring of the gamefile's properties when undo'ing the move. + */ + /** * Stores crucial game information for rewinding this move on the move object. * Upon rewinding, this information will be deleted. @@ -162,6 +143,8 @@ function deleteEnpassantAndSpecialRightsProperties(gamefile, startCoords, endCoo * - `simulated`: Whether you plan on undo'ing this move. If true, the index of the captured piece within the gamefile's piece list will be stored in the `rewindInfo` property of the move for easy undo'ing without screwing up the mesh. */ function movePiece_NoSpecial(gamefile, piece, move, { updateData = true, animate = true, simulated = false } = {}) { // piece: { coords, type, index } + const moveChanges = []; + const capturedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); if (capturedPiece) move.captured = capturedPiece.type; if (capturedPiece && simulated) move.rewindInfo.capturedIndex = capturedPiece.index; @@ -170,7 +153,7 @@ function movePiece_NoSpecial(gamefile, piece, move, { updateData = true, animate movePiece(gamefile, piece, move.endCoords, { updateData }); - if (animate) animation.animatePiece(piece.type, move.startCoords, move.endCoords, capturedPiece); + //if (animate) animation.animatePiece(piece.type, move.startCoords, move.endCoords, capturedPiece); } /** @@ -560,9 +543,6 @@ function stripSpecialMoveTagsFromCoords(coords) { return [coords[0], coords[1]]; export default { makeMove, - movePiece, - addPiece, - deletePiece, makeAllMovesInGame, calculateMoveFromShortmove, forwardToFront, diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index d2c70465a..72c990b13 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -1,8 +1,6 @@ - // Import Start import gamefileutility from '../util/gamefileutility.js'; -import animation from '../../game/rendering/animation.js'; -import movepiece from './movepiece.js'; +import changes from './changes.js'; import coordutil from '../util/coordutil.js'; // Import End @@ -37,14 +35,15 @@ function getFunctions() { // Called when the piece moved is a king. // Tests if the move contains "castle" special move, if so it executes it! // RETURNS FALSE if special move was not executed! -function kings(gamefile, piece, move, { updateData = true, animate = true, updateProperties = true, simulated = false } = {}) { +function kings(gamefile, piece, move) { + const moveChanges = []; const specialTag = move.castle; // { dir: -1/1, coord } - if (!specialTag) return false; // No special move to execute, return false to signify we didn't move the piece. + if (!specialTag) return moveChanges; // No special move to execute, return false to signify we didn't move the piece. // Move the king to new square - movepiece.movePiece(gamefile, piece, move.endCoords, { updateData }); // Make normal move + changes.queueMovePiece(moveChanges, piece, move.endCoords); // Make normal move // Move the rook to new square @@ -52,27 +51,23 @@ function kings(gamefile, piece, move, { updateData = true, animate = true, updat const landSquare = [move.endCoords[0] - specialTag.dir, move.endCoords[1]]; // Delete the rook's special move rights const key = coordutil.getKeyFromCoords(pieceToCastleWith.coords); - delete gamefile.specialRights[key]; - movepiece.movePiece(gamefile, pieceToCastleWith, landSquare, { updateData }); // Make normal move + changes.queueDeleteSpecialRights(moveChanges, pieceToCastleWith, gamefile.specialRights[key]); - if (animate) { - animation.animatePiece(piece.type, piece.coords, move.endCoords); // King - const resetAnimations = false; - animation.animatePiece(pieceToCastleWith.type, pieceToCastleWith.coords, landSquare, undefined, resetAnimations); // Castled piece - } + changes.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move // Special move was executed! // There is no captured piece with castling - return true; + return moveChanges; } -function pawns(gamefile, piece, move, { updateData = true, animate = true, updateProperties = true, simulated = false } = {}) { +function pawns(gamefile, piece, move, {updateProperties = true, simulated = false } = {}) { // If it was a double push, then add the enpassant flag to the gamefile, and remove its special right! if (updateProperties && isPawnMoveADoublePush(piece.coords, move.endCoords)) { gamefile.enpassant = getEnPassantSquare(piece.coords, move.endCoords); } + const moveChanges = []; const enpassantTag = move.enpassant; // -1/1 const promotionTag = move.promotion; // promote type if (!enpassantTag && !promotionTag) return false; ; // No special move to execute, return false to signify we didn't move the piece. @@ -84,23 +79,21 @@ function pawns(gamefile, piece, move, { updateData = true, animate = true, updat if (capturedPiece && simulated) move.rewindInfo.capturedIndex = capturedPiece.index; // Delete the piece captured - if (capturedPiece) movepiece.deletePiece(gamefile, capturedPiece, { updateData }); + if (capturedPiece) changes.queueDeleteChange(moveChanges, capturedPiece); if (promotionTag) { // Delete original pawn - movepiece.deletePiece(gamefile, piece, { updateData }); + changes.queueDeleteChange(moveChanges, piece); - movepiece.addPiece(gamefile, promotionTag, move.endCoords, null, { updateData }); + changes.queueAddChange(moveChanges, promotionTag, move.endCoords, null); } else /* enpassantTag */ { // Move the pawn - movepiece.movePiece(gamefile, piece, move.endCoords, { updateData }); + changes.queueMoveChange(moveChanges, piece, move.endCoords); } - if (animate) animation.animatePiece(piece.type, piece.coords, move.endCoords, capturedPiece); - // Special move was executed! - return true; + return moveChanges; } function isPawnMoveADoublePush(pawnCoords, endCoords) { return Math.abs(pawnCoords[1] - endCoords[1]) === 2; } diff --git a/src/client/scripts/esm/chess/logic/specialundo.js b/src/client/scripts/esm/chess/logic/specialundo.js deleted file mode 100644 index 467d96c8d..000000000 --- a/src/client/scripts/esm/chess/logic/specialundo.js +++ /dev/null @@ -1,122 +0,0 @@ - -// Import Start -import gamefileutility from '../util/gamefileutility.js'; -import movepiece from './movepiece.js'; -import animation from '../../game/rendering/animation.js'; -import colorutil from '../util/colorutil.js'; -import coordutil from '../util/coordutil.js'; -// Import End - -"use strict"; - -/** This script returns the functions for UNDOING special moves */ - -// This returns the functions for undo'ing special moves. -// In the future, parameters can be added if variants have -// different special moves for pieces. -function getFunctions() { - return { - "kings": kings, - "royalCentaurs": kings, - "pawns": pawns - }; -} - -// A custom special move needs to be able to: -// * Delete a custom piece -// * Move a custom piece -// * Add a custom piece - - -// ALL FUNCTIONS NEED TO: -// * Make the move -// * Animate the piece - - -// Called when the moved piece to undo is a king -// Tests if the move contains "castle" special move, if so it undos it! -// RETURNS FALSE if no special move was detected! -function kings(gamefile, move, { updateData = true, animate = true } = {}) { - - const specialTag = move.castle; // { dir, coord } - if (!specialTag) return false; // No special move to undo, return false to signify we didn't undo the move. - - // Move the king back - - let movedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); // Returns { type, index, coords } - movepiece.movePiece(gamefile, movedPiece, move.startCoords, { updateData }); // Changes the pieces coords and data in the organized lists without making any captures. - - // Move the rook back - - const kingCoords = movedPiece.coords; - const castledPieceCoords = [kingCoords[0] - specialTag.dir, kingCoords[1]]; - movedPiece = gamefileutility.getPieceAtCoords(gamefile, castledPieceCoords); // Returns { type, index, coords } - movepiece.movePiece(gamefile, movedPiece, specialTag.coord, { updateData }); // Changes the pieces coords and data in the organized lists without making any captures. - // Restore the rook's special move rights if this is a simulated move - // (the kings special move rights are restored within checkdetection.doesMovePutInCheck()) - if (!updateData) { - const key = coordutil.getKeyFromCoords(specialTag.coord); - gamefile.specialRights[key] = true; - } - - - if (animate) { - animation.animatePiece(move.type, move.endCoords, move.startCoords); - const resetAnimations = false; - animation.animatePiece(movedPiece.type, castledPieceCoords, specialTag.coord, undefined, resetAnimations); // Castled piece - } - - return true; // Special move has been undo'd! -} - -// pawnIndex should be specified if it's a promotion move we're undoing -function pawns(gamefile, move, { updateData = true, animate = true } = {}) { - - const enpassantTag = move.enpassant; // -1/1 - const promotionTag = move.promotion; // promote type - const isDoublePush = Math.abs(move.endCoords[1] - move.startCoords[1]) === 2; - if (!enpassantTag && !promotionTag && !isDoublePush) return false; // No special move to execute, return false to signify we didn't move the piece. - - - // First move piece back - - const movedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); // Returns { type, index, coords }1 - - // Detect promotion - if (move.promotion) { // Was a promotion move - // Delete promoted piece - const WorB = colorutil.getColorExtensionFromType(movedPiece.type); - movepiece.deletePiece(gamefile, movedPiece, { updateData }); - // Replace pawn back where it originally was - const type = "pawns" + WorB; - movepiece.addPiece(gamefile, type, move.startCoords, move.rewindInfo.pawnIndex, { updateData }); - } else { // Move it back normally - movepiece.movePiece(gamefile, movedPiece, move.startCoords, { updateData }); // Changes the pieces coords and data in the organized lists without making any captures. - // Remove the gamefile's enpassant flag ONLY if this is a simulated move! - if (!updateData && isDoublePush) { - delete gamefile.enpassant; - } - } - - // Next replace piece captured - - // Detect en passant - if (move.enpassant) { // Was an an passant capture - const type = move.captured; - const captureCoords = [ move.endCoords[0], move.endCoords[1] + move.enpassant ]; - movepiece.addPiece(gamefile, type, captureCoords, move.rewindInfo.capturedIndex, { updateData }); - - } else if (move.captured) { // Was NOT an passant, BUT there was a capture - const type = move.captured; - movepiece.addPiece(gamefile, type, move.endCoords, move.rewindInfo.capturedIndex, { updateData }); - } - - - if (animate) animation.animatePiece(move.type, move.endCoords, move.startCoords); - - return true; // Special move has been undo'd! -} - -export default { - getFunctions, -}; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/movesequence.js b/src/client/scripts/esm/game/chess/movesequence.js new file mode 100644 index 000000000..5d7ac46c3 --- /dev/null +++ b/src/client/scripts/esm/game/chess/movesequence.js @@ -0,0 +1,62 @@ +import gamefileutility from "../../chess/util/gamefileutility"; +import onlinegame from "../misc/onlinegame"; +import game from "./game"; +import arrows from "../rendering/arrows"; +import frametracker from "../rendering/frametracker"; +import stats from "../gui/stats"; +import guinavigation from "../gui/guinavigation"; +import moveutil from "../../chess/util/moveutil"; +import guigameinfo from "../gui/guigameinfo"; + +function runMove(gamefile, { doGameOverChecks = true, updateData = true, concludeGameIfOver = true}) { + + if (flipTurn) flipWhosTurn(gamefile, { pushClock, doGameOverChecks }); + + if (doGameOverChecks) { + gamefileutility.doGameOverChecks(gamefile); + if (concludeGameIfOver && gamefile.gameConclusion && !onlinegame.areInOnlineGame()) game.concludeGame(); + } + + if (updateData) { + guinavigation.update_MoveButtons(); + stats.setTextContentOfMoves(); // Making a move should change the move number in the stats + frametracker.onVisualChange(); + } + + arrows.clearListOfHoveredPieces(); +} + +/** + * Fast-forwards the game to front, to the most-recently played move. + * @param {gamefile} gamefile - The gamefile + * @param {Object} options - An object containing various options (ALL of these are default *true*): + * - `flipTurn`: Whether each forwarded move should flip whosTurn. This should be false when forwarding to the game's front after rewinding. + * - `animateLastMove`: Whether to animate the last move, or most-recently played. + * - `updateData`: Whether to modify the mesh of all the pieces. Should be false if we plan on regenerating the model manually after forwarding. + * - `updateProperties`: Whether each move should update gamefile properties that game-over algorithms rely on, such as the 50-move-rule's status, or 3-Check's check counter. + * - `simulated`: Whether you plan on undo'ing this forward, rewinding back to where you were. If true, the `rewindInfo` property will be added to each forwarded move in the gamefile for easy reverting when it comes time. + */ + +function forwardToFront(gamefile, { animateLastMove = true } = {}) { + + while (true) { // For as long as we have moves to forward... + const nextIndex = gamefile.moveIndex + 1; + if (moveutil.isIndexOutOfRange(gamefile.moves, nextIndex)) break; + + const nextMove = moveutil.getMoveFromIndex(gamefile.moves, nextIndex); + + const isLastMove = moveutil.isIndexTheLastMove(gamefile.moves, nextIndex); + const animate = animateLastMove && isLastMove; + makeMove(gamefile, nextMove, { recordMove: false, pushClock: false, doGameOverChecks: false, flipTurn: false, animate, updateData: true, updateProperties:false, simulated: false }); + } + + guigameinfo.updateWhosTurn(gamefile); + + // lock the rewind/forward buttons for a brief moment. + guinavigation.lockRewind(); +} + +export default { + runMove, + forwardToFront, +}; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index 841d6252e..f57a47bf9 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -23,6 +23,7 @@ import colorutil from '../../chess/util/colorutil.js'; import coordutil from '../../chess/util/coordutil.js'; import frametracker from '../rendering/frametracker.js'; import config from '../config.js'; +import movesequence from './movesequence.js'; // Import End /** @@ -205,12 +206,9 @@ function handleSelectingPiece(pieceClickedType) { // ^^ The extra conditions needed here so in edit mode and you click on an opponent piece // it will still forward you to front! - return movepiece.forwardToFront(gamefile, { flipTurn: false, updateProperties: false }); + return movesequence.forwardToFront(gamefile, { flipTurn: false, updateProperties: false }); } - // If it's your turn, select that piece. - - // if (clickedPieceColor !== gamefile.whosTurn && !options.getEM()) return; // Don't select opposite color if (hoverSquareLegal) return; // Don't select different piece if the move is legal (its a capture) const clickedPieceColor = colorutil.getPieceColorFromType(pieceClickedType); if (!options.getEM() && clickedPieceColor === colorutil.colorOfNeutrals) return; // Don't select neutrals, unless we're in edit mode @@ -292,7 +290,7 @@ function moveGamefilePiece(coords) { const compact = formatconverter.LongToShort_CompactMove(move); move.compact = compact; - movepiece.makeMove(game.getGamefile(), move); + movesequence.makeMove(game.getGamefile(), move); onlinegame.sendMove(); unselectPiece(); diff --git a/src/client/scripts/esm/game/gui/guinavigation.js b/src/client/scripts/esm/game/gui/guinavigation.js index bf46c9771..bf3d71a44 100644 --- a/src/client/scripts/esm/game/gui/guinavigation.js +++ b/src/client/scripts/esm/game/gui/guinavigation.js @@ -15,6 +15,8 @@ import stats from './stats.js'; import movepiece from '../../chess/logic/movepiece.js'; import selection from '../chess/selection.js'; import frametracker from '../rendering/frametracker.js'; +import changes from '../../chess/logic/changes.js'; +import movesequence from '../chess/movesequence.js'; // Import End "use strict"; @@ -402,13 +404,16 @@ function rewindMove() { /** Forwards the currently-loaded gamefile by 1 move. Unselects any piece, updates the rewind/forward move buttons. */ function forwardMove() { - if (game.getGamefile().mesh.locked) return statustext.pleaseWaitForTask(); - if (!moveutil.isIncrementingLegal(game.getGamefile())) return stats.showMoves(); + const gamefile = game.getGamefile() + + if (gamefile.mesh.locked) return statustext.pleaseWaitForTask(); + if (!moveutil.isIncrementingLegal(gamefile)) return stats.showMoves(); - const move = moveutil.getMoveOneForward(game.getGamefile()); + const move = moveutil.getMoveOneForward(gamefile); - // Only leave animate and updateData as true - movepiece.makeMove(game.getGamefile(), move, { flipTurn: false, recordMove: false, pushClock: false, doGameOverChecks: false, updateProperties: false }); + gamefile.moveIndex++; + changes.applyChanges(gamefile, move.changes); + movesequence.animateChanges(gamefile, move.changes); // transition.teleportToLastMove() From da85876d3b3831ac1f5ac9717e8aec866600970b Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:12:58 +0000 Subject: [PATCH 02/44] more clean up --- .../logic/{changes.js => boardchanges.js} | 46 ++++---- .../scripts/esm/chess/logic/movepiece.js | 103 +++++------------- .../scripts/esm/chess/logic/specialmove.js | 20 ++-- src/client/scripts/esm/chess/util/moveutil.js | 2 + .../scripts/esm/game/gui/guinavigation.js | 2 +- 5 files changed, 66 insertions(+), 107 deletions(-) rename src/client/scripts/esm/chess/logic/{changes.js => boardchanges.js} (84%) diff --git a/src/client/scripts/esm/chess/logic/changes.js b/src/client/scripts/esm/chess/logic/boardchanges.js similarity index 84% rename from src/client/scripts/esm/chess/logic/changes.js rename to src/client/scripts/esm/chess/logic/boardchanges.js index 44c31798b..75033e8da 100644 --- a/src/client/scripts/esm/chess/logic/changes.js +++ b/src/client/scripts/esm/chess/logic/boardchanges.js @@ -1,6 +1,6 @@ -import organizedlines from "./organizedlines"; -import gamefileutility from "../util/gamefileutility"; -import jsutil from "../../util/jsutil"; +import organizedlines from "./organizedlines.js"; +import gamefileutility from "../util/gamefileutility.js"; +import jsutil from "../../util/jsutil.js"; /** * @typedef {import('./gamefile.js').gamefile} gamefile @@ -40,23 +40,19 @@ const undoFuncs = { /** * @param {Array} changes - * @param {*} type - * @param {*} coords - * @param {*} desiredIndex + * @param {Piece} piece */ -function queueAddPiece(changes, type, coords, desiredIndex) { - changes.push({action: 'add', piece: {type: type, coords: coords, index: desiredIndex}}); +function queueAddPiece(changes, piece) { + changes.push({action: 'add', piece: piece}); }; /** * * @param {Array} changes - * @param {*} type - * @param {*} coords - * @param {*} index + * @param {Piece} piece */ -function queueDeletePiece(changes, type, coords, index) { - changes.push({action: 'delete', piece: {type: type, coords: coords, index: index}}); +function queueDeletePiece(changes, piece) { + changes.push({action: 'delete', piece: piece}); } /** @@ -69,8 +65,12 @@ function queueMovePiece(changes, piece, endCoords) { changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); } -function queueAddSpecialRights(changes, piece, curRights) { - changes.push({action: "addRights", piece: piece, curRights: curRights}); +function queueAddSpecialRights(changes, coords, curRights) { + changes.push({action: "addRights", coords: coords, curRights: curRights}); +} + +function queueDeleteSpecialRights(changes, coords, curRights) { + changes.push({action: "removeRights", coords: coords, curRights: curRights}); } function queueSetEnPassant(changes, curPassant, newPassant) { @@ -192,18 +192,18 @@ function returnPiece(gamefile, change) { * @param {*} change */ function addRights(gamefile, change) { - gamefile.specialRights[change.piece.coords] = true; + gamefile.specialRights[change.coords] = true; } function deleteRights(gamefile, change) { - delete gamefile.specialRights[change.piece.coords]; + delete gamefile.specialRights[change.coords]; } function revertRights(gamefile, change) { if (change.curRights === undefined) { - delete gamefile.specialRights[change.piece.coords]; + delete gamefile.specialRights[change.coords]; } else { - gamefile.specialRights[change.piece.coords] = change.curRights; + gamefile.specialRights[change.coords] = change.curRights; } } @@ -217,8 +217,11 @@ function setPassant(gamefile, change) { } function revertPassant(gamefile, change) { - i - gamefile.enpassant = change.curPassant; + if (change.curPassant === undefined) { + delete gamefile.enpassant; + } else { + gamefile.enpassant = change.curPassant; + } } export default { @@ -226,6 +229,7 @@ export default { queueDeletePiece, queueMovePiece, queueAddSpecialRights, + queueDeleteSpecialRights, queueSetEnPassant, applyChanges, undoChanges, diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index a3457e657..e88e70fcd 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -3,11 +3,11 @@ import legalmoves from './legalmoves.js'; import gamefileutility from '../util/gamefileutility.js'; import specialdetect from './specialdetect.js'; -import changes from './changes.js'; +import boardchanges from './boardchanges.js'; import clock from './clock.js'; import guiclock from '../../game/gui/guiclock.js'; import organizedlines from './organizedlines.js'; -import animation from '../../game/rendering/animation.js'; +import wincondition from './wincondition.js'; import guinavigation from '../../game/gui/guinavigation.js'; import piecesmodel from '../../game/rendering/piecesmodel.js'; import guigameinfo from '../../game/gui/guigameinfo.js'; @@ -35,6 +35,8 @@ import frametracker from '../../game/rendering/frametracker.js'; /** Here lies the universal methods for moving pieces, forward or rewinding. */ function generateMove(gamefile, move) { + move.changes = []; + const piece = gamefileutility.getPieceAtCoords(gamefile, move.startCoords); if (!piece) throw new Error(`Cannot make move because no piece exists at coords ${move.startCoords}.`); move.type = piece.type; @@ -43,17 +45,17 @@ function generateMove(gamefile, move) { storeRewindInfoOnMove(gamefile, move, piece.index); // Keep track if important stuff to remember, for rewinding the game if we undo moves // Do this before making the move, so that if its a pawn double push, enpassant can be reinstated and not deleted. - if (recordMove || updateProperties) deleteEnpassantAndSpecialRightsProperties(gamefile, move.startCoords, move.endCoords); + deleteEnpassantAndSpecialRightsProperties(gamefile, move); let specialMoveMade; - if (gamefile.specialMoves[trimmedType]) specialMoveMade = gamefile.specialMoves[trimmedType](gamefile, piece, move, { updateData, animate, updateProperties, simulated }); - if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move, { updateData, recordMove, animate, simulated }); // Move piece regularly (no special tag) + if (gamefile.specialMoves[trimmedType]) specialMoveMade = gamefile.specialMoves[trimmedType](gamefile, piece, move); + if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move); // Move piece regularly (no special tag) } -function makeMove(gamefile, move) { +function makeMove(gamefile, move, { updateProperties, recordCheck }) { const wasACapture = move.captured != null; - changes.applyChanges(gamefile, move.changes); + boardchanges.applyChanges(gamefile, move.changes); gamefile.moveIndex++; gamefile.moves.push(move); @@ -61,10 +63,10 @@ function makeMove(gamefile, move) { // The "check" property will be added inside updateInCheck()... // The "mate" property will be added inside our game conclusion checks... - if (updateProperties) incrementMoveRule(gamefile, piece.type, wasACapture); + if (updateProperties) incrementMoveRule(gamefile, move.type, wasACapture); // ALWAYS DO THIS NOW, no matter what. - updateInCheck(gamefile, recordMove); + updateInCheck(gamefile, recordCheck); } /** @@ -118,15 +120,14 @@ function storeRewindInfoOnMove(gamefile, move, pieceIndex, { simulated = false } * Deletes the gamefile's enpassant property, and the moving piece's special right. * This needs to be done every time we make a move. * @param {gamefile} gamefile - The gamefile - * @param {number[]} startCoords - The coordinates of the piece moving - * @param {number[]} endCoords - The destination of the piece moving + * @param {Move} move */ -function deleteEnpassantAndSpecialRightsProperties(gamefile, startCoords, endCoords) { - delete gamefile.enpassant; - let key = coordutil.getKeyFromCoords(startCoords); - delete gamefile.specialRights[key]; // We also delete its special move right for ANY piece moved - key = coordutil.getKeyFromCoords(endCoords); - delete gamefile.specialRights[key]; // We also delete the captured pieces specialRights for ANY move. +function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { + boardchanges.queueSetEnPassant(move.changes, gamefile.enpassant, undefined); + let key = coordutil.getKeyFromCoords(move.startCoords); + boardchanges.queueDeleteSpecialRights(move.changes, key, gamefile.specialRights[key]); + key = coordutil.getKeyFromCoords(move.endCoords); + boardchanges.queueDeleteSpecialRights(move.changes, key, gamefile.specialRights[key]); // We also delete the captured pieces specialRights for ANY move. } // RETURNS index of captured piece! Required for undo'ing moves. @@ -142,18 +143,15 @@ function deleteEnpassantAndSpecialRightsProperties(gamefile, startCoords, endCoo * - `animate`: Whether to animate this move. * - `simulated`: Whether you plan on undo'ing this move. If true, the index of the captured piece within the gamefile's piece list will be stored in the `rewindInfo` property of the move for easy undo'ing without screwing up the mesh. */ -function movePiece_NoSpecial(gamefile, piece, move, { updateData = true, animate = true, simulated = false } = {}) { // piece: { coords, type, index } - const moveChanges = []; +function movePiece_NoSpecial(gamefile, piece, move) { // piece: { coords, type, index } const capturedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); if (capturedPiece) move.captured = capturedPiece.type; - if (capturedPiece && simulated) move.rewindInfo.capturedIndex = capturedPiece.index; - if (capturedPiece) deletePiece(gamefile, capturedPiece, { updateData }); + if (capturedPiece) boardchanges.queueDeletePiece(move.changes, capturedPiece); - movePiece(gamefile, piece, move.endCoords, { updateData }); + boardchanges.queueMovePiece(move.changes, piece, move.endCoords); - //if (animate) animation.animatePiece(piece.type, move.startCoords, move.endCoords, capturedPiece); } /** @@ -434,72 +432,26 @@ function rewindGameToIndex(gamefile, moveIndex, { removeMove = true, updateData * - `removeMove`: Whether to delete the move from the gamefile's move list. Should be true if we're undo'ing simulated moves. * - `animate`: Whether to animate this rewinding. */ -function rewindMove(gamefile, { updateData = true, removeMove = true, animate = true } = {}) { +function rewindMove(gamefile, removeMove = true ) { const move = moveutil.getMoveFromIndex(gamefile.moves, gamefile.moveIndex); // { type, startCoords, endCoords, captured } - const trimmedType = colorutil.trimColorExtensionFromType(move.type); - let isSpecialMove = false; - if (gamefile.specialUndos[trimmedType]) isSpecialMove = gamefile.specialUndos[trimmedType](gamefile, move, { updateData, animate }); - if (!isSpecialMove) rewindMove_NoSpecial(gamefile, move, { updateData, animate }); + boardchanges.undoChanges(gamefile, move.changes); // inCheck and attackers are always restored, no matter if we're deleting the move or not. gamefile.inCheck = move.rewindInfo.inCheck; if (move.rewindInfo.attackers) gamefile.attackers = move.rewindInfo.attackers; if (removeMove) { // Restore original values - gamefile.enpassant = move.rewindInfo.enpassant; gamefile.moveRuleState = move.rewindInfo.moveRuleState; gamefile.checksGiven = move.rewindInfo.checksGiven; - if (move.rewindInfo.specialRightStart) { // Restore their special right - const key = coordutil.getKeyFromCoords(move.startCoords); - gamefile.specialRights[key] = true; - } - if (move.rewindInfo.specialRightEnd) { // Restore their special right - const key = coordutil.getKeyFromCoords(move.endCoords); - gamefile.specialRights[key] = true; - } gamefile.gameConclusion = move.rewindInfo.gameConclusion; // Simulated moves may or may not have performed game over checks. } - // The capturedIndex and pawnIndex are only used for undo'ing - // simulated moves, so that we don't screw up the mesh - delete move.rewindInfo.capturedIndex; - delete move.rewindInfo.pawnIndex; // Finally, delete the move off the top of our moves [] array list if (removeMove) moveutil.deleteLastMove(gamefile.moves); gamefile.moveIndex--; if (removeMove) flipWhosTurn(gamefile, { pushClock: false, doGameOverChecks: false }); - - // if (animate) updateInCheck(gamefile, false) - // No longer needed, as rewinding the move restores the inCheck property. - // updateInCheck(gamefile, false) - - if (updateData) { - guinavigation.update_MoveButtons(); - frametracker.onVisualChange(); - } -} - -/** - * Standardly rewinds a move. Adds back any captured piece. Animates if specified. - * If the move was a special move, a separate method is needed. - * @param {gamefile} gamefile - The gamefile - * @param {Move} move - The move that's being undo'd - * @param {Object} options - An object containing various options (ALL of these are default *true*): - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh afterward. - * - `animate`: Whether to animate this move. - */ -function rewindMove_NoSpecial(gamefile, move, { updateData = true, animate = true } = {}) { - const movedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); // Returns { type, index, coords } - movePiece(gamefile, movedPiece, move.startCoords, { updateData }); // Changes the pieces coords and data in the organized lists without making any captures. - - if (move.captured) { // Replace the piece captured - const type = move.captured; - addPiece(gamefile, type, move.endCoords, move.rewindInfo.capturedIndex, { updateData }); - } - - if (animate) animation.animatePiece(move.type, move.endCoords, move.startCoords); } /** @@ -516,19 +468,20 @@ function rewindMove_NoSpecial(gamefile, move, { updateData = true, animate = tru */ function simulateMove(gamefile, move, colorToTestInCheck, { doGameOverChecks = false } = {}) { // Moves the piece without unselecting it or regenerating the pieces model. - makeMove(gamefile, move, { pushClock: false, animate: false, updateData: false, simulated: true, doGameOverChecks, updateProperties: doGameOverChecks }); - + generateMove(gamefile, move); + makeMove(gamefile, move, {recordCheck: true, updateProperties: doGameOverChecks}); + // What info can we pull from the game after simulating this move? const info = { isCheck: doGameOverChecks ? gamefile.inCheck : checkdetection.detectCheck(gamefile, colorToTestInCheck, []), - gameConclusion: doGameOverChecks ? gamefile.gameConclusion : undefined + gameConclusion: doGameOverChecks ? wincondition.getGameConclusion(gamefile) : undefined }; // Undo the move, REWIND. // We don't have to worry about the index changing, it is the same. // BUT THE CAPTURED PIECE MUST be inserted in the exact location! // Only remove the move - rewindMove(gamefile, { updateData: false, animate: false }); + rewindMove(gamefile, true); return info; // Info from simulating the move: { isCheck, gameConclusion } } diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index 72c990b13..ac97a6b4e 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -1,6 +1,6 @@ // Import Start import gamefileutility from '../util/gamefileutility.js'; -import changes from './changes.js'; +import boardchanges from './boardchanges.js'; import coordutil from '../util/coordutil.js'; // Import End @@ -43,7 +43,7 @@ function kings(gamefile, piece, move) { // Move the king to new square - changes.queueMovePiece(moveChanges, piece, move.endCoords); // Make normal move + boardchanges.queueMovePiece(moveChanges, piece, move.endCoords); // Make normal move // Move the rook to new square @@ -51,9 +51,9 @@ function kings(gamefile, piece, move) { const landSquare = [move.endCoords[0] - specialTag.dir, move.endCoords[1]]; // Delete the rook's special move rights const key = coordutil.getKeyFromCoords(pieceToCastleWith.coords); - changes.queueDeleteSpecialRights(moveChanges, pieceToCastleWith, gamefile.specialRights[key]); + boardchanges.queueDeleteSpecialRights(moveChanges, pieceToCastleWith, gamefile.specialRights[key]); - changes.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move + boardchanges.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move // Special move was executed! // There is no captured piece with castling @@ -61,13 +61,13 @@ function kings(gamefile, piece, move) { } function pawns(gamefile, piece, move, {updateProperties = true, simulated = false } = {}) { + const moveChanges = []; // If it was a double push, then add the enpassant flag to the gamefile, and remove its special right! if (updateProperties && isPawnMoveADoublePush(piece.coords, move.endCoords)) { - gamefile.enpassant = getEnPassantSquare(piece.coords, move.endCoords); + boardchanges.queueSetEnPassant(moveChanges, gamefile.enpassant, getEnPassantSquare(piece.coords, move.endCoords)); } - const moveChanges = []; const enpassantTag = move.enpassant; // -1/1 const promotionTag = move.promotion; // promote type if (!enpassantTag && !promotionTag) return false; ; // No special move to execute, return false to signify we didn't move the piece. @@ -79,17 +79,17 @@ function pawns(gamefile, piece, move, {updateProperties = true, simulated = fals if (capturedPiece && simulated) move.rewindInfo.capturedIndex = capturedPiece.index; // Delete the piece captured - if (capturedPiece) changes.queueDeleteChange(moveChanges, capturedPiece); + if (capturedPiece) boardchanges.queueDeleteChange(moveChanges, capturedPiece); if (promotionTag) { // Delete original pawn - changes.queueDeleteChange(moveChanges, piece); + boardchanges.queueDeleteChange(moveChanges, piece); - changes.queueAddChange(moveChanges, promotionTag, move.endCoords, null); + boardchanges.queueAddChange(moveChanges, promotionTag, move.endCoords, null); } else /* enpassantTag */ { // Move the pawn - changes.queueMoveChange(moveChanges, piece, move.endCoords); + boardchanges.queueMoveChange(moveChanges, piece, move.endCoords); } // Special move was executed! diff --git a/src/client/scripts/esm/chess/util/moveutil.js b/src/client/scripts/esm/chess/util/moveutil.js index 017ab70e5..fbe889577 100644 --- a/src/client/scripts/esm/chess/util/moveutil.js +++ b/src/client/scripts/esm/chess/util/moveutil.js @@ -22,6 +22,8 @@ function Move() { /** The type of piece moved (e.g. `queensW`). */ this.type = undefined; + /** @type {Array} */ + this.changes = undefined; /** The start coordinates of the piece: `[x,y]` */ this.startCoords = undefined; /** The end coordinates of the piece: `[x,y]` */ diff --git a/src/client/scripts/esm/game/gui/guinavigation.js b/src/client/scripts/esm/game/gui/guinavigation.js index bf3d71a44..c999358f0 100644 --- a/src/client/scripts/esm/game/gui/guinavigation.js +++ b/src/client/scripts/esm/game/gui/guinavigation.js @@ -15,7 +15,7 @@ import stats from './stats.js'; import movepiece from '../../chess/logic/movepiece.js'; import selection from '../chess/selection.js'; import frametracker from '../rendering/frametracker.js'; -import changes from '../../chess/logic/changes.js'; +import changes from '../../chess/logic/boardchanges.js'; import movesequence from '../chess/movesequence.js'; // Import End From 75c2e2a6823d32fb345511b13f7906771b1abcd9 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:12:58 +0000 Subject: [PATCH 03/44] make nextTurn a sequence call --- .../scripts/esm/chess/logic/movepiece.js | 40 +++---------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index e88e70fcd..ce2eaf557 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -69,24 +69,6 @@ function makeMove(gamefile, move, { updateProperties, recordCheck }) { updateInCheck(gamefile, recordCheck); } -/** - * **Universal** function for executing forward (not rewinding) moves. - * Called when we move the selected piece, receive our opponent's move, - * or need to simulate a move within the checkmate algorithm. - * @param {gamefile} gamefile - The gamefile - * @param {Move} move - The move to make, with the properties `startCoords`, `endCoords`, and any special move flags, all other properties of the move will be added within. CRUCIAL: If `simulated` is true and `recordMove` is false, the move passed in MUST be one of the moves in the gamefile's move list! Otherwise we'll have trouble undo'ing the simulated move without messing up the mesh. - * @param {Object} options - An object containing various options (ALL of these are default *true*, EXCEPT `simulated` which is default *false*): - * - `flipTurn`: Whether to flip the `whosTurn` property of the gamefile. Most of the time this will be true, except when hitting the rewind/forward buttons. - * - `recordMove`: Whether to record the move in the gamefile's move list. Should be false when rewinding/fast-forwarding the game. - * - `pushClock`: Whether to push the clock. If we're in an online game we NEVER push the clock anyway, only the server does. - * - `doGameOverChecks`: Whether to perform game-over checks, such as checkmate or other win conditions. - * - `concludeGameIfOver`: If true, and `doGameOverChecks` is true, then if this move ends the game, we will not stop the clocks, darken the board, display who won, or play a sound effect. - * - `animate`: Whether to animate this move. - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh after this. - * - `updateProperties`: Whether to update gamefile properties that game-over algorithms rely on, such as the 50-move-rule's status, or 3-Check's check counter. - * - `simulated`: Whether you plan on undo'ing this move. If true, the `rewindInfo` property will be added to the `move` for easy restoring of the gamefile's properties when undo'ing the move. - */ - /** * Stores crucial game information for rewinding this move on the move object. * Upon rewinding, this information will be deleted. @@ -104,13 +86,8 @@ function storeRewindInfoOnMove(gamefile, move, pieceIndex, { simulated = false } rewindInfo.inCheck = jsutil.deepCopyObject(gamefile.inCheck); rewindInfo.gameConclusion = gamefile.gameConclusion; if (gamefile.attackers) rewindInfo.attackers = jsutil.deepCopyObject(gamefile.attackers); - if (gamefile.enpassant) rewindInfo.enpassant = gamefile.enpassant; - if (gamefile.moveRuleState != null) rewindInfo.moveRuleState = gamefile.moveRuleState; + if (gamefile.moveRuleState !== undefined) rewindInfo.moveRuleState = gamefile.moveRuleState; if (gamefile.checksGiven) rewindInfo.checksGiven = gamefile.checksGiven; - let key = coordutil.getKeyFromCoords(move.startCoords); - if (gamefile.specialRights[key]) rewindInfo.specialRightStart = true; - key = coordutil.getKeyFromCoords(move.endCoords); - if (gamefile.specialRights[key]) rewindInfo.specialRightEnd = true; } move.rewindInfo = rewindInfo; @@ -264,12 +241,10 @@ function incrementMoveRule(gamefile, typeMoved, wasACapture) { * - `pushClock`: Whether to push the clock. * - `doGameOverChecks`: Whether game-over checks such as checkmate, or other win conditions, are performed for this move. */ -function flipWhosTurn(gamefile, { pushClock = true, doGameOverChecks = true } = {}) { +function nextTurn(gamefile, { pushClock = true } = {}) { gamefile.whosTurn = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); - if (doGameOverChecks) guigameinfo.updateWhosTurn(gamefile); if (pushClock) { clock.push(gamefile); - guiclock.push(gamefile); }; } @@ -414,12 +389,10 @@ function forwardToFront(gamefile, { flipTurn = true, animateLastMove = true, upd * - `removeMove`: Whether to delete the moves in the gamefile's moves list while rewinding. * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh after this. */ -function rewindGameToIndex(gamefile, moveIndex, { removeMove = true, updateData = true } = {}) { +function rewindGameToIndex(gamefile, moveIndex, { removeMove = true } = {}) { if (removeMove && !moveutil.areWeViewingLatestMove(gamefile)) return console.error("Cannot rewind game to index while deleting moves unless we start at the most recent move. forwardToFront() first."); if (gamefile.moveIndex < moveIndex) return console.error("Cannot rewind game to index when we need to forward instead."); - while (gamefile.moveIndex > moveIndex) rewindMove(gamefile, { animate: false, updateData, removeMove }); - guigameinfo.updateWhosTurn(gamefile); - frametracker.onVisualChange(); + while (gamefile.moveIndex > moveIndex) rewindMove(gamefile, removeMove); } /** @@ -432,7 +405,7 @@ function rewindGameToIndex(gamefile, moveIndex, { removeMove = true, updateData * - `removeMove`: Whether to delete the move from the gamefile's move list. Should be true if we're undo'ing simulated moves. * - `animate`: Whether to animate this rewinding. */ -function rewindMove(gamefile, removeMove = true ) { +function rewindMove(gamefile, { removeMove = true } = {} ) { const move = moveutil.getMoveFromIndex(gamefile.moves, gamefile.moveIndex); // { type, startCoords, endCoords, captured } @@ -450,8 +423,6 @@ function rewindMove(gamefile, removeMove = true ) { // Finally, delete the move off the top of our moves [] array list if (removeMove) moveutil.deleteLastMove(gamefile.moves); gamefile.moveIndex--; - - if (removeMove) flipWhosTurn(gamefile, { pushClock: false, doGameOverChecks: false }); } /** @@ -496,6 +467,7 @@ function stripSpecialMoveTagsFromCoords(coords) { return [coords[0], coords[1]]; export default { makeMove, + nextTurn, makeAllMovesInGame, calculateMoveFromShortmove, forwardToFront, From 370b031bbaf12bb980580cefed16afc4ba3a24ab Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:13:16 +0000 Subject: [PATCH 04/44] completed boardchanges --- .../{boardchanges.js => boardchanges.ts} | 184 ++++++++---------- .../scripts/esm/chess/logic/checkdetection.js | 1 + .../scripts/esm/chess/logic/movepiece.js | 142 +++----------- .../{movesequence.js => movesequence.ts} | 77 +++++++- src/client/scripts/esm/util/math.js | 12 ++ 5 files changed, 197 insertions(+), 219 deletions(-) rename src/client/scripts/esm/chess/logic/{boardchanges.js => boardchanges.ts} (52%) rename src/client/scripts/esm/game/chess/{movesequence.js => movesequence.ts} (53%) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.js b/src/client/scripts/esm/chess/logic/boardchanges.ts similarity index 52% rename from src/client/scripts/esm/chess/logic/boardchanges.js rename to src/client/scripts/esm/chess/logic/boardchanges.ts index 75033e8da..ed65dc6c6 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.js +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -2,110 +2,96 @@ import organizedlines from "./organizedlines.js"; import gamefileutility from "../util/gamefileutility.js"; import jsutil from "../../util/jsutil.js"; -/** - * @typedef {import('./gamefile.js').gamefile} gamefile - * @typedef {import()} - */ +import type { gamefile } from "./gamefile.js"; +import type { Coords } from "./movesets.js"; +import type { Move } from "../util/moveutil.js"; -/** - * @typedef {Object} Piece - * @property {string} type - The type of the piece (e.g. `queensW`). - * @property {number[]} coords - The coordinates of the piece: `[x,y]` - * @property {number} index - The index of the piece within the gamefile's piece list. - */ +interface Piece { + type: string // - The type of the piece (e.g. `queensW`). + coords: Coords // - The coordinates of the piece: `[x,y]` + index: Number // - The index of the piece within the gamefile's piece list. +} -/** - * @typedef {Object} BoardChange - * @property {string} action - * @property {Piece} piece - */ +interface Change { + action: string, -const changeFuncs = { - "add": addPiece, - "delete": deletePiece, - "movePiece": movePiece, - "addRights": addRights, - "deleteRights": deleteRights, - "setPassant": setPassant, -}; + [data: string]: any +} + +interface ActionList { + [actionName: string]: (gamefile: gamefile, change: Change) => void +} + +interface ChangeApplication { + forward: ActionList + + backward: ActionList +} -const undoFuncs = { - "delete": addPiece, - "add": deletePiece, - "movePiece": returnPiece, - "addRights": revertRights, - "deleteRights": revertRights, - "setPassant": revertPassant, +const changeFuncs: ChangeApplication = { + forward: { + "add": addPiece, + "delete": deletePiece, + "movePiece": movePiece, + "capturePiece": capturePiece, + "setRights": setRights, + "setPassant": setPassant, + }, + + backward: { + "delete": addPiece, + "add": deletePiece, + "movePiece": returnPiece, + "capturePiece": uncapturePiece, + "setRights": revertRights, + "setPassant": revertPassant, + } }; -/** - * @param {Array} changes - * @param {Piece} piece - */ -function queueAddPiece(changes, piece) { +function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { + changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}) // Need to differentiate this from move so animations can work +} + +function queueAddPiece(changes: Array, piece: Piece) { changes.push({action: 'add', piece: piece}); }; -/** - * - * @param {Array} changes - * @param {Piece} piece - */ -function queueDeletePiece(changes, piece) { +function queueDeletePiece(changes: Array, piece: Piece) { changes.push({action: 'delete', piece: piece}); } -/** - * - * @param {Array} changes - * @param {*} piece - * @param {*} endCoords - */ -function queueMovePiece(changes, piece, endCoords) { +function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); } -function queueAddSpecialRights(changes, coords, curRights) { - changes.push({action: "addRights", coords: coords, curRights: curRights}); -} - -function queueDeleteSpecialRights(changes, coords, curRights) { - changes.push({action: "removeRights", coords: coords, curRights: curRights}); +function queueSetSpecialRights(changes, coords, curRights, rights) { + changes.push({action: "setRights", coords: coords, curRights: curRights, rights: rights}); } function queueSetEnPassant(changes, curPassant, newPassant) { changes.push({action: "setPassant", curPassant: curPassant, newPassant: newPassant}); } -/** - * - * @param {gamefile} gamefile - * @param {Array} changes - */ -function applyChanges(gamefile, changes) { +function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { for (const c of changes) { - changeFuncs[c.type](gamefile, c); + if (c.action in funcs) { + funcs[c.action](gamefile, c); + } } } -/** - * - * @param {gamefile} gamefile - * @param {Array} changes - */ -function undoChanges(gamefile, changes) { - for (const c of changes) { - undoFuncs[c.type](gamefile, c); - } +function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { + let funcs = forward ? changeFuncs.forward : changeFuncs.backward + applyChanges(gamefile, move.changes, funcs) } /** * Most basic add-a-piece method. Adds it the gamefile's piece list, * organizes the piece in the organized lists, and updates its mesh data. - * @param {gamefile} gamefile - The gamefile - * @param {BoardChange} change - the data of the piece to be added + * @param gamefile - The gamefile + * @param change - the data of the piece to be added */ -function addPiece(gamefile, change) { // desiredIndex optional +function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional const piece = change.piece; const list = gamefile.ourPieces[piece.type]; @@ -121,7 +107,7 @@ function addPiece(gamefile, change) { // desiredIndex optional if (isPieceAtCoords) throw new Error("Can't add a piece on top of another piece!"); // Remove the undefined from the undefineds list - const deleteSuccussful = jsutil.deleteValueFromOrganizedArray(gamefile.ourPieces[piece.type].undefineds, piece.index) !== false; + const deleteSuccussful = jsutil.deleteValueFromOrganizedArray(gamefile.ourPieces[piece.type].undefineds, piece.index) !== undefined; if (!deleteSuccussful) throw new Error("Index to add a piece has an existing piece on it!"); list[piece.index] = piece.coords; @@ -130,12 +116,6 @@ function addPiece(gamefile, change) { // desiredIndex optional organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -/** - * Most basic delete-a-piece method. Deletes it from the gamefile's piece list, - * from the organized lists, and deletes its mesh data (overwrites with zeros). - * @param {gamefile} gamefile - The gamefile - * @param {BoardChange} change - */ function deletePiece(gamefile, change) { // piece: { type, index } const piece = change.piece; @@ -146,12 +126,6 @@ function deletePiece(gamefile, change) { // piece: { type, index } organizedlines.removeOrganizedPiece(gamefile, piece.coords); } -/** - * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, - * reorganizes the piece in the organized lists, and updates its mesh data. - * @param {gamefile} gamefile - The gamefile - * @param {BoardChange} change - - */ function movePiece(gamefile, change) { const piece = change.piece; const endCoords = change.endCoords; @@ -166,12 +140,6 @@ function movePiece(gamefile, change) { organizedlines.organizePiece(piece.type, endCoords, gamefile); } -/** - * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, - * reorganizes the piece in the organized lists, and updates its mesh data. - * @param {gamefile} gamefile - The gamefile - * @param {BoardChange} change - */ function returnPiece(gamefile, change) { const piece = change.piece; const endCoords = change.endCoords; @@ -186,17 +154,22 @@ function returnPiece(gamefile, change) { organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -/** - * - * @param {gamefile} gamefile - * @param {*} change - */ -function addRights(gamefile, change) { - gamefile.specialRights[change.coords] = true; +function capturePiece(gamefile: gamefile, change: Change) { + deletePiece(gamefile, {piece: change.capturedPiece, action: ""}) + movePiece(gamefile, change) } -function deleteRights(gamefile, change) { - delete gamefile.specialRights[change.coords]; +function uncapturePiece(gamefile: gamefile, change: Change) { + returnPiece(gamefile, change) + addPiece(gamefile, {piece: change.capturedPiece, action:""}) +} + +function setRights(gamefile, change) { + if (change.rights === undefined) { + delete gamefile.specialRights[change.coords]; + } else { + gamefile.specialRights[change.coords] = change.rights; + } } function revertRights(gamefile, change) { @@ -223,14 +196,15 @@ function revertPassant(gamefile, change) { gamefile.enpassant = change.curPassant; } } +export type { ChangeApplication, Change } export default { queueAddPiece, queueDeletePiece, queueMovePiece, - queueAddSpecialRights, - queueDeleteSpecialRights, + queueCaputure, + queueSetSpecialRights, queueSetEnPassant, - applyChanges, - undoChanges, + + runMove, }; \ No newline at end of file diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index a5221d260..69883bfe6 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -9,6 +9,7 @@ import math from '../../util/math.js'; import colorutil from '../util/colorutil.js'; import jsutil from '../../util/jsutil.js'; import coordutil from '../util/coordutil.js'; +import boardchanges from './boardchanges.js'; // Import End /** diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index ce2eaf557..5383e1d76 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -5,19 +5,13 @@ import gamefileutility from '../util/gamefileutility.js'; import specialdetect from './specialdetect.js'; import boardchanges from './boardchanges.js'; import clock from './clock.js'; -import guiclock from '../../game/gui/guiclock.js'; -import organizedlines from './organizedlines.js'; -import wincondition from './wincondition.js'; -import guinavigation from '../../game/gui/guinavigation.js'; -import piecesmodel from '../../game/rendering/piecesmodel.js'; -import guigameinfo from '../../game/gui/guigameinfo.js'; +import math from '../../util/math.js'; import moveutil from '../util/moveutil.js'; import checkdetection from './checkdetection.js'; import formatconverter from './formatconverter.js'; import colorutil from '../util/colorutil.js'; import jsutil from '../../util/jsutil.js'; import coordutil from '../util/coordutil.js'; -import frametracker from '../../game/rendering/frametracker.js'; // Import End /** @@ -123,101 +117,16 @@ function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { function movePiece_NoSpecial(gamefile, piece, move) { // piece: { coords, type, index } const capturedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); - if (capturedPiece) move.captured = capturedPiece.type; - if (capturedPiece) boardchanges.queueDeletePiece(move.changes, capturedPiece); + if (capturedPiece) { + boardchanges.queueCaputure(move.changes, piece, move.endCoords, capturedPiece); + return; + }; boardchanges.queueMovePiece(move.changes, piece, move.endCoords); } -/** - * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, - * reorganizes the piece in the organized lists, and updates its mesh data. - * @param {gamefile} gamefile - The gamefile - * @param {Piece} piece - The piece being moved - * @param {number[]} endCoords - The destination coordinates - * @param {Object} options - An object that may contain the property `updateData`, that when true will update the piece in the mesh. - */ -function movePiece(gamefile, piece, endCoords, { updateData = true } = {}) { - // Move the piece, change the coordinates - gamefile.ourPieces[piece.type][piece.index] = endCoords; - - // Remove selected piece from all the organized piece lists (piecesOrganizedByKey, etc.) - organizedlines.removeOrganizedPiece(gamefile, piece.coords); - - // Add the piece to organized lists with new destination - organizedlines.organizePiece(piece.type, endCoords, gamefile); - - // Edit its data within the mesh of the pieces! - if (updateData) piecesmodel.movebufferdata(gamefile, piece, endCoords); -} - -/** - * Most basic add-a-piece method. Adds it the gamefile's piece list, - * organizes the piece in the organized lists, and updates its mesh data. - * @param {gamefile} gamefile - The gamefile - * @param {string} type - The type of piece to place - * @param {number[]} coords - The coordinates - * @param {number} [desiredIndex] - Optional. If specified, this will place the piece at that index within the gamefile's piece list. This is crucial for undo'ing simulated moves so as to not screw up the mesh. - * @param {Object} options - An object that may contain the property `updateData`, that when true will update the piece in the mesh. - */ -function addPiece(gamefile, type, coords, desiredIndex, { updateData = true } = {}) { // desiredIndex optional - - const list = gamefile.ourPieces[type]; - - // If no index specified, make the default the first undefined in the list! - if (desiredIndex == null) desiredIndex = list.undefineds[0]; - - // If there are no undefined placeholders left, updateData better be false, because we are going to append on the end! - if (desiredIndex == null && updateData) throw new Error("Cannot add a piece and update the data when there are no undefined placeholders remaining!"); - - if (desiredIndex == null) list.push(coords); - else { // desiredIndex specified - - const isPieceAtCoords = gamefileutility.getPieceTypeAtCoords(gamefile, coords) != null; - if (isPieceAtCoords) throw new Error("Can't add a piece on top of another piece!"); - - // Remove the undefined from the undefineds list - const deleteSuccussful = jsutil.deleteValueFromOrganizedArray(gamefile.ourPieces[type].undefineds, desiredIndex) !== false; - if (!deleteSuccussful) throw new Error("Index to add a piece has an existing piece on it!"); - - list[desiredIndex] = coords; - } - - organizedlines.organizePiece(type, coords, gamefile); - - if (!updateData) return; - - // Edit its data within the pieces buffer! - const undefinedPiece = { type, index: desiredIndex }; - piecesmodel.overwritebufferdata(gamefile, undefinedPiece, coords, type); - - // Do we need to add more undefineds? - // Only adding pieces can ever reduce the number of undefineds we have, so we do that here! - if (organizedlines.areWeShortOnUndefineds(gamefile)) organizedlines.addMoreUndefineds(gamefile, { log: true }); -} - -/** - * Most basic delete-a-piece method. Deletes it from the gamefile's piece list, - * from the organized lists, and deletes its mesh data (overwrites with zeros). - * @param {gamefile} gamefile - The gamefile - * @param {string} type - The type of piece to place - * @param {number[]} coords - The coordinates - * @param {number} [desiredIndex] - Optional. If specified, this will place the piece at that index within the gamefile's piece list. This is crucial for undo'ing simulated moves so as to not screw up the mesh. - * @param {Object} options - An object that may contain the property `updateData`, that when true will update the piece in the mesh. - */ -function deletePiece(gamefile, piece, { updateData = true } = {}) { // piece: { type, index } - - const list = gamefile.ourPieces[piece.type]; - gamefileutility.deleteIndexFromPieceList(list, piece.index); - - // Remove captured piece from organized piece lists - organizedlines.removeOrganizedPiece(gamefile, piece.coords); - - // Delete its data within the pieces buffer! Overwrite with 0's - if (updateData) piecesmodel.deletebufferdata(gamefile, piece); -} /** * Increments the gamefile's moveRuleStatus property, if the move-rule is in use. @@ -239,9 +148,8 @@ function incrementMoveRule(gamefile, typeMoved, wasACapture) { * @param {gamefile} gamefile - The gamefile * @param {Object} options - An object that may contain the options (all are default *true*): * - `pushClock`: Whether to push the clock. - * - `doGameOverChecks`: Whether game-over checks such as checkmate, or other win conditions, are performed for this move. */ -function nextTurn(gamefile, { pushClock = true } = {}) { +function updateTurnData(gamefile, { pushClock = true } = {}) { gamefile.whosTurn = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); if (pushClock) { clock.push(gamefile); @@ -343,9 +251,32 @@ function calculateMoveFromShortmove(gamefile, shortmove) { break; } + generateMove(gamefile, move) + return move; } +/** + * + * @param {gamefile} gamefile + * @param {number} targetIndex + */ +function forEachMove(gamefile, targetIndex, callback) { + if (gamefile.moves.length >= targetIndex) return console.error("Cannot!"); //TODO: make this error useful + + let i = gamefile.moveIndex; + + while (true) { + i = math.moveTowards(i, targetIndex, 1) + const move = gamefile.moves[i] + + callback(move) + + if (i === moveIndex) break; + } + +} + /** * Fast-forwards the game to front, to the most-recently played move. * @param {gamefile} gamefile - The gamefile @@ -364,21 +295,6 @@ function forwardToFront(gamefile, { flipTurn = true, animateLastMove = true, upd return; } - while (true) { // For as long as we have moves to forward... - const nextIndex = gamefile.moveIndex + 1; - if (moveutil.isIndexOutOfRange(gamefile.moves, nextIndex)) break; - - const nextMove = moveutil.getMoveFromIndex(gamefile.moves, nextIndex); - - const isLastMove = moveutil.isIndexTheLastMove(gamefile.moves, nextIndex); - const animate = animateLastMove && isLastMove; - makeMove(gamefile, nextMove, { recordMove: false, pushClock: false, doGameOverChecks: false, flipTurn, animate, updateData, updateProperties, simulated }); - } - - if (!simulated) guigameinfo.updateWhosTurn(gamefile); - - // If updateData is true, lock the rewind/forward buttons for a brief moment. - if (updateData) guinavigation.lockRewind(); } /** diff --git a/src/client/scripts/esm/game/chess/movesequence.js b/src/client/scripts/esm/game/chess/movesequence.ts similarity index 53% rename from src/client/scripts/esm/game/chess/movesequence.js rename to src/client/scripts/esm/game/chess/movesequence.ts index 5d7ac46c3..5809e70b3 100644 --- a/src/client/scripts/esm/game/chess/movesequence.js +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -7,10 +7,20 @@ import stats from "../gui/stats"; import guinavigation from "../gui/guinavigation"; import moveutil from "../../chess/util/moveutil"; import guigameinfo from "../gui/guigameinfo"; +import movepiece from "../../chess/logic/movepiece"; + +import boardchanges from "../../chess/logic/boardchanges"; + +import animation from "../rendering/animation"; +import piecesmodel from "../rendering/piecesmodel"; +import organizedlines from "../../chess/logic/organizedlines"; + +import type { ChangeApplication, Change } from "../../chess/logic/boardchanges"; +import type gamefile from "../../chess/logic/gamefile"; function runMove(gamefile, { doGameOverChecks = true, updateData = true, concludeGameIfOver = true}) { - if (flipTurn) flipWhosTurn(gamefile, { pushClock, doGameOverChecks }); + movepiece.nextTurn(gamefile); if (doGameOverChecks) { gamefileutility.doGameOverChecks(gamefile); @@ -56,7 +66,72 @@ function forwardToFront(gamefile, { animateLastMove = true } = {}) { guinavigation.lockRewind(); } +const animatableChanges: ChangeApplication = { + forward: { + "movePiece": animateMove, + "capturedPiece": animateCapture, + }, + + backward: { + "movePiece": animateReturn, + "capturePiece": animateReturn, + } +}; + +function animateMove(gamefile: gamefile, change: Change) { + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords); +} + +function animateReturn(gamefile: gamefile, change: Change) { + animation.animatePiece(change.piece.type, change.endCoords, change.piece.coords); +} + +function animateCapture(gamefile: gamefile, change: Change) { + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, change.capturedPiece); +} + +const meshChanges: ChangeApplication = { + forward: { + "add": addMeshPiece, + "delete": deleteMeshPiece, + "movePiece": moveMeshPiece, + }, + + backward: { + "delete": addMeshPiece, + "add": deleteMeshPiece, + "movePiece": returnMeshPiece, + } +}; + +function addMeshPiece(gamefile: gamefile, change) { + piecesmodel.overwritebufferdata(gamefile, change.piece, change.piece.coords, change.piecetype); + + // Do we need to add more undefineds? + // Only adding pieces can ever reduce the number of undefineds we have, so we do that here! + if (organizedlines.areWeShortOnUndefineds(gamefile)) organizedlines.addMoreUndefineds(gamefile, { log: true }); +} + +function deleteMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.deletebufferdata(gamefile, change.piece); +} + +function moveMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.movebufferdata(gamefile, change.piece, change.endCoords); +} + +function returnMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.movebufferdata(gamefile, change.piece, change.piece.coords); +} + +function animateMoveAtIdx(gamefile: gamefile, moveIdx = gamefile.moveIndex, forward = true) { + const move = gamefile.moves[moveIdx] + if (move === undefined) return + boardchanges.runMove(gamefile, move, animatableChanges, forward) +} + export default { runMove, forwardToFront, + animateMoveAtIdx, }; \ No newline at end of file diff --git a/src/client/scripts/esm/util/math.js b/src/client/scripts/esm/util/math.js index 2fc624285..fb638dc27 100644 --- a/src/client/scripts/esm/util/math.js +++ b/src/client/scripts/esm/util/math.js @@ -472,6 +472,17 @@ function roundUpToPowerOf2(num) { return Math.pow(2, Math.ceil(Math.log2(num))); } +/** + * + * @param {Number} s + * @param {Number} e + * @param {Number} progress + */ +function moveTowards(s, e, progress) { + const maxProg = s + Math.sign(e - s) * min(abs(e - s), progress); + +} + export default { isPowerOfTwo, isAproxEqual, @@ -503,4 +514,5 @@ export default { GCD, LCM, roundUpToPowerOf2, + moveTowards, }; \ No newline at end of file From 980a38de817ea04f48147f722014dd2383b927c5 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:13:43 +0000 Subject: [PATCH 05/44] change gui to utilise movesequence --- package-lock.json | 27 +++--- .../scripts/esm/chess/logic/boardchanges.ts | 85 ++++++++++++------ .../scripts/esm/chess/logic/checkdetection.js | 10 +-- src/client/scripts/esm/chess/logic/clock.js | 8 +- .../scripts/esm/chess/logic/gamefile.js | 3 +- .../scripts/esm/chess/logic/movepiece.js | 15 +--- src/client/scripts/esm/chess/util/moveutil.js | 9 ++ src/client/scripts/esm/game/chess/game.js | 2 +- .../esm/game/chess/graphicalchanges.ts | 75 ++++++++++++++++ .../scripts/esm/game/chess/movesequence.ts | 86 +++---------------- .../scripts/esm/game/chess/selection.js | 5 +- .../scripts/esm/game/gui/guinavigation.js | 10 +-- .../scripts/esm/game/misc/onlinegame.js | 26 +++--- 13 files changed, 205 insertions(+), 156 deletions(-) create mode 100644 src/client/scripts/esm/game/chess/graphicalchanges.ts diff --git a/package-lock.json b/package-lock.json index 3b6ca56ae..42db4a8fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3574,9 +3574,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -3598,7 +3598,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -3613,6 +3613,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { @@ -5677,9 +5681,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/path-type": { @@ -6428,12 +6432,15 @@ } }, "node_modules/smol-toml": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.2.2.tgz", - "integrity": "sha512-fVEjX2ybKdJKzFL46VshQbj9PuA4IUKivalgp48/3zwS9vXzyykzQ6AX92UxHSvWJagziMRLeHMgEzoGO7A8hQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz", + "integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" } }, "node_modules/source-map": { diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index ed65dc6c6..9aaed2296 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -9,17 +9,38 @@ import type { Move } from "../util/moveutil.js"; interface Piece { type: string // - The type of the piece (e.g. `queensW`). coords: Coords // - The coordinates of the piece: `[x,y]` - index: Number // - The index of the piece within the gamefile's piece list. + index: number // - The index of the piece within the gamefile's piece list. } interface Change { action: string, +} + +interface PieceChange extends Change { + piece: Piece +} + +interface MoveChange extends PieceChange { + endCoords: Coords +} + +interface CaptureChange extends MoveChange { + capturedPiece: Piece +} - [data: string]: any +interface RightsChange extends Change { + coords: string + curRights: any + rights: any +} + +interface EnpassantChange extends Change { + curPassant: Coords | undefined + newPassant: Coords | undefined } interface ActionList { - [actionName: string]: (gamefile: gamefile, change: Change) => void + [actionName: string]: (gamefile: gamefile, change: any) => void } interface ChangeApplication { @@ -48,31 +69,37 @@ const changeFuncs: ChangeApplication = { } }; -function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { +function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}) // Need to differentiate this from move so animations can work + return changes; } -function queueAddPiece(changes: Array, piece: Piece) { +function queueAddPiece(changes: Array, piece: Piece) { changes.push({action: 'add', piece: piece}); + return changes; }; -function queueDeletePiece(changes: Array, piece: Piece) { +function queueDeletePiece(changes: Array, piece: Piece) { changes.push({action: 'delete', piece: piece}); + return changes; } -function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { +function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); + return changes; } -function queueSetSpecialRights(changes, coords, curRights, rights) { +function queueSetSpecialRights(changes: Array, coords: string, curRights: any, rights: any) { changes.push({action: "setRights", coords: coords, curRights: curRights, rights: rights}); + return changes; } -function queueSetEnPassant(changes, curPassant, newPassant) { +function queueSetEnPassant(changes: Array, curPassant: any, newPassant: any) { changes.push({action: "setPassant", curPassant: curPassant, newPassant: newPassant}); + return changes; } -function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { +function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { for (const c of changes) { if (c.action in funcs) { funcs[c.action](gamefile, c); @@ -91,7 +118,7 @@ function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, * @param gamefile - The gamefile * @param change - the data of the piece to be added */ -function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional +function addPiece(gamefile: gamefile, change: PieceChange) { // desiredIndex optional const piece = change.piece; const list = gamefile.ourPieces[piece.type]; @@ -116,7 +143,7 @@ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -function deletePiece(gamefile, change) { // piece: { type, index } +function deletePiece(gamefile: gamefile, change: PieceChange) { // piece: { type, index } const piece = change.piece; const list = gamefile.ourPieces[piece.type]; @@ -126,7 +153,7 @@ function deletePiece(gamefile, change) { // piece: { type, index } organizedlines.removeOrganizedPiece(gamefile, piece.coords); } -function movePiece(gamefile, change) { +function movePiece(gamefile: gamefile, change: MoveChange) { const piece = change.piece; const endCoords = change.endCoords; @@ -140,7 +167,7 @@ function movePiece(gamefile, change) { organizedlines.organizePiece(piece.type, endCoords, gamefile); } -function returnPiece(gamefile, change) { +function returnPiece(gamefile: gamefile, change: MoveChange) { const piece = change.piece; const endCoords = change.endCoords; @@ -154,17 +181,17 @@ function returnPiece(gamefile, change) { organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -function capturePiece(gamefile: gamefile, change: Change) { +function capturePiece(gamefile: gamefile, change: CaptureChange) { deletePiece(gamefile, {piece: change.capturedPiece, action: ""}) movePiece(gamefile, change) } -function uncapturePiece(gamefile: gamefile, change: Change) { +function uncapturePiece(gamefile: gamefile, change: CaptureChange) { returnPiece(gamefile, change) addPiece(gamefile, {piece: change.capturedPiece, action:""}) } -function setRights(gamefile, change) { +function setRights(gamefile: gamefile, change: RightsChange) { if (change.rights === undefined) { delete gamefile.specialRights[change.coords]; } else { @@ -172,7 +199,7 @@ function setRights(gamefile, change) { } } -function revertRights(gamefile, change) { +function revertRights(gamefile: gamefile, change: RightsChange) { if (change.curRights === undefined) { delete gamefile.specialRights[change.coords]; } else { @@ -180,23 +207,27 @@ function revertRights(gamefile, change) { } } -/** - * - * @param {gamefile} gamefile - * @param {*} change - */ -function setPassant(gamefile, change) { +function setPassant(gamefile: gamefile, change: EnpassantChange) { gamefile.enpassant = change.newPassant; } -function revertPassant(gamefile, change) { +function revertPassant(gamefile: gamefile, change: EnpassantChange) { if (change.curPassant === undefined) { delete gamefile.enpassant; } else { gamefile.enpassant = change.curPassant; } } -export type { ChangeApplication, Change } + +export type { + ChangeApplication, + Change, + PieceChange, + MoveChange, + CaptureChange, + RightsChange, + EnpassantChange, +} export default { queueAddPiece, @@ -207,4 +238,6 @@ export default { queueSetEnPassant, runMove, + applyChanges, + changeFuncs, }; \ No newline at end of file diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index 69883bfe6..d95971619 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -10,6 +10,7 @@ import colorutil from '../util/colorutil.js'; import jsutil from '../../util/jsutil.js'; import coordutil from '../util/coordutil.js'; import boardchanges from './boardchanges.js'; +import moveutil from '../util/moveutil.js'; // Import End /** @@ -255,7 +256,7 @@ function removeIndividualMovesThatPutYouInCheck(gamefile, individualMoves, piece // Simulates the move, tests for check, undos the move. Color is the color of the piece we're moving function doesMovePutInCheck(gamefile, pieceSelected, destCoords, color) { // pieceSelected: { type, index, coords } /** @type {Move} */ - const move = { type: pieceSelected.type, startCoords: jsutil.deepCopyObject(pieceSelected.coords), endCoords: movepiece.stripSpecialMoveTagsFromCoords(destCoords) }; + const move = { type: pieceSelected.type, startCoords: jsutil.deepCopyObject(pieceSelected.coords), endCoords: moveutil.stripSpecialMoveTagsFromCoords(destCoords) }; specialdetect.transferSpecialFlags_FromCoordsToMove(destCoords, move); return movepiece.simulateMove(gamefile, move, color).isCheck; } @@ -374,8 +375,8 @@ function removeSlidingMovesThatOpenDiscovered(gamefile, moves, kingCoords, piece if (sameLines.length === 0) return; // Delete the piece, and add it back when we're done! - const deletedPiece = jsutil.deepCopyObject(pieceSelected); - movepiece.deletePiece(gamefile, pieceSelected, { updateData: false }); + let deleteChange = boardchanges.queueDeletePiece([], pieceSelected); + boardchanges.applyChanges(gamefile, deleteChange, boardchanges.changeFuncs.forward) // let checklines = []; // For Idon's code below // For every line direction we share with the king... @@ -393,7 +394,6 @@ function removeSlidingMovesThatOpenDiscovered(gamefile, moves, kingCoords, piece if (coordutil.areCoordsEqual(direction1, direction2NumbArray)) continue; // Same line, it's okay to keep because it wouldn't open a discovered delete moves.sliding[direction2]; // Not same line, delete it because it would open a discovered. } - } // Idon us's code that handles the situation where moving off a line could expose multiple checks @@ -451,7 +451,7 @@ function removeSlidingMovesThatOpenDiscovered(gamefile, moves, kingCoords, piece // } // Add the piece back with the EXACT SAME index it had before!! - movepiece.addPiece(gamefile, deletedPiece.type, deletedPiece.coords, deletedPiece.index, { updateData: false }); + boardchanges.applyChanges(gamefile, deleteChange, boardchanges.changeFuncs.backward) } // Appends moves to moves.individual if the selected pieces is able to get between squares 1 & 2 diff --git a/src/client/scripts/esm/chess/logic/clock.js b/src/client/scripts/esm/chess/logic/clock.js index 2572470f0..cbe153337 100644 --- a/src/client/scripts/esm/chess/logic/clock.js +++ b/src/client/scripts/esm/chess/logic/clock.js @@ -1,12 +1,10 @@ // Import Start -import onlinegame from '../../game/misc/onlinegame.js'; import moveutil from '../util/moveutil.js'; import clockutil from '../util/clockutil.js'; import timeutil from '../../util/timeutil.js'; import gamefileutility from '../util/gamefileutility.js'; import pingManager from '../../util/pingManager.js'; -import options from '../../game/rendering/options.js'; // Import End /** @@ -60,7 +58,7 @@ function set(gamefile, currentTimes) { * @param {number} clockValues.timerBlack - Black's current time, in milliseconds. * @param {number} clockValues.accountForPing - True if it's an online game */ -function edit(gamefile, clockValues) { +function edit(gamefile, clockValues, debug = false) { if (!clockValues) return; // Likely a no-timed game const { timerWhite, timerBlack } = clockValues; const clocks = gamefile.clocks; @@ -77,7 +75,7 @@ function edit(gamefile, clockValues) { const halfPing = pingManager.getHalfPing(); clocks.currentTime[gamefile.whosTurn] -= halfPing; if (halfPing > 2500) console.error("Ping is above 5000 milliseconds!!! This is a lot to adjust the clock values!"); - if (options.isDebugModeOn()) console.log(`Ping is ${halfPing * 2}. Subtracted ${halfPing} millis from ${gamefile.whosTurn}'s clock.`); + if (debug) console.log(`Ping is ${halfPing * 2}. Subtracted ${halfPing} millis from ${gamefile.whosTurn}'s clock.`); } clocks.timeRemainAtTurnStart = clocks.colorTicking === 'white' ? clocks.currentTime.white : clocks.currentTime.black; } @@ -88,7 +86,6 @@ function edit(gamefile, clockValues) { */ function push(gamefile) { const clocks = gamefile.clocks; - if (onlinegame.areInOnlineGame()) return; // Only the server can push clocks if (clocks.untimed) return; if (!moveutil.isGameResignable(gamefile)) return; // Don't push unless atleast 2 moves have been played @@ -125,7 +122,6 @@ function update(gamefile) { clocks.currentTime[clocks.colorTicking] = Math.ceil(clocks.timeRemainAtTurnStart - timePassedSinceTurnStart); // Has either clock run out of time? - if (onlinegame.areInOnlineGame()) return; // Don't conclude game by time if in an online game, only the server does that. // TODO: update when lose conditions are added if (clocks.currentTime.white <= 0) { clocks.currentTime.white = 0; diff --git a/src/client/scripts/esm/chess/logic/gamefile.js b/src/client/scripts/esm/chess/logic/gamefile.js index 005fd73c8..c4c32250b 100644 --- a/src/client/scripts/esm/chess/logic/gamefile.js +++ b/src/client/scripts/esm/chess/logic/gamefile.js @@ -8,9 +8,9 @@ import area from '../../game/rendering/area.js'; import initvariant from './initvariant.js'; import jsutil from '../../util/jsutil.js'; import clock from './clock.js'; +import movesequence from '../../game/chess/movesequence.js'; import wincondition from './wincondition.js'; import gamerules from '../variants/gamerules.js'; - // Type Definitions... /** @typedef {import('../../util/math.js').BoundingBox} BoundingBox */ @@ -259,6 +259,7 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion, clockV organizedlines.initOrganizedPieceLists(this, { appendUndefineds: false }); // THIS HAS TO BE AFTER gamerules.swapCheckmateForRoyalCapture() AS THIS DOES GAME-OVER CHECKS!!! movepiece.makeAllMovesInGame(this, moves); + movesequence.animateMoveAtIdx(gamefile) // Start animation for last move /** The game's conclusion, if it is over. For example, `'white checkmate'` * Server's gameConclusion should overwrite preexisting gameConclusion. */ if (gameConclusion) this.gameConclusion = gameConclusion; diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 5383e1d76..d8b3f60bd 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -149,10 +149,10 @@ function incrementMoveRule(gamefile, typeMoved, wasACapture) { * @param {Object} options - An object that may contain the options (all are default *true*): * - `pushClock`: Whether to push the clock. */ -function updateTurnData(gamefile, { pushClock = true } = {}) { +function updateTurn(gamefile, { pushClock = true } = {}) { gamefile.whosTurn = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); if (pushClock) { - clock.push(gamefile); + return clock.push(gamefile); }; } @@ -373,22 +373,13 @@ function simulateMove(gamefile, move, colorToTestInCheck, { doGameOverChecks = f return info; // Info from simulating the move: { isCheck, gameConclusion } } -/** - * Strips the coordinates of their special move properties. - * For example, unstripped coords may look like: `[2,7,enpassant:true]` - * @param {number[]} coords - The coordinates - * @returns {number[]} The stripped coordinates: `[2,7]` - */ -function stripSpecialMoveTagsFromCoords(coords) { return [coords[0], coords[1]]; } - export default { makeMove, - nextTurn, + updateTurn, makeAllMovesInGame, calculateMoveFromShortmove, forwardToFront, rewindGameToIndex, rewindMove, simulateMove, - stripSpecialMoveTagsFromCoords }; \ No newline at end of file diff --git a/src/client/scripts/esm/chess/util/moveutil.js b/src/client/scripts/esm/chess/util/moveutil.js index fbe889577..61f2f7101 100644 --- a/src/client/scripts/esm/chess/util/moveutil.js +++ b/src/client/scripts/esm/chess/util/moveutil.js @@ -316,6 +316,14 @@ function doesAnyPlayerGet2TurnsInARow(gamefile) { return false; } +/** + * Strips the coordinates of their special move properties. + * For example, unstripped coords may look like: `[2,7,enpassant:true]` + * @param {number[]} coords - The coordinates + * @returns {number[]} The stripped coordinates: `[2,7]` + */ +function stripSpecialMoveTagsFromCoords(coords) { return [coords[0], coords[1]]; } + // Type export DO NOT USE export { Move }; @@ -341,4 +349,5 @@ export default { getWhosTurnAtMoveIndex, doesAnyPlayerGet2TurnsInARow, getMoveOneForward, + stripSpecialMoveTagsFromCoords, }; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/game.js b/src/client/scripts/esm/game/chess/game.js index 401927b36..3a957f121 100644 --- a/src/client/scripts/esm/game/chess/game.js +++ b/src/client/scripts/esm/game/chess/game.js @@ -146,7 +146,7 @@ function updateBoard() { } const timeWinner = clock.update(gamefile); - if (timeWinner) { // undefined if no clock has ran out + if (timeWinner && !onlinegame.areInOnlineGame()) { // undefined if no clock has ran out gamefile.gameConclusion = `${timeWinner} time`; concludeGame(); } diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts new file mode 100644 index 000000000..9d58b25b4 --- /dev/null +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -0,0 +1,75 @@ + +import animation from "../rendering/animation"; +import piecesmodel from "../rendering/piecesmodel"; +import organizedlines from "../../chess/logic/organizedlines"; +import coordutil from "../../chess/util/coordutil"; + +// @ts-ignore +import type { ChangeApplication, PieceChange, MoveChange, CaptureChange } from "../../chess/logic/boardchanges"; +// @ts-ignore +import type gamefile from "../../chess/logic/gamefile"; + +const animatableChanges: ChangeApplication = { + forward: { + "movePiece": animateMove, + "capturedPiece": animateCapture, + }, + + backward: { + "movePiece": animateReturn, + "capturePiece": animateReturn, + } +}; + +function animateMove(gamefile: gamefile, change: MoveChange) { + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords); +} + +function animateReturn(gamefile: gamefile, change: MoveChange) { + animation.animatePiece(change.piece.type, change.endCoords, change.piece.coords); +} + +function animateCapture(gamefile: gamefile, change: CaptureChange) { + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, coordutil.getKeyFromCoords(change.capturedPiece.coords)); +} + +const meshChanges: ChangeApplication = { + forward: { + "add": addMeshPiece, + "delete": deleteMeshPiece, + "movePiece": moveMeshPiece, + "capturePiece": moveMeshPiece, + }, + + backward: { + "delete": addMeshPiece, + "add": deleteMeshPiece, + "movePiece": returnMeshPiece, + "capturePiece": returnMeshPiece, + } +}; + +function addMeshPiece(gamefile: gamefile, change: PieceChange) { + piecesmodel.overwritebufferdata(gamefile, change.piece, change.piece.coords, change.piece.type); + + // Do we need to add more undefineds? + // Only adding pieces can ever reduce the number of undefineds we have, so we do that here! + if (organizedlines.areWeShortOnUndefineds(gamefile)) organizedlines.addMoreUndefineds(gamefile, { log: true }); +} + +function deleteMeshPiece(gamefile: gamefile, change: PieceChange) { + piecesmodel.deletebufferdata(gamefile, change.piece); +} + +function moveMeshPiece(gamefile: gamefile, change: MoveChange) { + piecesmodel.movebufferdata(gamefile, change.piece, change.endCoords); +} + +function returnMeshPiece(gamefile: gamefile, change: MoveChange) { + piecesmodel.movebufferdata(gamefile, change.piece, change.piece.coords); +} + +export { + animatableChanges, + meshChanges, +} \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 5809e70b3..23da459c2 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -10,28 +10,23 @@ import guigameinfo from "../gui/guigameinfo"; import movepiece from "../../chess/logic/movepiece"; import boardchanges from "../../chess/logic/boardchanges"; +import { animatableChanges, meshChanges } from "./graphicalchanges"; -import animation from "../rendering/animation"; -import piecesmodel from "../rendering/piecesmodel"; -import organizedlines from "../../chess/logic/organizedlines"; - -import type { ChangeApplication, Change } from "../../chess/logic/boardchanges"; +// @ts-ignore import type gamefile from "../../chess/logic/gamefile"; -function runMove(gamefile, { doGameOverChecks = true, updateData = true, concludeGameIfOver = true}) { +function makeMove(gamefile: gamefile, { doGameOverChecks = true, concludeGameIfOver = true}) { - movepiece.nextTurn(gamefile); + movepiece.updateTurn(gamefile, { pushClock: !onlinegame.areInOnlineGame() }); if (doGameOverChecks) { gamefileutility.doGameOverChecks(gamefile); if (concludeGameIfOver && gamefile.gameConclusion && !onlinegame.areInOnlineGame()) game.concludeGame(); } - if (updateData) { - guinavigation.update_MoveButtons(); - stats.setTextContentOfMoves(); // Making a move should change the move number in the stats - frametracker.onVisualChange(); - } + guinavigation.update_MoveButtons(); + stats.setTextContentOfMoves(); // Making a move should change the move number in the stats + frametracker.onVisualChange(); arrows.clearListOfHoveredPieces(); } @@ -66,64 +61,6 @@ function forwardToFront(gamefile, { animateLastMove = true } = {}) { guinavigation.lockRewind(); } -const animatableChanges: ChangeApplication = { - forward: { - "movePiece": animateMove, - "capturedPiece": animateCapture, - }, - - backward: { - "movePiece": animateReturn, - "capturePiece": animateReturn, - } -}; - -function animateMove(gamefile: gamefile, change: Change) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords); -} - -function animateReturn(gamefile: gamefile, change: Change) { - animation.animatePiece(change.piece.type, change.endCoords, change.piece.coords); -} - -function animateCapture(gamefile: gamefile, change: Change) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, change.capturedPiece); -} - -const meshChanges: ChangeApplication = { - forward: { - "add": addMeshPiece, - "delete": deleteMeshPiece, - "movePiece": moveMeshPiece, - }, - - backward: { - "delete": addMeshPiece, - "add": deleteMeshPiece, - "movePiece": returnMeshPiece, - } -}; - -function addMeshPiece(gamefile: gamefile, change) { - piecesmodel.overwritebufferdata(gamefile, change.piece, change.piece.coords, change.piecetype); - - // Do we need to add more undefineds? - // Only adding pieces can ever reduce the number of undefineds we have, so we do that here! - if (organizedlines.areWeShortOnUndefineds(gamefile)) organizedlines.addMoreUndefineds(gamefile, { log: true }); -} - -function deleteMeshPiece(gamefile: gamefile, change: Change) { - piecesmodel.deletebufferdata(gamefile, change.piece); -} - -function moveMeshPiece(gamefile: gamefile, change: Change) { - piecesmodel.movebufferdata(gamefile, change.piece, change.endCoords); -} - -function returnMeshPiece(gamefile: gamefile, change: Change) { - piecesmodel.movebufferdata(gamefile, change.piece, change.piece.coords); -} - function animateMoveAtIdx(gamefile: gamefile, moveIdx = gamefile.moveIndex, forward = true) { const move = gamefile.moves[moveIdx] if (move === undefined) return @@ -131,7 +68,12 @@ function animateMoveAtIdx(gamefile: gamefile, moveIdx = gamefile.moveIndex, forw } export default { - runMove, - forwardToFront, + makeMove, + rewindMove, + + viewForward, + viewBackward, + viewFront, + viewIdx, animateMoveAtIdx, }; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index f57a47bf9..729acf5c0 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -4,7 +4,6 @@ import guipause from '../gui/guipause.js'; import legalmoves from '../../chess/logic/legalmoves.js'; import input from '../input.js'; import onlinegame from '../misc/onlinegame.js'; -import movepiece from '../../chess/logic/movepiece.js'; import gamefileutility from '../../chess/util/gamefileutility.js'; import game from './game.js'; import specialdetect from '../../chess/logic/specialdetect.js'; @@ -206,7 +205,7 @@ function handleSelectingPiece(pieceClickedType) { // ^^ The extra conditions needed here so in edit mode and you click on an opponent piece // it will still forward you to front! - return movesequence.forwardToFront(gamefile, { flipTurn: false, updateProperties: false }); + return movesequence.viewFront(gamefile, { flipTurn: false, updateProperties: false }); } if (hoverSquareLegal) return; // Don't select different piece if the move is legal (its a capture) @@ -283,7 +282,7 @@ function unselectPiece() { * @param {number[]} coords - The destination coordinates`[x,y]`. MUST contain any special move flags. */ function moveGamefilePiece(coords) { - const strippedCoords = movepiece.stripSpecialMoveTagsFromCoords(coords); + const strippedCoords = moveutil.stripSpecialMoveTagsFromCoords(coords); /** @type {Move} */ const move = { type: pieceSelected.type, startCoords: pieceSelected.coords, endCoords: strippedCoords }; specialdetect.transferSpecialFlags_FromCoordsToMove(coords, move); diff --git a/src/client/scripts/esm/game/gui/guinavigation.js b/src/client/scripts/esm/game/gui/guinavigation.js index c999358f0..d2a8662dc 100644 --- a/src/client/scripts/esm/game/gui/guinavigation.js +++ b/src/client/scripts/esm/game/gui/guinavigation.js @@ -12,10 +12,8 @@ import transition from '../rendering/transition.js'; import gamefileutility from '../../chess/util/gamefileutility.js'; import statustext from './statustext.js'; import stats from './stats.js'; -import movepiece from '../../chess/logic/movepiece.js'; import selection from '../chess/selection.js'; import frametracker from '../rendering/frametracker.js'; -import changes from '../../chess/logic/boardchanges.js'; import movesequence from '../chess/movesequence.js'; // Import End @@ -393,7 +391,7 @@ function rewindMove() { frametracker.onVisualChange(); - movepiece.rewindMove(game.getGamefile(), { removeMove: false }); + movesequence.viewBackward(game.getGamefile()); selection.unselectPiece(); @@ -409,11 +407,7 @@ function forwardMove() { if (gamefile.mesh.locked) return statustext.pleaseWaitForTask(); if (!moveutil.isIncrementingLegal(gamefile)) return stats.showMoves(); - const move = moveutil.getMoveOneForward(gamefile); - - gamefile.moveIndex++; - changes.applyChanges(gamefile, move.changes); - movesequence.animateChanges(gamefile, move.changes); + movesequence.viewForward(gamefile); // transition.teleportToLastMove() diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index 2222a6fd3..fa5300cc6 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -9,7 +9,6 @@ import guititle from '../gui/guititle.js'; import clock from '../../chess/logic/clock.js'; import guiclock from '../gui/guiclock.js'; import statustext from '../gui/statustext.js'; -import movepiece from '../../chess/logic/movepiece.js'; import game from '../chess/game.js'; import specialdetect from '../../chess/logic/specialdetect.js'; import selection from '../chess/selection.js'; @@ -28,6 +27,8 @@ import colorutil from '../../chess/util/colorutil.js'; import jsutil from '../../util/jsutil.js'; import config from '../config.js'; import pingManager from '../../util/pingManager.js'; +import movesequence from '../chess/movesequence.js'; +import options from '../rendering/options.js'; // Import End /** @@ -281,7 +282,7 @@ function onmessage(data) { // { sub, action, value, id } const message = data.value; // { clockValues: { timerWhite, timerBlack } } message.clockValues.accountForPing = true; // We are in an online game so we need to inform the clock script to account for ping const gamefile = game.getGamefile(); - clock.edit(gamefile, message.clockValues); // Edit the clocks + clock.edit(gamefile, message.clockValues, options.isDebugModeOn()); // Edit the clocks guiclock.edit(gamefile); break; } case "gameupdate": // When the game has ended by time/disconnect/resignation/aborted, OR we are resyncing to the game. @@ -461,7 +462,7 @@ function handleOpponentsMove(message) { // { move, gameConclusion, moveNumber, c if (moveIsLegal !== true) console.log(`Buddy made an illegal play: ${JSON.stringify(moveAndConclusion)}`); if (moveIsLegal !== true && !isPrivate) return reportOpponentsMove(moveIsLegal); // Allow illegal moves in private games - movepiece.forwardToFront(gamefile, { flipTurn: false, animateLastMove: false, updateProperties: false }); + movesequence.viewFront(gamefile); // Forward the move... @@ -472,13 +473,13 @@ function handleOpponentsMove(message) { // { move, gameConclusion, moveNumber, c move.type = piecemoved.type; specialdetect.transferSpecialFlags_FromCoordsToMove(endCoordsToAppendSpecial, move); - movepiece.makeMove(gamefile, move); + movesequence.makeMove(gamefile, move); selection.reselectPiece(); // Reselect the currently selected piece. Recalc its moves and recolor it if needed. // Edit the clocks if (message.clockValues !== undefined) message.clockValues.accountForPing = true; // Set this to true so our clock knows to account for ping. - clock.edit(gamefile, message.clockValues); + clock.edit(gamefile, message.clockValues, options.isDebugModeOn()); guiclock.edit(gamefile); // For online games, we do NOT EVER conclude the game, so do that here if our opponents move concluded the game @@ -566,7 +567,7 @@ function handleServerGameUpdate(messageContents) { // { gameConclusion, clockVal gamefile.gameConclusion = claimedGameConclusion; // When the game has ended by time/disconnect/resignation/aborted - clock.edit(gamefile, messageContents.clockValues); + clock.edit(gamefile, messageContents.clockValues, options.isDebugModeOn()); if (gamefileutility.isGameOver(gamefile)) { game.concludeGame(); @@ -597,11 +598,11 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { } const originalMoveIndex = gamefile.moveIndex; - movepiece.forwardToFront(gamefile, { flipTurn: false, animateLastMove: false, updateProperties: false }); + movesequence.viewFront(gamefile); let aChangeWasMade = false; while (gamefile.moves.length > moves.length) { // While we have more moves than what the server does.. - movepiece.rewindMove(gamefile, { animate: false }); + movesequence.rewindMove(gamefile, { animate: false }); console.log("Rewound one move while resyncing to online game."); aChangeWasMade = true; } @@ -613,7 +614,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { if (thisGamefileMove) { // The move is defined if (thisGamefileMove.compact === moves[i]) break; // The moves MATCH // The moves don't match... remove this one off our list. - movepiece.rewindMove(gamefile, { animate: false }); + movesequence.rewindMove(gamefile, { animate: false }); console.log("Rewound one INCORRECT move while resyncing to online game."); aChangeWasMade = true; } @@ -626,7 +627,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { while (i < moves.length - 1) { // Increment i, adding the server's correct moves to our moves list i++; const thisShortmove = moves[i]; // '1,2>3,4Q' The shortmove from the server's move list to add - const move = movepiece.calculateMoveFromShortmove(gamefile, thisShortmove); + const move = moveutil.calculateMoveFromShortmove(gamefile, thisShortmove); const colorThatPlayedThisMove = moveutil.getColorThatPlayedMoveIndex(gamefile, i); const opponentPlayedThisMove = colorThatPlayedThisMove === opponentColor; @@ -648,12 +649,13 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { } else cancelFlashTabTimer(); const isLastMove = i === moves.length - 1; - movepiece.makeMove(gamefile, move, { doGameOverChecks: isLastMove, concludeGameIfOver: false, animate: isLastMove }); + movesequence.makeMove(gamefile, move, { doGameOverChecks: isLastMove, concludeGameIfOver: false}); + if (isLastMove) movesequence.animateMoveAtIdx(gamefile) console.log("Forwarded one move while resyncing to online game."); aChangeWasMade = true; } - if (!aChangeWasMade) movepiece.rewindGameToIndex(gamefile, originalMoveIndex, { removeMove: false }); + if (!aChangeWasMade) movesequence.viewIndex(gamefile, originalMoveIndex); else selection.reselectPiece(); // Reselect the selected piece from before we resynced. Recalc its moves and recolor it if needed. return true; // No cheating detected From 55a465d79f05ba07ebb49bbabe6c119c9af9e9d7 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:20:54 +0000 Subject: [PATCH 06/44] Move interaction --- package-lock.json | 7 +- .../scripts/esm/chess/logic/boardchanges.ts | 43 ++++--- .../scripts/esm/chess/logic/gamefile.js | 2 - .../scripts/esm/chess/logic/initvariant.js | 2 - .../scripts/esm/chess/logic/legalmoves.js | 9 +- .../scripts/esm/chess/logic/movepiece.js | 89 ++++++--------- .../scripts/esm/chess/logic/specialmove.js | 30 ++--- src/client/scripts/esm/chess/util/moveutil.js | 4 +- src/client/scripts/esm/game/chess/game.js | 9 +- .../esm/game/chess/graphicalchanges.ts | 53 ++++++--- .../scripts/esm/game/chess/movesequence.ts | 107 ++++++++++-------- .../scripts/esm/game/chess/selection.js | 3 +- .../scripts/esm/game/gui/guinavigation.js | 17 ++- .../scripts/esm/game/misc/onlinegame.js | 11 +- src/client/scripts/esm/util/math.js | 3 +- 15 files changed, 208 insertions(+), 181 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42db4a8fe..f75be4cbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5276,9 +5276,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -5286,6 +5286,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 9aaed2296..da416e488 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -1,9 +1,15 @@ +// @ts-ignore import organizedlines from "./organizedlines.js"; +// @ts-ignore import gamefileutility from "../util/gamefileutility.js"; +// @ts-ignore import jsutil from "../../util/jsutil.js"; +// @ts-ignore import type { gamefile } from "./gamefile.js"; +// @ts-ignore import type { Coords } from "./movesets.js"; +// @ts-ignore import type { Move } from "../util/moveutil.js"; interface Piece { @@ -13,7 +19,8 @@ interface Piece { } interface Change { - action: string, + action: string + [changeData: string]: any } interface PieceChange extends Change { @@ -39,14 +46,14 @@ interface EnpassantChange extends Change { newPassant: Coords | undefined } -interface ActionList { - [actionName: string]: (gamefile: gamefile, change: any) => void +interface ActionList { + [actionName: string]: T } interface ChangeApplication { - forward: ActionList + forward: ActionList<(gamefile: gamefile, change: any) => void> - backward: ActionList + backward: ActionList<(gamefile: gamefile, change: any) => void> } const changeFuncs: ChangeApplication = { @@ -70,7 +77,7 @@ const changeFuncs: ChangeApplication = { }; function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { - changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}) // Need to differentiate this from move so animations can work + changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); // Need to differentiate this from move so animations can work return changes; } @@ -99,17 +106,18 @@ function queueSetEnPassant(changes: Array, curPassant: any, return changes; } -function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { +function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList<(gamefile: gamefile, change: any) => void>) { for (const c of changes) { - if (c.action in funcs) { - funcs[c.action](gamefile, c); - } + if (typeof c.action !== "string") continue; + if (!(c.action in funcs)) continue; + // @ts-ignore + funcs[c.action](gamefile, c); } } function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { - let funcs = forward ? changeFuncs.forward : changeFuncs.backward - applyChanges(gamefile, move.changes, funcs) + const funcs = forward ? changeFuncs.forward : changeFuncs.backward; + applyChanges(gamefile, move.changes, funcs); } /** @@ -182,13 +190,13 @@ function returnPiece(gamefile: gamefile, change: MoveChange) { } function capturePiece(gamefile: gamefile, change: CaptureChange) { - deletePiece(gamefile, {piece: change.capturedPiece, action: ""}) - movePiece(gamefile, change) + deletePiece(gamefile, {piece: change.capturedPiece, action: ""}); + movePiece(gamefile, change); } function uncapturePiece(gamefile: gamefile, change: CaptureChange) { - returnPiece(gamefile, change) - addPiece(gamefile, {piece: change.capturedPiece, action:""}) + returnPiece(gamefile, change); + addPiece(gamefile, {piece: change.capturedPiece, action:""}); } function setRights(gamefile: gamefile, change: RightsChange) { @@ -220,6 +228,7 @@ function revertPassant(gamefile: gamefile, change: EnpassantChange) { } export type { + ActionList, ChangeApplication, Change, PieceChange, @@ -227,7 +236,7 @@ export type { CaptureChange, RightsChange, EnpassantChange, -} +}; export default { queueAddPiece, diff --git a/src/client/scripts/esm/chess/logic/gamefile.js b/src/client/scripts/esm/chess/logic/gamefile.js index c4c32250b..7e586f9c7 100644 --- a/src/client/scripts/esm/chess/logic/gamefile.js +++ b/src/client/scripts/esm/chess/logic/gamefile.js @@ -8,7 +8,6 @@ import area from '../../game/rendering/area.js'; import initvariant from './initvariant.js'; import jsutil from '../../util/jsutil.js'; import clock from './clock.js'; -import movesequence from '../../game/chess/movesequence.js'; import wincondition from './wincondition.js'; import gamerules from '../variants/gamerules.js'; // Type Definitions... @@ -259,7 +258,6 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion, clockV organizedlines.initOrganizedPieceLists(this, { appendUndefineds: false }); // THIS HAS TO BE AFTER gamerules.swapCheckmateForRoyalCapture() AS THIS DOES GAME-OVER CHECKS!!! movepiece.makeAllMovesInGame(this, moves); - movesequence.animateMoveAtIdx(gamefile) // Start animation for last move /** The game's conclusion, if it is over. For example, `'white checkmate'` * Server's gameConclusion should overwrite preexisting gameConclusion. */ if (gameConclusion) this.gameConclusion = gameConclusion; diff --git a/src/client/scripts/esm/chess/logic/initvariant.js b/src/client/scripts/esm/chess/logic/initvariant.js index 4b1ef751d..df10e4505 100644 --- a/src/client/scripts/esm/chess/logic/initvariant.js +++ b/src/client/scripts/esm/chess/logic/initvariant.js @@ -1,6 +1,5 @@ // Import Start -import specialundo from './specialundo.js'; import legalmoves from './legalmoves.js'; import formatconverter from './formatconverter.js'; import specialdetect from './specialdetect.js'; @@ -106,7 +105,6 @@ function initPieceMovesets(gamefile, { Variant, UTCDate, UTCTime }) { gamefile.pieceMovesets = variant.getMovesetsOfVariant({ Variant, UTCDate, UTCTime }); gamefile.specialDetects = specialdetect.getSpecialMoves(); gamefile.specialMoves = specialmove.getFunctions(); - gamefile.specialUndos = specialundo.getFunctions(); gamefile.vicinity = legalmoves.genVicinity(gamefile); } diff --git a/src/client/scripts/esm/chess/logic/legalmoves.js b/src/client/scripts/esm/chess/logic/legalmoves.js index 0b129e46e..eda415f51 100644 --- a/src/client/scripts/esm/chess/logic/legalmoves.js +++ b/src/client/scripts/esm/chess/logic/legalmoves.js @@ -311,7 +311,8 @@ function isOpponentsMoveLegal(gamefile, move, claimedGameConclusion) { const attackersB4Forwarding = jsutil.deepCopyObject(gamefile.attackers); const originalMoveIndex = gamefile.moveIndex; // Used to return to this move after we're done simulating - movepiece.forwardToFront(gamefile, { flipTurn: false, animateLastMove: false, updateData: false, updateProperties: false, simulated: true }); + movepiece.forEachMove(gamefile, gamefile.move.length - 1, (m) => movepiece.applyMove(gamefile, m, true)); + movepiece.updateTurn(gamefile, { pushClock: false }); // Make sure a piece exists on the start coords const piecemoved = gamefileutility.getPieceAtCoords(gamefile, moveCopy.startCoords); // { type, index, coords } @@ -378,13 +379,15 @@ function isOpponentsMoveLegal(gamefile, move, claimedGameConclusion) { // ... // Rewind the game back to the index we were originally on before simulating - movepiece.rewindGameToIndex(gamefile, originalMoveIndex, { removeMove: false, updateData: false }); + movepiece.forEachMove(gamefile, originalMoveIndex, (m) => movepiece.applyMove(gamefile, m, false)); + movepiece.updateTurn(gamefile, { pushClock: false }); return true; // By this point, nothing illegal! function rewindGameAndReturnReason(reasonIllegal) { // Rewind the game back to the index we were originally on - movepiece.rewindGameToIndex(gamefile, originalMoveIndex, { removeMove: false, updateData: false }); + movepiece.forEachMove(gamefile, originalMoveIndex, (m) => movepiece.applyMove(gamefile, m, false)); + movepiece.updateTurn(gamefile, { pushClock: false }); return reasonIllegal; } } diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index d8b3f60bd..48175dfd8 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -46,10 +46,14 @@ function generateMove(gamefile, move) { if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move); // Move piece regularly (no special tag) } -function makeMove(gamefile, move, { updateProperties, recordCheck }) { +function applyMove(gamefile, move, forward = true) { + boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); +} + +function makeMove(gamefile, move, { updateProperties = true } = {}) { const wasACapture = move.captured != null; - boardchanges.applyChanges(gamefile, move.changes); + applyMove(gamefile, move); gamefile.moveIndex++; gamefile.moves.push(move); @@ -60,7 +64,7 @@ function makeMove(gamefile, move, { updateProperties, recordCheck }) { if (updateProperties) incrementMoveRule(gamefile, move.type, wasACapture); // ALWAYS DO THIS NOW, no matter what. - updateInCheck(gamefile, recordCheck); + updateInCheck(gamefile); } /** @@ -96,9 +100,9 @@ function storeRewindInfoOnMove(gamefile, move, pieceIndex, { simulated = false } function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { boardchanges.queueSetEnPassant(move.changes, gamefile.enpassant, undefined); let key = coordutil.getKeyFromCoords(move.startCoords); - boardchanges.queueDeleteSpecialRights(move.changes, key, gamefile.specialRights[key]); + boardchanges.queueSetSpecialRights(move.changes, key, gamefile.specialRights[key], undefined); key = coordutil.getKeyFromCoords(move.endCoords); - boardchanges.queueDeleteSpecialRights(move.changes, key, gamefile.specialRights[key]); // We also delete the captured pieces specialRights for ANY move. + boardchanges.queueSetSpecialRights(move.changes, key, gamefile.specialRights[key], undefined); // We also delete the captured pieces specialRights for ANY move. } // RETURNS index of captured piece! Required for undo'ing moves. @@ -190,7 +194,9 @@ function updateInCheck(gamefile, flagMoveAsCheck = true) { */ function makeAllMovesInGame(gamefile, moves) { if (gamefile.moveIndex !== -1) throw new Error("Cannot make all moves in game when we're not at the beginning."); - + + gamefile.moves = []; + for (let i = 0; i < moves.length; i++) { const shortmove = moves[i]; @@ -201,12 +207,16 @@ function makeAllMovesInGame(gamefile, moves) { // Make the move in the game! - // const isLastMove = i === moves.length - 1; - // const animate = isLastMove; - makeMove(gamefile, move, { pushClock: false, updateData: false, concludeGameIfOver: false, doGameOverChecks: false, animate: false }); + applyMove(gamefile, move); + + gamefile.moveIndex++; + gamefile.moves.push(move); } - if (moves.length === 0) updateInCheck(gamefile, false); + // FIXME: REENABLE, TESTING + // if (moves.length === 0) updateInCheck(gamefile, false); + + gamefileutility.doGameOverChecks(gamefile); // Update the gameConclusion } /** @@ -251,7 +261,7 @@ function calculateMoveFromShortmove(gamefile, shortmove) { break; } - generateMove(gamefile, move) + generateMove(gamefile, move); return move; } @@ -262,53 +272,17 @@ function calculateMoveFromShortmove(gamefile, shortmove) { * @param {number} targetIndex */ function forEachMove(gamefile, targetIndex, callback) { - if (gamefile.moves.length >= targetIndex) return console.error("Cannot!"); //TODO: make this error useful + console.log(gamefile.moves.length, targetIndex); + if (gamefile.moves.length <= targetIndex) return console.error("Cannot!"); //TODO: make this error useful let i = gamefile.moveIndex; - while (true) { - i = math.moveTowards(i, targetIndex, 1) - const move = gamefile.moves[i] - - callback(move) + while (i !== targetIndex) { + i = math.moveTowards(i, targetIndex, 1); + const move = gamefile.moves[i]; - if (i === moveIndex) break; + callback(move); } - -} - -/** - * Fast-forwards the game to front, to the most-recently played move. - * @param {gamefile} gamefile - The gamefile - * @param {Object} options - An object containing various options (ALL of these are default *true*): - * - `flipTurn`: Whether each forwarded move should flip whosTurn. This should be false when forwarding to the game's front after rewinding. - * - `animateLastMove`: Whether to animate the last move, or most-recently played. - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false if we plan on regenerating the model manually after forwarding. - * - `updateProperties`: Whether each move should update gamefile properties that game-over algorithms rely on, such as the 50-move-rule's status, or 3-Check's check counter. - * - `simulated`: Whether you plan on undo'ing this forward, rewinding back to where you were. If true, the `rewindInfo` property will be added to each forwarded move in the gamefile for easy reverting when it comes time. - */ - -function forwardToFront(gamefile, { flipTurn = true, animateLastMove = true, updateData = true, updateProperties = true, simulated = false } = {}) { - if (updateData && gamefile.mesh.locked > 0) { // The mesh is locked (we cannot forward moves right now) - // Call this function again with the same arguments as soon as the mesh is unlocked - gamefile.mesh.callbacksOnUnlock.push(gamefile => forwardToFront(gamefile, { flipTurn, animateLastMove, updateData, updateProperties, simulated })); - return; - } - -} - -/** - * Rewinds the game until we reach the desired move index. - * @param {gamefile} gamefile - The gamefile - * @param {number} moveIndex - The desired move index - * @param {Object} options - An object containing various options (ALL of these are default *true*, EXCEPT `simulated` which is default *false*): - * - `removeMove`: Whether to delete the moves in the gamefile's moves list while rewinding. - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh after this. - */ -function rewindGameToIndex(gamefile, moveIndex, { removeMove = true } = {}) { - if (removeMove && !moveutil.areWeViewingLatestMove(gamefile)) return console.error("Cannot rewind game to index while deleting moves unless we start at the most recent move. forwardToFront() first."); - if (gamefile.moveIndex < moveIndex) return console.error("Cannot rewind game to index when we need to forward instead."); - while (gamefile.moveIndex > moveIndex) rewindMove(gamefile, removeMove); } /** @@ -325,7 +299,7 @@ function rewindMove(gamefile, { removeMove = true } = {} ) { const move = moveutil.getMoveFromIndex(gamefile.moves, gamefile.moveIndex); // { type, startCoords, endCoords, captured } - boardchanges.undoChanges(gamefile, move.changes); + boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, false); // inCheck and attackers are always restored, no matter if we're deleting the move or not. gamefile.inCheck = move.rewindInfo.inCheck; @@ -356,7 +330,7 @@ function rewindMove(gamefile, { removeMove = true } = {} ) { function simulateMove(gamefile, move, colorToTestInCheck, { doGameOverChecks = false } = {}) { // Moves the piece without unselecting it or regenerating the pieces model. generateMove(gamefile, move); - makeMove(gamefile, move, {recordCheck: true, updateProperties: doGameOverChecks}); + makeMove(gamefile, move, { updateProperties: doGameOverChecks }); // What info can we pull from the game after simulating this move? const info = { @@ -374,12 +348,13 @@ function simulateMove(gamefile, move, colorToTestInCheck, { doGameOverChecks = f } export default { + generateMove, makeMove, updateTurn, + forEachMove, makeAllMovesInGame, calculateMoveFromShortmove, - forwardToFront, - rewindGameToIndex, + applyMove, rewindMove, simulateMove, }; \ No newline at end of file diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index ac97a6b4e..1db690e53 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -37,9 +37,9 @@ function getFunctions() { // RETURNS FALSE if special move was not executed! function kings(gamefile, piece, move) { - const moveChanges = []; + const moveChanges = move.changes; const specialTag = move.castle; // { dir: -1/1, coord } - if (!specialTag) return moveChanges; // No special move to execute, return false to signify we didn't move the piece. + if (!specialTag) return false; // No special move to execute, return false to signify we didn't move the piece. // Move the king to new square @@ -51,17 +51,17 @@ function kings(gamefile, piece, move) { const landSquare = [move.endCoords[0] - specialTag.dir, move.endCoords[1]]; // Delete the rook's special move rights const key = coordutil.getKeyFromCoords(pieceToCastleWith.coords); - boardchanges.queueDeleteSpecialRights(moveChanges, pieceToCastleWith, gamefile.specialRights[key]); + boardchanges.queueSetSpecialRights(moveChanges, pieceToCastleWith, gamefile.specialRights[key], undefined); boardchanges.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move // Special move was executed! // There is no captured piece with castling - return moveChanges; + return true; } -function pawns(gamefile, piece, move, {updateProperties = true, simulated = false } = {}) { - const moveChanges = []; +function pawns(gamefile, piece, move, { updateProperties = true } = {}) { + const moveChanges = move.changes; // If it was a double push, then add the enpassant flag to the gamefile, and remove its special right! if (updateProperties && isPawnMoveADoublePush(piece.coords, move.endCoords)) { @@ -76,24 +76,28 @@ function pawns(gamefile, piece, move, {updateProperties = true, simulated = fals const capturedPiece = gamefileutility.getPieceAtCoords(gamefile, captureCoords); if (capturedPiece) move.captured = capturedPiece.type; - if (capturedPiece && simulated) move.rewindInfo.capturedIndex = capturedPiece.index; // Delete the piece captured - if (capturedPiece) boardchanges.queueDeleteChange(moveChanges, capturedPiece); if (promotionTag) { + if (capturedPiece) boardchanges.queueDeletePiece(moveChanges, capturedPiece); + // Delete original pawn - boardchanges.queueDeleteChange(moveChanges, piece); + boardchanges.queueDeletePiece(moveChanges, piece); - boardchanges.queueAddChange(moveChanges, promotionTag, move.endCoords, null); + boardchanges.queueAddPiece(moveChanges, promotionTag, move.endCoords, null); } else /* enpassantTag */ { - // Move the pawn - boardchanges.queueMoveChange(moveChanges, piece, move.endCoords); + if (capturedPiece) { + boardchanges.queueCaputure(moveChanges, piece, move.endCoords, capturedPiece); + } else { + // Move the pawn + boardchanges.queueMovePiece(moveChanges, piece, move.endCoords); + } } // Special move was executed! - return moveChanges; + return true; } function isPawnMoveADoublePush(pawnCoords, endCoords) { return Math.abs(pawnCoords[1] - endCoords[1]) === 2; } diff --git a/src/client/scripts/esm/chess/util/moveutil.js b/src/client/scripts/esm/chess/util/moveutil.js index 61f2f7101..7b32ea3fe 100644 --- a/src/client/scripts/esm/chess/util/moveutil.js +++ b/src/client/scripts/esm/chess/util/moveutil.js @@ -9,9 +9,9 @@ import coordutil from './coordutil.js'; /** * Type Definitions * @typedef {import('../logic/gamefile.js').gamefile} gamefile + * @typedef {import('../logic/boardchanges.js').Change} Change */ - "use strict"; // Custom type definitions... @@ -22,7 +22,7 @@ function Move() { /** The type of piece moved (e.g. `queensW`). */ this.type = undefined; - /** @type {Array} */ + /** @type {Array} */ this.changes = undefined; /** The start coordinates of the piece: `[x,y]` */ this.startCoords = undefined; diff --git a/src/client/scripts/esm/game/chess/game.js b/src/client/scripts/esm/game/chess/game.js index 3a957f121..63a32a1a9 100644 --- a/src/client/scripts/esm/game/chess/game.js +++ b/src/client/scripts/esm/game/chess/game.js @@ -36,8 +36,8 @@ import jsutil from '../../util/jsutil.js'; import winconutil from '../../chess/util/winconutil.js'; import sound from '../misc/sound.js'; import spritesheet from '../rendering/spritesheet.js'; +import movesequence from './movesequence.js'; import loadingscreen from '../gui/loadingscreen.js'; -import movepiece from '../../chess/logic/movepiece.js'; import frametracker from '../rendering/frametracker.js'; import area from '../rendering/area.js'; // Import End @@ -223,11 +223,12 @@ async function loadGamefile(newGamefile) { } guipromotion.initUI(gamefile.gameRules.promotionsAllowed); - // Rewind one move so that we can animate the very final move. - if (newGamefile.moveIndex > -1) movepiece.rewindMove(newGamefile, { updateData: false, removeMove: false, animate: false }); // A small delay to animate the very last move, so the loading screen // spinny pawn animation has time to fade away. - animateLastMoveTimeoutID = setTimeout(movepiece.forwardToFront, delayOfLatestMoveAnimationOnRejoinMillis, gamefile, { flipTurn: false, updateProperties: false }); + animateLastMoveTimeoutID = setTimeout(() => { + const move = gamefile.moves[gamefile.moveIndex]; + if (move !== undefined) movesequence.animateMove(gamefile, move, true); + }, delayOfLatestMoveAnimationOnRejoinMillis); // Disable miniimages and arrows if there's over 50K pieces. They render too slow. if (newGamefile.startSnapshot.pieceCount >= gamefileutility.pieceCountToDisableCheckmate) { diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index 9d58b25b4..e2b4d9257 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -1,15 +1,24 @@ - -import animation from "../rendering/animation"; -import piecesmodel from "../rendering/piecesmodel"; -import organizedlines from "../../chess/logic/organizedlines"; -import coordutil from "../../chess/util/coordutil"; +// @ts-ignore +import animation from "../rendering/animation.js"; +// @ts-ignore +import piecesmodel from "../rendering/piecesmodel.js"; +// @ts-ignore +import organizedlines from "../../chess/logic/organizedlines.js"; // @ts-ignore -import type { ChangeApplication, PieceChange, MoveChange, CaptureChange } from "../../chess/logic/boardchanges"; +import type { ChangeApplication, PieceChange, MoveChange, CaptureChange, ActionList } from "../../chess/logic/boardchanges.js"; // @ts-ignore -import type gamefile from "../../chess/logic/gamefile"; +import type gamefile from "../../chess/logic/gamefile.js"; + +// ESLint, THIS IS A TYPE INTERFACE SHUT UP +interface ChangeAnimations { + // eslint-disable-next-line no-unused-vars + forward: ActionList<(change: CaptureChange, clearanimations: boolean) => void> + // eslint-disable-next-line no-unused-vars + backward: ActionList<(change: MoveChange, clearanimations: boolean) => void> +} -const animatableChanges: ChangeApplication = { +const animatableChanges: ChangeAnimations = { forward: { "movePiece": animateMove, "capturedPiece": animateCapture, @@ -21,16 +30,16 @@ const animatableChanges: ChangeApplication = { } }; -function animateMove(gamefile: gamefile, change: MoveChange) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords); +function animateMove(change: MoveChange, clearanimations: boolean) { + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, undefined, clearanimations); } -function animateReturn(gamefile: gamefile, change: MoveChange) { - animation.animatePiece(change.piece.type, change.endCoords, change.piece.coords); +function animateReturn(change: MoveChange, clearanimations: boolean) { + animation.animatePiece(change.piece.type, change.endCoords, change.piece.coords, undefined, clearanimations); } -function animateCapture(gamefile: gamefile, change: CaptureChange) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, coordutil.getKeyFromCoords(change.capturedPiece.coords)); +function animateCapture(change: CaptureChange, clearanimations: boolean) { + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, change.capturedPiece.type, clearanimations); } const meshChanges: ChangeApplication = { @@ -38,14 +47,14 @@ const meshChanges: ChangeApplication = { "add": addMeshPiece, "delete": deleteMeshPiece, "movePiece": moveMeshPiece, - "capturePiece": moveMeshPiece, + "capturePiece": captureMeshPiece, }, backward: { "delete": addMeshPiece, "add": deleteMeshPiece, "movePiece": returnMeshPiece, - "capturePiece": returnMeshPiece, + "capturePiece": uncaptureMeshPiece, } }; @@ -69,7 +78,17 @@ function returnMeshPiece(gamefile: gamefile, change: MoveChange) { piecesmodel.movebufferdata(gamefile, change.piece, change.piece.coords); } +function captureMeshPiece(gamefile: gamefile, change: CaptureChange) { + piecesmodel.deletebufferdata(gamefile, change.capturedPiece); + moveMeshPiece(gamefile, change); +} + +function uncaptureMeshPiece(gamefile: gamefile, change: CaptureChange) { + returnMeshPiece(gamefile, change); + addMeshPiece(gamefile, {action: "addPiece", piece: change.capturedPiece}); +} + export { animatableChanges, meshChanges, -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 23da459c2..7e7c4abe9 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -1,23 +1,34 @@ -import gamefileutility from "../../chess/util/gamefileutility"; -import onlinegame from "../misc/onlinegame"; -import game from "./game"; -import arrows from "../rendering/arrows"; -import frametracker from "../rendering/frametracker"; -import stats from "../gui/stats"; -import guinavigation from "../gui/guinavigation"; -import moveutil from "../../chess/util/moveutil"; -import guigameinfo from "../gui/guigameinfo"; -import movepiece from "../../chess/logic/movepiece"; +// @ts-ignore +import gamefileutility from "../../chess/util/gamefileutility.js"; +// @ts-ignore +import onlinegame from "../misc/onlinegame.js"; +// @ts-ignore +import game from "./game.js"; +// @ts-ignore +import arrows from "../rendering/arrows.js"; +// @ts-ignore +import frametracker from "../rendering/frametracker.js"; +// @ts-ignore +import stats from "../gui/stats.js"; +// @ts-ignore +import guinavigation from "../gui/guinavigation.js"; +// @ts-ignore +import movepiece from "../../chess/logic/movepiece.js"; -import boardchanges from "../../chess/logic/boardchanges"; -import { animatableChanges, meshChanges } from "./graphicalchanges"; +import boardchanges from "../../chess/logic/boardchanges.js"; +import { animatableChanges, meshChanges } from "./graphicalchanges.js"; // @ts-ignore -import type gamefile from "../../chess/logic/gamefile"; +import type gamefile from "../../chess/logic/gamefile.js"; +// @ts-ignore +import type { Move } from "../../chess/util/moveutil.js"; -function makeMove(gamefile: gamefile, { doGameOverChecks = true, concludeGameIfOver = true}) { +function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, concludeGameIfOver = true} = {}) { + movepiece.generateMove(gamefile, move); + movepiece.makeMove(gamefile, move); movepiece.updateTurn(gamefile, { pushClock: !onlinegame.areInOnlineGame() }); + boardchanges.runMove(gamefile, move, meshChanges, true); if (doGameOverChecks) { gamefileutility.doGameOverChecks(gamefile); @@ -31,49 +42,49 @@ function makeMove(gamefile: gamefile, { doGameOverChecks = true, concludeGameIfO arrows.clearListOfHoveredPieces(); } -/** - * Fast-forwards the game to front, to the most-recently played move. - * @param {gamefile} gamefile - The gamefile - * @param {Object} options - An object containing various options (ALL of these are default *true*): - * - `flipTurn`: Whether each forwarded move should flip whosTurn. This should be false when forwarding to the game's front after rewinding. - * - `animateLastMove`: Whether to animate the last move, or most-recently played. - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false if we plan on regenerating the model manually after forwarding. - * - `updateProperties`: Whether each move should update gamefile properties that game-over algorithms rely on, such as the 50-move-rule's status, or 3-Check's check counter. - * - `simulated`: Whether you plan on undo'ing this forward, rewinding back to where you were. If true, the `rewindInfo` property will be added to each forwarded move in the gamefile for easy reverting when it comes time. - */ - -function forwardToFront(gamefile, { animateLastMove = true } = {}) { - - while (true) { // For as long as we have moves to forward... - const nextIndex = gamefile.moveIndex + 1; - if (moveutil.isIndexOutOfRange(gamefile.moves, nextIndex)) break; - - const nextMove = moveutil.getMoveFromIndex(gamefile.moves, nextIndex); - - const isLastMove = moveutil.isIndexTheLastMove(gamefile.moves, nextIndex); - const animate = animateLastMove && isLastMove; - makeMove(gamefile, nextMove, { recordMove: false, pushClock: false, doGameOverChecks: false, flipTurn: false, animate, updateData: true, updateProperties:false, simulated: false }); +function animateMove(gamefile: gamefile, move: Move, forward = true) { + const funcs = forward ? animatableChanges.forward : animatableChanges.backward; + let clearanimations = true; + for (const c of move.changes) { + if (c.action) continue; + if (!(c.action in funcs)) continue; + // @ts-ignore + funcs[c.action](c, clearanimations); + clearanimations = false; } +} - guigameinfo.updateWhosTurn(gamefile); +function rewindMove(gamefile: gamefile) { + boardchanges.runMove(gamefile, gamefile.moves[gamefile.moveIndex], meshChanges, false); + movepiece.rewindMove(gamefile); + guinavigation.update_MoveButtons(); + frametracker.onVisualChange(); +} - // lock the rewind/forward buttons for a brief moment. - guinavigation.lockRewind(); +function viewFront(gamefile: gamefile) { + movepiece.forEachMove(gamefile, gamefile.moves.length - 1, (m: Move) => viewMove(gamefile, m, true)); + gamefile.moveIndex = gamefile.moves.length - 1; + guinavigation.update_MoveButtons(); + stats.showMoves(); } -function animateMoveAtIdx(gamefile: gamefile, moveIdx = gamefile.moveIndex, forward = true) { - const move = gamefile.moves[moveIdx] - if (move === undefined) return - boardchanges.runMove(gamefile, move, animatableChanges, forward) +function viewMove(gamefile: gamefile, move: Move, forward = true) { + boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); + boardchanges.runMove(gamefile, move, meshChanges, forward); +} + +function viewIndex(gamefile: gamefile, index: number) { + movepiece.forEachMove(gamefile, index, (m: Move) => viewMove(gamefile, m, index >= gamefile.moveIndex)); + gamefile.moveIndex = index; + guinavigation.update_MoveButtons(); + stats.showMoves(); } export default { makeMove, rewindMove, - - viewForward, - viewBackward, + viewMove, viewFront, - viewIdx, - animateMoveAtIdx, + viewIndex, + animateMove, }; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index 729acf5c0..89cd9b033 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -205,7 +205,7 @@ function handleSelectingPiece(pieceClickedType) { // ^^ The extra conditions needed here so in edit mode and you click on an opponent piece // it will still forward you to front! - return movesequence.viewFront(gamefile, { flipTurn: false, updateProperties: false }); + return movesequence.viewFront(gamefile); } if (hoverSquareLegal) return; // Don't select different piece if the move is legal (its a capture) @@ -290,6 +290,7 @@ function moveGamefilePiece(coords) { move.compact = compact; movesequence.makeMove(game.getGamefile(), move); + movesequence.animateMove(game.getGamefile(), move, true); onlinegame.sendMove(); unselectPiece(); diff --git a/src/client/scripts/esm/game/gui/guinavigation.js b/src/client/scripts/esm/game/gui/guinavigation.js index d2a8662dc..9f50752c8 100644 --- a/src/client/scripts/esm/game/gui/guinavigation.js +++ b/src/client/scripts/esm/game/gui/guinavigation.js @@ -385,13 +385,18 @@ function testIfForwardMove() { } /** Rewinds the currently-loaded gamefile by 1 move. Unselects any piece, updates the rewind/forward move buttons. */ -function rewindMove() { - if (game.getGamefile().mesh.locked) return statustext.pleaseWaitForTask(); +function rewindMove() {4 + const gamefile = game.getGamefile() + + if (gamefile.mesh.locked) return statustext.pleaseWaitForTask(); if (!moveutil.isDecrementingLegal(game.getGamefile())) return stats.showMoves(); frametracker.onVisualChange(); - movesequence.viewBackward(game.getGamefile()); + const idx = gamefile.moveIndex; + gamefile.moveIndex--; + movesequence.viewMove(gamefile, gamefile.moves[idx], false); + movesequence.animateMove(gamefile, gamefile.moves[idx], false); selection.unselectPiece(); @@ -402,12 +407,14 @@ function rewindMove() { /** Forwards the currently-loaded gamefile by 1 move. Unselects any piece, updates the rewind/forward move buttons. */ function forwardMove() { - const gamefile = game.getGamefile() + const gamefile = game.getGamefile(); if (gamefile.mesh.locked) return statustext.pleaseWaitForTask(); if (!moveutil.isIncrementingLegal(gamefile)) return stats.showMoves(); - movesequence.viewForward(gamefile); + gamefile.moveIndex++; + movesequence.viewMove(gamefile, gamefile.moves[gamefile.moveIndex], true); + movesequence.animateMove(gamefile, gamefile.moves[gamefile.moveIndex], true); // transition.teleportToLastMove() diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index fa5300cc6..0a068f643 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -33,7 +33,7 @@ import options from '../rendering/options.js'; /** * Type Definitions - * @typedef {import('../../chess/logic/gamefile.js'} gamefile + * @typedef {import('../../chess/logic/gamefile.js').gamefile} gamefile * @typedef {import('../../chess/util/moveutil.js').Move} Move * @typedef {import('../websocket.js').WebsocketMessage} WebsocketMessage */ @@ -474,6 +474,7 @@ function handleOpponentsMove(message) { // { move, gameConclusion, moveNumber, c move.type = piecemoved.type; specialdetect.transferSpecialFlags_FromCoordsToMove(endCoordsToAppendSpecial, move); movesequence.makeMove(gamefile, move); + movesequence.animateMove(gamefile, move, true); selection.reselectPiece(); // Reselect the currently selected piece. Recalc its moves and recolor it if needed. @@ -602,7 +603,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { let aChangeWasMade = false; while (gamefile.moves.length > moves.length) { // While we have more moves than what the server does.. - movesequence.rewindMove(gamefile, { animate: false }); + movesequence.rewindMove(gamefile); console.log("Rewound one move while resyncing to online game."); aChangeWasMade = true; } @@ -614,7 +615,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { if (thisGamefileMove) { // The move is defined if (thisGamefileMove.compact === moves[i]) break; // The moves MATCH // The moves don't match... remove this one off our list. - movesequence.rewindMove(gamefile, { animate: false }); + movesequence.rewindMove(gamefile); console.log("Rewound one INCORRECT move while resyncing to online game."); aChangeWasMade = true; } @@ -650,12 +651,12 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { const isLastMove = i === moves.length - 1; movesequence.makeMove(gamefile, move, { doGameOverChecks: isLastMove, concludeGameIfOver: false}); - if (isLastMove) movesequence.animateMoveAtIdx(gamefile) + if (isLastMove) movesequence.animateMove(gamefile, move, true); console.log("Forwarded one move while resyncing to online game."); aChangeWasMade = true; } - if (!aChangeWasMade) movesequence.viewIndex(gamefile, originalMoveIndex); + if (!aChangeWasMade) movesequence.viewToIndex(gamefile, originalMoveIndex); else selection.reselectPiece(); // Reselect the selected piece from before we resynced. Recalc its moves and recolor it if needed. return true; // No cheating detected diff --git a/src/client/scripts/esm/util/math.js b/src/client/scripts/esm/util/math.js index fb638dc27..ba085ddac 100644 --- a/src/client/scripts/esm/util/math.js +++ b/src/client/scripts/esm/util/math.js @@ -479,8 +479,7 @@ function roundUpToPowerOf2(num) { * @param {Number} progress */ function moveTowards(s, e, progress) { - const maxProg = s + Math.sign(e - s) * min(abs(e - s), progress); - + return s + Math.sign(e - s) * Math.min(Math.abs(e - s), progress); } export default { From 17709602376a3eb8c4585807de9613883d02e788 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:20:54 +0000 Subject: [PATCH 07/44] update clock and gui on turm update --- src/client/scripts/esm/game/chess/movesequence.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 7e7c4abe9..fd2df3fed 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -14,6 +14,10 @@ import stats from "../gui/stats.js"; import guinavigation from "../gui/guinavigation.js"; // @ts-ignore import movepiece from "../../chess/logic/movepiece.js"; +// @ts-ignore +import guigameinfo from "../gui/guigameinfo.js"; +// @ts-ignore +import guiclock from "../gui/guiclock.js"; import boardchanges from "../../chess/logic/boardchanges.js"; import { animatableChanges, meshChanges } from "./graphicalchanges.js"; @@ -27,9 +31,12 @@ function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, con movepiece.generateMove(gamefile, move); movepiece.makeMove(gamefile, move); - movepiece.updateTurn(gamefile, { pushClock: !onlinegame.areInOnlineGame() }); boardchanges.runMove(gamefile, move, meshChanges, true); + movepiece.updateTurn(gamefile, { pushClock: !onlinegame.areInOnlineGame() }); + guigameinfo.updateWhosTurn(gamefile); + if (!onlinegame.areInOnlineGame()) guiclock.push(gamefile); + if (doGameOverChecks) { gamefileutility.doGameOverChecks(gamefile); if (concludeGameIfOver && gamefile.gameConclusion && !onlinegame.areInOnlineGame()) game.concludeGame(); From 98764538c82c6e75bcea91665fb7cb47705255c1 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 08/44] fix move animation, capture crashes though --- src/client/scripts/esm/game/chess/graphicalchanges.ts | 2 +- src/client/scripts/esm/game/chess/movesequence.ts | 6 +++--- src/client/scripts/esm/game/chess/selection.js | 2 +- src/client/scripts/esm/game/gui/guinavigation.js | 4 ++-- src/client/scripts/esm/game/misc/onlinegame.js | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index e2b4d9257..e0c8a130e 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -21,7 +21,7 @@ interface ChangeAnimations { const animatableChanges: ChangeAnimations = { forward: { "movePiece": animateMove, - "capturedPiece": animateCapture, + "capturePiece": animateCapture, }, backward: { diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index fd2df3fed..7acfed1b8 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -49,13 +49,13 @@ function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, con arrows.clearListOfHoveredPieces(); } -function animateMove(gamefile: gamefile, move: Move, forward = true) { +function animateMove(move: Move, forward = true) { const funcs = forward ? animatableChanges.forward : animatableChanges.backward; let clearanimations = true; for (const c of move.changes) { - if (c.action) continue; + if (c.action === undefined) continue; if (!(c.action in funcs)) continue; - // @ts-ignore + // @ts-ignore WHY NOT???? funcs[c.action](c, clearanimations); clearanimations = false; } diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index 89cd9b033..7ae28df77 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -290,7 +290,7 @@ function moveGamefilePiece(coords) { move.compact = compact; movesequence.makeMove(game.getGamefile(), move); - movesequence.animateMove(game.getGamefile(), move, true); + movesequence.animateMove(move, true); onlinegame.sendMove(); unselectPiece(); diff --git a/src/client/scripts/esm/game/gui/guinavigation.js b/src/client/scripts/esm/game/gui/guinavigation.js index 9f50752c8..3c392198a 100644 --- a/src/client/scripts/esm/game/gui/guinavigation.js +++ b/src/client/scripts/esm/game/gui/guinavigation.js @@ -396,7 +396,7 @@ function rewindMove() {4 const idx = gamefile.moveIndex; gamefile.moveIndex--; movesequence.viewMove(gamefile, gamefile.moves[idx], false); - movesequence.animateMove(gamefile, gamefile.moves[idx], false); + movesequence.animateMove(gamefile.moves[idx], false); selection.unselectPiece(); @@ -414,7 +414,7 @@ function forwardMove() { gamefile.moveIndex++; movesequence.viewMove(gamefile, gamefile.moves[gamefile.moveIndex], true); - movesequence.animateMove(gamefile, gamefile.moves[gamefile.moveIndex], true); + movesequence.animateMove(gamefile.moves[gamefile.moveIndex], true); // transition.teleportToLastMove() diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index 0a068f643..07783c367 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -474,7 +474,7 @@ function handleOpponentsMove(message) { // { move, gameConclusion, moveNumber, c move.type = piecemoved.type; specialdetect.transferSpecialFlags_FromCoordsToMove(endCoordsToAppendSpecial, move); movesequence.makeMove(gamefile, move); - movesequence.animateMove(gamefile, move, true); + movesequence.animateMove(move, true); selection.reselectPiece(); // Reselect the currently selected piece. Recalc its moves and recolor it if needed. @@ -651,7 +651,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { const isLastMove = i === moves.length - 1; movesequence.makeMove(gamefile, move, { doGameOverChecks: isLastMove, concludeGameIfOver: false}); - if (isLastMove) movesequence.animateMove(gamefile, move, true); + if (isLastMove) movesequence.animateMove(move, true); console.log("Forwarded one move while resyncing to online game."); aChangeWasMade = true; } From 0a8d425947e2194c86e558d041a5fdf5aa8a95b0 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 09/44] fix capture animation --- src/client/scripts/esm/game/chess/graphicalchanges.ts | 2 +- src/client/scripts/esm/game/rendering/animation.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index e0c8a130e..63456e29e 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -39,7 +39,7 @@ function animateReturn(change: MoveChange, clearanimations: boolean) { } function animateCapture(change: CaptureChange, clearanimations: boolean) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, change.capturedPiece.type, clearanimations); + animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, change.capturedPiece, clearanimations); } const meshChanges: ChangeApplication = { diff --git a/src/client/scripts/esm/game/rendering/animation.js b/src/client/scripts/esm/game/rendering/animation.js index 6d7a3f59e..4bd7bddc6 100644 --- a/src/client/scripts/esm/game/rendering/animation.js +++ b/src/client/scripts/esm/game/rendering/animation.js @@ -16,6 +16,7 @@ import spritesheet from './spritesheet.js'; * Type Definitions * @typedef {import('../../chess/util/moveutil.js').Move} Move * @typedef {import('./buffermodel.js').BufferModel} BufferModel + * @typedef {import('../../chess/logic/movepiece.js').Piece} Piece */ "use strict"; @@ -46,7 +47,7 @@ const moveAnimationDuration = { * @param {string} type - The type of piece to animate * @param {number[]} startCoords - [x,y] * @param {number[]} endCoords - [x,y] - * @param {string} [captured] The type of piece captured, if one was captured. + * @param {Piece} [captured] The piece captured, if one was captured. * @param {boolean} [resetAnimations] If false, allows animation of multiple pieces at once. Useful for castling. Default: true */ function animatePiece(type, startCoords, endCoords, captured, resetAnimations = true) { // captured: { type, coords } From b289e8987de0b360ddc848244ec986cafeab9bfc Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 10/44] fix pawn animation --- .../scripts/esm/chess/logic/specialmove.js | 20 +++++++++---------- .../scripts/esm/game/chess/selection.js | 5 +++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index 1db690e53..055353fc6 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -79,21 +79,19 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // Delete the piece captured - if (promotionTag) { - if (capturedPiece) boardchanges.queueDeletePiece(moveChanges, capturedPiece); + if (capturedPiece) { + boardchanges.queueCaputure(moveChanges, piece, move.endCoords, capturedPiece); + } else { + // Move the pawn + boardchanges.queueMovePiece(moveChanges, piece, move.endCoords); + } + if (promotionTag) { // Delete original pawn - boardchanges.queueDeletePiece(moveChanges, piece); + boardchanges.queueDeletePiece(moveChanges, {type: piece.type, coords: move.endCoords, index: piece.index}); - boardchanges.queueAddPiece(moveChanges, promotionTag, move.endCoords, null); + boardchanges.queueAddPiece(moveChanges, {type: promotionTag, coords: move.endCoords, index: null}); - } else /* enpassantTag */ { - if (capturedPiece) { - boardchanges.queueCaputure(moveChanges, piece, move.endCoords, capturedPiece); - } else { - // Move the pawn - boardchanges.queueMovePiece(moveChanges, piece, move.endCoords); - } } // Special move was executed! diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index 7ae28df77..e172da51c 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -204,8 +204,9 @@ function handleSelectingPiece(pieceClickedType) { // options.getEM() && pieceClickedType !== 'voidsN') // ^^ The extra conditions needed here so in edit mode and you click on an opponent piece // it will still forward you to front! - - return movesequence.viewFront(gamefile); + movesequence.viewFront(gamefile); + movesequence.animateMove(gamefile.moves[gamefile.moveIndex]); + return; } if (hoverSquareLegal) return; // Don't select different piece if the move is legal (its a capture) From 3c36a3a7f5cb3d678a807da43c7f79dac200aea2 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 11/44] Type changes and cleanup --- .../scripts/esm/chess/logic/boardchanges.ts | 111 +++++++----------- .../esm/game/chess/graphicalchanges.ts | 42 +++---- .../scripts/esm/game/chess/movesequence.ts | 5 +- 3 files changed, 65 insertions(+), 93 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index da416e488..3c02058d8 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -7,7 +7,6 @@ import jsutil from "../../util/jsutil.js"; // @ts-ignore import type { gamefile } from "./gamefile.js"; -// @ts-ignore import type { Coords } from "./movesets.js"; // @ts-ignore import type { Move } from "../util/moveutil.js"; @@ -23,37 +22,18 @@ interface Change { [changeData: string]: any } -interface PieceChange extends Change { - piece: Piece -} - -interface MoveChange extends PieceChange { - endCoords: Coords -} - -interface CaptureChange extends MoveChange { - capturedPiece: Piece -} - -interface RightsChange extends Change { - coords: string - curRights: any - rights: any -} - -interface EnpassantChange extends Change { - curPassant: Coords | undefined - newPassant: Coords | undefined -} - interface ActionList { [actionName: string]: T } +// I dislike eslint +// eslint-disable-next-line no-unused-vars +type genericChangeFunc = (gamefile: gamefile, change: any) => void; + interface ChangeApplication { - forward: ActionList<(gamefile: gamefile, change: any) => void> + forward: ActionList - backward: ActionList<(gamefile: gamefile, change: any) => void> + backward: ActionList } const changeFuncs: ChangeApplication = { @@ -76,42 +56,40 @@ const changeFuncs: ChangeApplication = { } }; -function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { +function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); // Need to differentiate this from move so animations can work return changes; } -function queueAddPiece(changes: Array, piece: Piece) { +function queueAddPiece(changes: Array, piece: Piece) { changes.push({action: 'add', piece: piece}); return changes; }; -function queueDeletePiece(changes: Array, piece: Piece) { +function queueDeletePiece(changes: Array, piece: Piece) { changes.push({action: 'delete', piece: piece}); return changes; } -function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { +function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); return changes; } -function queueSetSpecialRights(changes: Array, coords: string, curRights: any, rights: any) { +function queueSetSpecialRights(changes: Array, coords: string, curRights: any, rights: any) { changes.push({action: "setRights", coords: coords, curRights: curRights, rights: rights}); return changes; } -function queueSetEnPassant(changes: Array, curPassant: any, newPassant: any) { +function queueSetEnPassant(changes: Array, curPassant: any, newPassant: any) { changes.push({action: "setPassant", curPassant: curPassant, newPassant: newPassant}); return changes; } -function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList<(gamefile: gamefile, change: any) => void>) { +function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { for (const c of changes) { - if (typeof c.action !== "string") continue; if (!(c.action in funcs)) continue; - // @ts-ignore - funcs[c.action](gamefile, c); + funcs[c.action]!(gamefile, c); } } @@ -126,13 +104,13 @@ function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, * @param gamefile - The gamefile * @param change - the data of the piece to be added */ -function addPiece(gamefile: gamefile, change: PieceChange) { // desiredIndex optional - const piece = change.piece; +function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional + const piece = change['piece']; const list = gamefile.ourPieces[piece.type]; // If no index specified, make the default the first undefined in the list! - if (piece.index == null) change.piece.index = list.undefineds[0]; + if (piece.index == null) change['piece'].index = list.undefineds[0]; if (piece.index == null) { list.push(piece.coords); @@ -151,8 +129,8 @@ function addPiece(gamefile: gamefile, change: PieceChange) { // desiredIndex opt organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -function deletePiece(gamefile: gamefile, change: PieceChange) { // piece: { type, index } - const piece = change.piece; +function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, index } + const piece = change['piece']; const list = gamefile.ourPieces[piece.type]; gamefileutility.deleteIndexFromPieceList(list, piece.index); @@ -161,9 +139,9 @@ function deletePiece(gamefile: gamefile, change: PieceChange) { // piece: { type organizedlines.removeOrganizedPiece(gamefile, piece.coords); } -function movePiece(gamefile: gamefile, change: MoveChange) { - const piece = change.piece; - const endCoords = change.endCoords; +function movePiece(gamefile: gamefile, change: Change) { + const piece = change['piece']; + const endCoords = change['endCoords']; // Move the piece, change the coordinates gamefile.ourPieces[piece.type][piece.index] = endCoords; @@ -175,9 +153,9 @@ function movePiece(gamefile: gamefile, change: MoveChange) { organizedlines.organizePiece(piece.type, endCoords, gamefile); } -function returnPiece(gamefile: gamefile, change: MoveChange) { - const piece = change.piece; - const endCoords = change.endCoords; +function returnPiece(gamefile: gamefile, change: Change) { + const piece = change['piece']; + const endCoords = change['endCoords']; // Move the piece, change the coordinates gamefile.ourPieces[piece.type][piece.index] = piece.coords; @@ -189,41 +167,41 @@ function returnPiece(gamefile: gamefile, change: MoveChange) { organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -function capturePiece(gamefile: gamefile, change: CaptureChange) { - deletePiece(gamefile, {piece: change.capturedPiece, action: ""}); +function capturePiece(gamefile: gamefile, change: Change) { + deletePiece(gamefile, {piece: change['capturedPiece'], action: ""}); movePiece(gamefile, change); } -function uncapturePiece(gamefile: gamefile, change: CaptureChange) { +function uncapturePiece(gamefile: gamefile, change: Change) { returnPiece(gamefile, change); - addPiece(gamefile, {piece: change.capturedPiece, action:""}); + addPiece(gamefile, {piece: change['capturedPiece'], action:""}); } -function setRights(gamefile: gamefile, change: RightsChange) { - if (change.rights === undefined) { - delete gamefile.specialRights[change.coords]; +function setRights(gamefile: gamefile, change: Change) { + if (change['rights'] === undefined) { + delete gamefile.specialRights[change['coords']]; } else { - gamefile.specialRights[change.coords] = change.rights; + gamefile.specialRights[change['coords']] = change['rights']; } } -function revertRights(gamefile: gamefile, change: RightsChange) { - if (change.curRights === undefined) { - delete gamefile.specialRights[change.coords]; +function revertRights(gamefile: gamefile, change: Change) { + if (change['curRights'] === undefined) { + delete gamefile.specialRights[change['coords']]; } else { - gamefile.specialRights[change.coords] = change.curRights; + gamefile.specialRights[change['coords']] = change['curRights']; } } -function setPassant(gamefile: gamefile, change: EnpassantChange) { - gamefile.enpassant = change.newPassant; +function setPassant(gamefile: gamefile, change: Change) { + gamefile.enpassant = change['newPassant']; } -function revertPassant(gamefile: gamefile, change: EnpassantChange) { - if (change.curPassant === undefined) { +function revertPassant(gamefile: gamefile, change: Change) { + if (change['curPassant'] === undefined) { delete gamefile.enpassant; } else { - gamefile.enpassant = change.curPassant; + gamefile.enpassant = change['curPassant']; } } @@ -231,11 +209,6 @@ export type { ActionList, ChangeApplication, Change, - PieceChange, - MoveChange, - CaptureChange, - RightsChange, - EnpassantChange, }; export default { diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index 63456e29e..384c7a14a 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -6,16 +6,16 @@ import piecesmodel from "../rendering/piecesmodel.js"; import organizedlines from "../../chess/logic/organizedlines.js"; // @ts-ignore -import type { ChangeApplication, PieceChange, MoveChange, CaptureChange, ActionList } from "../../chess/logic/boardchanges.js"; +import type { ChangeApplication, Change, ActionList } from "../../chess/logic/boardchanges.js"; // @ts-ignore import type gamefile from "../../chess/logic/gamefile.js"; // ESLint, THIS IS A TYPE INTERFACE SHUT UP interface ChangeAnimations { // eslint-disable-next-line no-unused-vars - forward: ActionList<(change: CaptureChange, clearanimations: boolean) => void> + forward: ActionList<(change: Change, clearanimations: boolean) => void> // eslint-disable-next-line no-unused-vars - backward: ActionList<(change: MoveChange, clearanimations: boolean) => void> + backward: ActionList<(change: Change, clearanimations: boolean) => void> } const animatableChanges: ChangeAnimations = { @@ -30,16 +30,16 @@ const animatableChanges: ChangeAnimations = { } }; -function animateMove(change: MoveChange, clearanimations: boolean) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, undefined, clearanimations); +function animateMove(change: Change, clearanimations: boolean) { + animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], undefined, clearanimations); } -function animateReturn(change: MoveChange, clearanimations: boolean) { - animation.animatePiece(change.piece.type, change.endCoords, change.piece.coords, undefined, clearanimations); +function animateReturn(change: Change, clearanimations: boolean) { + animation.animatePiece(change['piece'].type, change['endCoords'], change['piece'].coords, undefined, clearanimations); } -function animateCapture(change: CaptureChange, clearanimations: boolean) { - animation.animatePiece(change.piece.type, change.piece.coords, change.endCoords, change.capturedPiece, clearanimations); +function animateCapture(change: Change, clearanimations: boolean) { + animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], change['capturedPiece'], clearanimations); } const meshChanges: ChangeApplication = { @@ -58,34 +58,34 @@ const meshChanges: ChangeApplication = { } }; -function addMeshPiece(gamefile: gamefile, change: PieceChange) { - piecesmodel.overwritebufferdata(gamefile, change.piece, change.piece.coords, change.piece.type); +function addMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.overwritebufferdata(gamefile, change['piece'], change['piece'].coords, change['piece'].type); // Do we need to add more undefineds? // Only adding pieces can ever reduce the number of undefineds we have, so we do that here! if (organizedlines.areWeShortOnUndefineds(gamefile)) organizedlines.addMoreUndefineds(gamefile, { log: true }); } -function deleteMeshPiece(gamefile: gamefile, change: PieceChange) { - piecesmodel.deletebufferdata(gamefile, change.piece); +function deleteMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.deletebufferdata(gamefile, change['piece']); } -function moveMeshPiece(gamefile: gamefile, change: MoveChange) { - piecesmodel.movebufferdata(gamefile, change.piece, change.endCoords); +function moveMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.movebufferdata(gamefile, change['piece'], change['endCoords']); } -function returnMeshPiece(gamefile: gamefile, change: MoveChange) { - piecesmodel.movebufferdata(gamefile, change.piece, change.piece.coords); +function returnMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.movebufferdata(gamefile, change['piece'], change['piece'].coords); } -function captureMeshPiece(gamefile: gamefile, change: CaptureChange) { - piecesmodel.deletebufferdata(gamefile, change.capturedPiece); +function captureMeshPiece(gamefile: gamefile, change: Change) { + piecesmodel.deletebufferdata(gamefile, change['capturedPiece']); moveMeshPiece(gamefile, change); } -function uncaptureMeshPiece(gamefile: gamefile, change: CaptureChange) { +function uncaptureMeshPiece(gamefile: gamefile, change: Change) { returnMeshPiece(gamefile, change); - addMeshPiece(gamefile, {action: "addPiece", piece: change.capturedPiece}); + addMeshPiece(gamefile, {action: "addPiece", piece: change['capturedPiece']}); } export { diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 7acfed1b8..bcc9175e1 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -26,6 +26,7 @@ import { animatableChanges, meshChanges } from "./graphicalchanges.js"; import type gamefile from "../../chess/logic/gamefile.js"; // @ts-ignore import type { Move } from "../../chess/util/moveutil.js"; +import type { Change } from "../../chess/logic/boardchanges.js"; function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, concludeGameIfOver = true} = {}) { @@ -53,10 +54,8 @@ function animateMove(move: Move, forward = true) { const funcs = forward ? animatableChanges.forward : animatableChanges.backward; let clearanimations = true; for (const c of move.changes) { - if (c.action === undefined) continue; if (!(c.action in funcs)) continue; - // @ts-ignore WHY NOT???? - funcs[c.action](c, clearanimations); + funcs[c.action]!(c, clearanimations); clearanimations = false; } } From d6f170439f4626d5c8a49b8b9493ffc54a7e9c68 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 12/44] some onlinefixes --- src/client/scripts/esm/chess/logic/legalmoves.js | 3 ++- src/client/scripts/esm/chess/logic/movepiece.js | 1 + src/client/scripts/esm/game/misc/onlinegame.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/legalmoves.js b/src/client/scripts/esm/chess/logic/legalmoves.js index eda415f51..4ddc34312 100644 --- a/src/client/scripts/esm/chess/logic/legalmoves.js +++ b/src/client/scripts/esm/chess/logic/legalmoves.js @@ -311,7 +311,8 @@ function isOpponentsMoveLegal(gamefile, move, claimedGameConclusion) { const attackersB4Forwarding = jsutil.deepCopyObject(gamefile.attackers); const originalMoveIndex = gamefile.moveIndex; // Used to return to this move after we're done simulating - movepiece.forEachMove(gamefile, gamefile.move.length - 1, (m) => movepiece.applyMove(gamefile, m, true)); + movepiece.forEachMove(gamefile, gamefile.moves.length - 1, (m) => movepiece.applyMove(gamefile, m, true)); + gamefile.moveIndex = gamefile.moves.length - 1; movepiece.updateTurn(gamefile, { pushClock: false }); // Make sure a piece exists on the start coords diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 48175dfd8..a45457809 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -12,6 +12,7 @@ import formatconverter from './formatconverter.js'; import colorutil from '../util/colorutil.js'; import jsutil from '../../util/jsutil.js'; import coordutil from '../util/coordutil.js'; +import wincondition from './wincondition.js'; // Import End /** diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index 07783c367..66d780a7f 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -656,7 +656,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { aChangeWasMade = true; } - if (!aChangeWasMade) movesequence.viewToIndex(gamefile, originalMoveIndex); + if (!aChangeWasMade) movesequence.viewIndex(gamefile, originalMoveIndex); else selection.reselectPiece(); // Reselect the selected piece from before we resynced. Recalc its moves and recolor it if needed. return true; // No cheating detected From bd2ee20db76a952404bb74453f7c12b7436aa2fb Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 13/44] fix backwards move iteration --- src/client/scripts/esm/chess/logic/movepiece.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index a45457809..d0ed54a6f 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -273,15 +273,22 @@ function calculateMoveFromShortmove(gamefile, shortmove) { * @param {number} targetIndex */ function forEachMove(gamefile, targetIndex, callback) { - console.log(gamefile.moves.length, targetIndex); + console.trace(gamefile.moves.length, targetIndex); if (gamefile.moves.length <= targetIndex) return console.error("Cannot!"); //TODO: make this error useful + targetIndex = targetIndex >= gamefile.moveIndex ? targetIndex : targetIndex + 1; + let i = gamefile.moveIndex; while (i !== targetIndex) { i = math.moveTowards(i, targetIndex, 1); const move = gamefile.moves[i]; + if (move === undefined) { + console.log(`Undefined! ${i}, ${targetIndex}`); + continue; + } + callback(move); } } From 4a0e6cf0e3bba6643b34649d9469ed9249da631d Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 14/44] added gotoMove, move clock.push out of updateTurn --- .../scripts/esm/chess/logic/checkdetection.js | 6 +++--- .../scripts/esm/chess/logic/legalmoves.js | 13 ++++++------- src/client/scripts/esm/chess/logic/movepiece.js | 11 +++++++---- .../scripts/esm/game/chess/movesequence.ts | 17 ++++++++++------- src/client/scripts/esm/game/misc/onlinegame.js | 2 +- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index d95971619..734f941a8 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -375,8 +375,8 @@ function removeSlidingMovesThatOpenDiscovered(gamefile, moves, kingCoords, piece if (sameLines.length === 0) return; // Delete the piece, and add it back when we're done! - let deleteChange = boardchanges.queueDeletePiece([], pieceSelected); - boardchanges.applyChanges(gamefile, deleteChange, boardchanges.changeFuncs.forward) + const deleteChange = boardchanges.queueDeletePiece([], pieceSelected); + boardchanges.applyChanges(gamefile, deleteChange, boardchanges.changeFuncs.forward); // let checklines = []; // For Idon's code below // For every line direction we share with the king... @@ -451,7 +451,7 @@ function removeSlidingMovesThatOpenDiscovered(gamefile, moves, kingCoords, piece // } // Add the piece back with the EXACT SAME index it had before!! - boardchanges.applyChanges(gamefile, deleteChange, boardchanges.changeFuncs.backward) + boardchanges.applyChanges(gamefile, deleteChange, boardchanges.changeFuncs.backward); } // Appends moves to moves.individual if the selected pieces is able to get between squares 1 & 2 diff --git a/src/client/scripts/esm/chess/logic/legalmoves.js b/src/client/scripts/esm/chess/logic/legalmoves.js index 4ddc34312..409dbb2ed 100644 --- a/src/client/scripts/esm/chess/logic/legalmoves.js +++ b/src/client/scripts/esm/chess/logic/legalmoves.js @@ -311,9 +311,8 @@ function isOpponentsMoveLegal(gamefile, move, claimedGameConclusion) { const attackersB4Forwarding = jsutil.deepCopyObject(gamefile.attackers); const originalMoveIndex = gamefile.moveIndex; // Used to return to this move after we're done simulating - movepiece.forEachMove(gamefile, gamefile.moves.length - 1, (m) => movepiece.applyMove(gamefile, m, true)); - gamefile.moveIndex = gamefile.moves.length - 1; - movepiece.updateTurn(gamefile, { pushClock: false }); + movepiece.gotoMove(gamefile, gamefile.moves.length - 1, (m) => movepiece.applyMove(gamefile, m, true)); + movepiece.updateTurn(gamefile); // Make sure a piece exists on the start coords const piecemoved = gamefileutility.getPieceAtCoords(gamefile, moveCopy.startCoords); // { type, index, coords } @@ -380,15 +379,15 @@ function isOpponentsMoveLegal(gamefile, move, claimedGameConclusion) { // ... // Rewind the game back to the index we were originally on before simulating - movepiece.forEachMove(gamefile, originalMoveIndex, (m) => movepiece.applyMove(gamefile, m, false)); - movepiece.updateTurn(gamefile, { pushClock: false }); + movepiece.gotoMove(gamefile, originalMoveIndex, (m) => movepiece.applyMove(gamefile, m, false)); + movepiece.updateTurn(gamefile); return true; // By this point, nothing illegal! function rewindGameAndReturnReason(reasonIllegal) { // Rewind the game back to the index we were originally on - movepiece.forEachMove(gamefile, originalMoveIndex, (m) => movepiece.applyMove(gamefile, m, false)); - movepiece.updateTurn(gamefile, { pushClock: false }); + movepiece.gotoMove(gamefile, originalMoveIndex, (m) => movepiece.applyMove(gamefile, m, false)); + movepiece.updateTurn(gamefile); return reasonIllegal; } } diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index d0ed54a6f..72fb75755 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -154,11 +154,8 @@ function incrementMoveRule(gamefile, typeMoved, wasACapture) { * @param {Object} options - An object that may contain the options (all are default *true*): * - `pushClock`: Whether to push the clock. */ -function updateTurn(gamefile, { pushClock = true } = {}) { +function updateTurn(gamefile) { gamefile.whosTurn = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); - if (pushClock) { - return clock.push(gamefile); - }; } /** @@ -293,6 +290,11 @@ function forEachMove(gamefile, targetIndex, callback) { } } +function gotoMove(gamefile, index, callback) { + forEachMove(gamefile, index, callback); + gamefile.moveIndex = index; +} + /** * **Universal** function for undo'ing or rewinding moves. * Called when we're rewinding the game to view past moves, @@ -360,6 +362,7 @@ export default { makeMove, updateTurn, forEachMove, + gotoMove, makeAllMovesInGame, calculateMoveFromShortmove, applyMove, diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index bcc9175e1..b14c6f7ae 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -18,6 +18,8 @@ import movepiece from "../../chess/logic/movepiece.js"; import guigameinfo from "../gui/guigameinfo.js"; // @ts-ignore import guiclock from "../gui/guiclock.js"; +// @ts-ignore +import clock from "../../chess/logic/clock.js"; import boardchanges from "../../chess/logic/boardchanges.js"; import { animatableChanges, meshChanges } from "./graphicalchanges.js"; @@ -26,7 +28,6 @@ import { animatableChanges, meshChanges } from "./graphicalchanges.js"; import type gamefile from "../../chess/logic/gamefile.js"; // @ts-ignore import type { Move } from "../../chess/util/moveutil.js"; -import type { Change } from "../../chess/logic/boardchanges.js"; function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, concludeGameIfOver = true} = {}) { @@ -34,9 +35,13 @@ function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, con movepiece.makeMove(gamefile, move); boardchanges.runMove(gamefile, move, meshChanges, true); - movepiece.updateTurn(gamefile, { pushClock: !onlinegame.areInOnlineGame() }); + movepiece.updateTurn(gamefile); guigameinfo.updateWhosTurn(gamefile); - if (!onlinegame.areInOnlineGame()) guiclock.push(gamefile); + + if (!onlinegame.areInOnlineGame()) { + clock.push(gamefile); + guiclock.push(gamefile); + } if (doGameOverChecks) { gamefileutility.doGameOverChecks(gamefile); @@ -68,8 +73,7 @@ function rewindMove(gamefile: gamefile) { } function viewFront(gamefile: gamefile) { - movepiece.forEachMove(gamefile, gamefile.moves.length - 1, (m: Move) => viewMove(gamefile, m, true)); - gamefile.moveIndex = gamefile.moves.length - 1; + movepiece.gotoMove(gamefile, gamefile.moves.length - 1, (m: Move) => viewMove(gamefile, m, true)); guinavigation.update_MoveButtons(); stats.showMoves(); } @@ -80,8 +84,7 @@ function viewMove(gamefile: gamefile, move: Move, forward = true) { } function viewIndex(gamefile: gamefile, index: number) { - movepiece.forEachMove(gamefile, index, (m: Move) => viewMove(gamefile, m, index >= gamefile.moveIndex)); - gamefile.moveIndex = index; + movepiece.gotoMove(gamefile, index, (m: Move) => viewMove(gamefile, m, index >= gamefile.moveIndex)); guinavigation.update_MoveButtons(); stats.showMoves(); } diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index 66d780a7f..0d8ddfd46 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -464,7 +464,7 @@ function handleOpponentsMove(message) { // { move, gameConclusion, moveNumber, c movesequence.viewFront(gamefile); - // Forward the move... + // // Forward the move... const piecemoved = gamefileutility.getPieceAtCoords(gamefile, move.startCoords); const legalMoves = legalmoves.calculate(gamefile, piecemoved); From d47072ad6759eb1622c8d6b3dd80f579273364ef Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 15/44] fixed backwards move jumps *again* --- src/client/scripts/esm/chess/logic/movepiece.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 72fb75755..aa6bfe744 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -270,16 +270,18 @@ function calculateMoveFromShortmove(gamefile, shortmove) { * @param {number} targetIndex */ function forEachMove(gamefile, targetIndex, callback) { - console.trace(gamefile.moves.length, targetIndex); - if (gamefile.moves.length <= targetIndex) return console.error("Cannot!"); //TODO: make this error useful + const forwards = targetIndex >= gamefile.moveIndex; + targetIndex = forwards ? targetIndex : targetIndex + 1; + let i = forwards ? gamefile.moveIndex : gamefile.moveIndex + 1; + if (gamefile.moves.length <= targetIndex || targetIndex < 0) return console.error("Target index is outside of the movelist!"); - targetIndex = targetIndex >= gamefile.moveIndex ? targetIndex : targetIndex + 1; - - let i = gamefile.moveIndex; + console.trace(gamefile.moves.length - 1, targetIndex); while (i !== targetIndex) { i = math.moveTowards(i, targetIndex, 1); const move = gamefile.moves[i]; + + console.log(move.compact, forwards); if (move === undefined) { console.log(`Undefined! ${i}, ${targetIndex}`); From 5797a0d4ada20875e2a232cc796521b5284d17bf Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 16/44] fix refreshes not resyncing check or turn --- src/client/scripts/esm/chess/logic/movepiece.js | 10 +++++----- src/client/scripts/esm/game/misc/onlinegame.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index aa6bfe744..a3c28f5f1 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -165,7 +165,7 @@ function updateTurn(gamefile) { * @param {gamefile} gamefile - The gamefile * @param {boolean} [flagMoveAsCheck] - If *true*, flags the last played move as a check. Default: true */ -function updateInCheck(gamefile, flagMoveAsCheck = true) { +function updateInCheck(gamefile) { let attackers = undefined; // Only pass in attackers array to be filled by the checking pieces if we're using checkmate win condition. @@ -176,7 +176,7 @@ function updateInCheck(gamefile, flagMoveAsCheck = true) { gamefile.inCheck = checkdetection.detectCheck(gamefile, whosTurnItWasAtMoveIndex, attackers); // Passes in the gamefile as an argument gamefile.attackers = attackers || []; // Erase the checking pieces calculated from previous turn and pass in new ones! - if (gamefile.inCheck && flagMoveAsCheck) moveutil.flagLastMoveAsCheck(gamefile); + if (gamefile.inCheck && gamefile.moveIndex !== -1) moveutil.flagLastMoveAsCheck(gamefile); } /** @@ -210,9 +210,9 @@ function makeAllMovesInGame(gamefile, moves) { gamefile.moveIndex++; gamefile.moves.push(move); } - - // FIXME: REENABLE, TESTING - // if (moves.length === 0) updateInCheck(gamefile, false); + + updateTurn(gamefile); + updateInCheck(gamefile); gamefileutility.doGameOverChecks(gamefile); // Update the gameConclusion } diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index 0d8ddfd46..0b31b157a 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -586,7 +586,7 @@ function handleServerGameUpdate(messageContents) { // { gameConclusion, clockVal * @returns {boolean} *false* if it detected an illegal move played by our opponent. */ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { - + console.log("Resyncing..."); // Early exit case. If we have played exactly 1 more move than the server, // and the rest of the moves list matches, don't modify our moves, // just re-submit our move! From 879ebf6699cd651c07bef0ce1d06e82ad549c2a9 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 17/44] make moveindex iteration throw error, run all moves updates move rule --- src/client/scripts/esm/chess/logic/movepiece.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index a3c28f5f1..894c9199e 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -52,7 +52,7 @@ function applyMove(gamefile, move, forward = true) { } function makeMove(gamefile, move, { updateProperties = true } = {}) { - const wasACapture = move.captured != null; + const wasACapture = move.captured !== undefined; applyMove(gamefile, move); @@ -206,11 +206,12 @@ function makeAllMovesInGame(gamefile, moves) { // Make the move in the game! applyMove(gamefile, move); - + incrementMoveRule(gamefile, move.type, move.captured !== undefined); + gamefile.moveIndex++; gamefile.moves.push(move); } - + updateTurn(gamefile); updateInCheck(gamefile); @@ -270,10 +271,13 @@ function calculateMoveFromShortmove(gamefile, shortmove) { * @param {number} targetIndex */ function forEachMove(gamefile, targetIndex, callback) { + if (targetIndex === gamefile.moveIndex) return; + const forwards = targetIndex >= gamefile.moveIndex; targetIndex = forwards ? targetIndex : targetIndex + 1; let i = forwards ? gamefile.moveIndex : gamefile.moveIndex + 1; - if (gamefile.moves.length <= targetIndex || targetIndex < 0) return console.error("Target index is outside of the movelist!"); + + if (gamefile.moves.length <= targetIndex || targetIndex < 0) throw new Error("Target index is outside of the movelist!"); console.trace(gamefile.moves.length - 1, targetIndex); From 1c002bda4fa4a930c88e2f8462821b044b10573a Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:21:34 +0000 Subject: [PATCH 18/44] fixed online game using the wrong module --- src/client/scripts/esm/game/misc/onlinegame.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/esm/game/misc/onlinegame.js b/src/client/scripts/esm/game/misc/onlinegame.js index 0b31b157a..45b253475 100644 --- a/src/client/scripts/esm/game/misc/onlinegame.js +++ b/src/client/scripts/esm/game/misc/onlinegame.js @@ -29,6 +29,7 @@ import config from '../config.js'; import pingManager from '../../util/pingManager.js'; import movesequence from '../chess/movesequence.js'; import options from '../rendering/options.js'; +import movepiece from '../../chess/logic/movepiece.js'; // Import End /** @@ -628,7 +629,7 @@ function synchronizeMovesList(gamefile, moves, claimedGameConclusion) { while (i < moves.length - 1) { // Increment i, adding the server's correct moves to our moves list i++; const thisShortmove = moves[i]; // '1,2>3,4Q' The shortmove from the server's move list to add - const move = moveutil.calculateMoveFromShortmove(gamefile, thisShortmove); + const move = movepiece.calculateMoveFromShortmove(gamefile, thisShortmove); const colorThatPlayedThisMove = moveutil.getColorThatPlayedMoveIndex(gamefile, i); const opponentPlayedThisMove = colorThatPlayedThisMove === opponentColor; From 90a013c2eb7953e194f2bf213c12423b04187113 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:43:06 +0000 Subject: [PATCH 19/44] unbreak animations --- src/client/scripts/esm/game/chess/game.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/esm/game/chess/game.js b/src/client/scripts/esm/game/chess/game.js index 63a32a1a9..77901b089 100644 --- a/src/client/scripts/esm/game/chess/game.js +++ b/src/client/scripts/esm/game/chess/game.js @@ -227,7 +227,7 @@ async function loadGamefile(newGamefile) { // spinny pawn animation has time to fade away. animateLastMoveTimeoutID = setTimeout(() => { const move = gamefile.moves[gamefile.moveIndex]; - if (move !== undefined) movesequence.animateMove(gamefile, move, true); + if (move !== undefined) movesequence.animateMove(move, true); }, delayOfLatestMoveAnimationOnRejoinMillis); // Disable miniimages and arrows if there's over 50K pieces. They render too slow. From d6e62ec1e734c6cfcbe3560a0920dca62ad42207 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Sun, 22 Dec 2024 11:29:42 +0000 Subject: [PATCH 20/44] fix royal capture --- src/client/scripts/esm/chess/logic/wincondition.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/wincondition.js b/src/client/scripts/esm/chess/logic/wincondition.js index f0fe4fe53..6ce2e73fb 100644 --- a/src/client/scripts/esm/chess/logic/wincondition.js +++ b/src/client/scripts/esm/chess/logic/wincondition.js @@ -131,14 +131,21 @@ function detectMoveRule(gamefile) { // Returns true if the very last move captured a royal piece. function wasLastMoveARoyalCapture(gamefile) { const lastMove = moveutil.getLastMove(gamefile.moves); + if (!lastMove) return false; - if (!lastMove.captured) return false; // Last move not a capture + const capturedTypes = new Set(); + + for (const c of lastMove.changes) { + if (c.action !== "capturePiece") continue; + capturedTypes.add(colorutil.trimColorExtensionFromType(c.capturedPiece.type)); + } - const trimmedTypeCaptured = colorutil.trimColorExtensionFromType(lastMove.captured); + if (!capturedTypes.size) return false; // Last move not a capture // Does the piece type captured equal any royal piece? - return typeutil.royals.includes(trimmedTypeCaptured); + // Idk why vscode does not hve set methods + return !capturedTypes.isDisjointFrom(new Set(typeutil.royals)); } /** From 511bade5ba3c6182698901c7ac014cb33c4ab6b5 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Sun, 22 Dec 2024 12:47:43 +0000 Subject: [PATCH 21/44] fixed cleints accusing accusing eachother on game conclusion added a wrapper for move simulation --- .../scripts/esm/chess/logic/boardchanges.ts | 26 +++-- .../scripts/esm/chess/logic/checkdetection.js | 4 +- .../scripts/esm/chess/logic/legalmoves.js | 9 +- .../scripts/esm/chess/logic/movepiece.js | 97 ++++++++++--------- .../scripts/esm/game/chess/movesequence.ts | 8 +- 5 files changed, 84 insertions(+), 60 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 3c02058d8..52a49b51e 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -56,36 +56,45 @@ const changeFuncs: ChangeApplication = { } }; +// TODO: doc function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); // Need to differentiate this from move so animations can work return changes; } +// TODO: doc function queueAddPiece(changes: Array, piece: Piece) { changes.push({action: 'add', piece: piece}); return changes; }; +// TODO: doc function queueDeletePiece(changes: Array, piece: Piece) { changes.push({action: 'delete', piece: piece}); return changes; } +// TODO: doc function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); return changes; } +// TODO: doc function queueSetSpecialRights(changes: Array, coords: string, curRights: any, rights: any) { + if (curRights === rights) return changes; // Nothing has changed changes.push({action: "setRights", coords: coords, curRights: curRights, rights: rights}); return changes; } +// TODO: doc function queueSetEnPassant(changes: Array, curPassant: any, newPassant: any) { + if (curPassant === newPassant) return changes; // Nothing has changed changes.push({action: "setPassant", curPassant: curPassant, newPassant: newPassant}); return changes; } +// TODO: doc function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { for (const c of changes) { if (!(c.action in funcs)) continue; @@ -93,17 +102,13 @@ function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionL } } +// TODO: doc function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { const funcs = forward ? changeFuncs.forward : changeFuncs.backward; applyChanges(gamefile, move.changes, funcs); } -/** - * Most basic add-a-piece method. Adds it the gamefile's piece list, - * organizes the piece in the organized lists, and updates its mesh data. - * @param gamefile - The gamefile - * @param change - the data of the piece to be added - */ +// TODO: doc function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional const piece = change['piece']; @@ -129,6 +134,7 @@ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional organizedlines.organizePiece(piece.type, piece.coords, gamefile); } +// TODO: doc function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, index } const piece = change['piece']; @@ -139,6 +145,7 @@ function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, ind organizedlines.removeOrganizedPiece(gamefile, piece.coords); } +// TODO: doc function movePiece(gamefile: gamefile, change: Change) { const piece = change['piece']; const endCoords = change['endCoords']; @@ -153,6 +160,7 @@ function movePiece(gamefile: gamefile, change: Change) { organizedlines.organizePiece(piece.type, endCoords, gamefile); } +// TODO: doc function returnPiece(gamefile: gamefile, change: Change) { const piece = change['piece']; const endCoords = change['endCoords']; @@ -167,16 +175,19 @@ function returnPiece(gamefile: gamefile, change: Change) { organizedlines.organizePiece(piece.type, piece.coords, gamefile); } +// TODO: doc function capturePiece(gamefile: gamefile, change: Change) { deletePiece(gamefile, {piece: change['capturedPiece'], action: ""}); movePiece(gamefile, change); } +// TODO: doc function uncapturePiece(gamefile: gamefile, change: Change) { returnPiece(gamefile, change); addPiece(gamefile, {piece: change['capturedPiece'], action:""}); } +// TODO: doc function setRights(gamefile: gamefile, change: Change) { if (change['rights'] === undefined) { delete gamefile.specialRights[change['coords']]; @@ -185,6 +196,7 @@ function setRights(gamefile: gamefile, change: Change) { } } +// TODO: doc function revertRights(gamefile: gamefile, change: Change) { if (change['curRights'] === undefined) { delete gamefile.specialRights[change['coords']]; @@ -193,10 +205,12 @@ function revertRights(gamefile: gamefile, change: Change) { } } +// TODO: doc function setPassant(gamefile: gamefile, change: Change) { gamefile.enpassant = change['newPassant']; } +// TODO: doc function revertPassant(gamefile: gamefile, change: Change) { if (change['curPassant'] === undefined) { delete gamefile.enpassant; diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index 734f941a8..d9d8e085d 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -240,6 +240,8 @@ function removeMovesThatPutYouInCheck(gamefile, moves, pieceSelected, color) { / // 2. Individual moves. We can iterate through these and use detectCheck() to test them. removeIndividualMovesThatPutYouInCheck(gamefile, moves.individual, pieceSelected, color); + + console.log(moves); } // Time complexity O(1) @@ -258,7 +260,7 @@ function doesMovePutInCheck(gamefile, pieceSelected, destCoords, color) { // pie /** @type {Move} */ const move = { type: pieceSelected.type, startCoords: jsutil.deepCopyObject(pieceSelected.coords), endCoords: moveutil.stripSpecialMoveTagsFromCoords(destCoords) }; specialdetect.transferSpecialFlags_FromCoordsToMove(destCoords, move); - return movepiece.simulateMove(gamefile, move, color).isCheck; + return movepiece.getSimulatedCheck(gamefile, move, color); } diff --git a/src/client/scripts/esm/chess/logic/legalmoves.js b/src/client/scripts/esm/chess/logic/legalmoves.js index 409dbb2ed..f8878d596 100644 --- a/src/client/scripts/esm/chess/logic/legalmoves.js +++ b/src/client/scripts/esm/chess/logic/legalmoves.js @@ -363,11 +363,10 @@ function isOpponentsMoveLegal(gamefile, move, claimedGameConclusion) { // Only do so if the win condition is decisive (exclude win conditions declared by the server, // such as time, aborted, resignation, disconnect) if (claimedGameConclusion === false || winconutil.isGameConclusionDecisive(claimedGameConclusion)) { - const color = colorutil.getPieceColorFromType(piecemoved.type); - const infoAboutSimulatedMove = movepiece.simulateMove(gamefile, moveCopy, color, { doGameOverChecks: true }); // { isCheck, gameConclusion } - if (infoAboutSimulatedMove.gameConclusion !== claimedGameConclusion) { - console.log(`Opponent's move is illegal because gameConclusion doesn't match. Should be "${infoAboutSimulatedMove.gameConclusion}", received "${claimedGameConclusion}". Their move: ${JSON.stringify(moveCopy)}`); - return rewindGameAndReturnReason(`Game conclusion isn't correct. Received: ${claimedGameConclusion}. Should be ${infoAboutSimulatedMove.gameConclusion}`); + const simulatedConclusion = movepiece.getSimulatedConclusion(gamefile, moveCopy); // { isCheck, gameConclusion } + if (simulatedConclusion !== claimedGameConclusion) { + console.log(`Opponent's move is illegal because gameConclusion doesn't match. Should be "${simulatedConclusion}", received "${claimedGameConclusion}". Their move: ${JSON.stringify(moveCopy)}`); + return rewindGameAndReturnReason(`Game conclusion isn't correct. Received: ${claimedGameConclusion}. Should be ${simulatedConclusion}`); } } diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 894c9199e..a2350afc9 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -29,6 +29,7 @@ import wincondition from './wincondition.js'; /** Here lies the universal methods for moving pieces, forward or rewinding. */ +// TODO: doc function generateMove(gamefile, move) { move.changes = []; @@ -47,23 +48,32 @@ function generateMove(gamefile, move) { if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move); // Move piece regularly (no special tag) } +// TODO: doc function applyMove(gamefile, move, forward = true) { boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); } +// TODO: doc function makeMove(gamefile, move, { updateProperties = true } = {}) { - const wasACapture = move.captured !== undefined; - applyMove(gamefile, move); gamefile.moveIndex++; gamefile.moves.push(move); + updateTurn(gamefile); // The "check" property will be added inside updateInCheck()... // The "mate" property will be added inside our game conclusion checks... - if (updateProperties) incrementMoveRule(gamefile, move.type, wasACapture); - + if (updateProperties) { + let wasACapture = false; + for (const c of move.changes) { + if (c.action === 'capturePiece') { + wasACapture = true; + break; + } + } + incrementMoveRule(gamefile, move.type, wasACapture); + } // ALWAYS DO THIS NOW, no matter what. updateInCheck(gamefile); } @@ -106,20 +116,14 @@ function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { boardchanges.queueSetSpecialRights(move.changes, key, gamefile.specialRights[key], undefined); // We also delete the captured pieces specialRights for ANY move. } -// RETURNS index of captured piece! Required for undo'ing moves. - /** * Standardly moves a piece. Deletes any captured piece. Animates if specified. * If the move is a special move, a separate method is needed. * @param {gamefile} gamefile - The gamefile * @param {Piece} piece - The piece to move * @param {Move} move - The move that's being made - * @param {Object} options - An object containing various options (ALL of these are default *true*): - * - `updateData`: Whether to modify the mesh of all the pieces. Should be false for simulated moves, or if you're planning on regenerating the mesh after this. - * - `animate`: Whether to animate this move. - * - `simulated`: Whether you plan on undo'ing this move. If true, the index of the captured piece within the gamefile's piece list will be stored in the `rewindInfo` property of the move for easy undo'ing without screwing up the mesh. - */ -function movePiece_NoSpecial(gamefile, piece, move) { // piece: { coords, type, index } +*/ +function movePiece_NoSpecial(gamefile, piece, move) { const capturedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); @@ -148,11 +152,8 @@ function incrementMoveRule(gamefile, typeMoved, wasACapture) { } /** - * Flips the `whosTurn` property of the gamefile, updates - * the text on-screen, then pushes the clock. + * Flips the `whosTurn` property of the gamefile. * @param {gamefile} gamefile - The gamefile - * @param {Object} options - An object that may contain the options (all are default *true*): - * - `pushClock`: Whether to push the clock. */ function updateTurn(gamefile) { gamefile.whosTurn = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); @@ -160,11 +161,10 @@ function updateTurn(gamefile) { /** * Updates the `inCheck` and `attackers` properties of the gamefile after making a move or rewinding. - - * Needs to be called AFTER flipping the `whosTurn` property. - * @param {gamefile} gamefile - The gamefile - * @param {boolean} [flagMoveAsCheck] - If *true*, flags the last played move as a check. Default: true - */ +* Needs to be called AFTER flipping the `whosTurn` property. +* @param {gamefile} gamefile - The gamefile +* @param {boolean} [flagMoveAsCheck] - If *true*, flags the last played move as a check. Default: true +*/ function updateInCheck(gamefile) { let attackers = undefined; @@ -265,11 +265,7 @@ function calculateMoveFromShortmove(gamefile, shortmove) { return move; } -/** - * - * @param {gamefile} gamefile - * @param {number} targetIndex - */ +// TODO: doc function forEachMove(gamefile, targetIndex, callback) { if (targetIndex === gamefile.moveIndex) return; @@ -296,6 +292,7 @@ function forEachMove(gamefile, targetIndex, callback) { } } +// TODO: doc function gotoMove(gamefile, index, callback) { forEachMove(gamefile, index, callback); gamefile.moveIndex = index; @@ -329,30 +326,17 @@ function rewindMove(gamefile, { removeMove = true } = {} ) { // Finally, delete the move off the top of our moves [] array list if (removeMove) moveutil.deleteLastMove(gamefile.moves); gamefile.moveIndex--; + updateTurn(gamefile); } -/** - * Simulates the provided move, testing if it's in check and, if specified, also the game conclusion, - * then undo's the move, restoring it to how the gamefile was before. - * @param {gamefile} gamefile - The gamefile - * @param {Move} The move to simulate. - * @param {string} colorToTestInCheck - The side to test if they are in check. Usually this is the color of the side making the move, because we don't want to step into check. - * @param {Object} options - An object that may contain the properties: - * - `doGameOverChecks`: Whether, while simulating this move, to perform game over checks such as checkmate or other win conditions. SLOWER, but this can be used to verify the game conclusion the opponent claimed. Default: *false* - * @returns {Object} An object that may contains the properties: - * - `isCheck`: Whether making this move puts the specified color in check. Usually it's you stepping into check. - * - `gameConclusion`: The resulting `gameConclusion` after the move, if `doGameOverChecks` was specified as *true*. - */ -function simulateMove(gamefile, move, colorToTestInCheck, { doGameOverChecks = false } = {}) { +// TODO: doc +function simulateMoveWrapper(gamefile, move, callback, {updateProperties = true} = {}) { // Moves the piece without unselecting it or regenerating the pieces model. generateMove(gamefile, move); - makeMove(gamefile, move, { updateProperties: doGameOverChecks }); + makeMove(gamefile, move, { updateProperties: updateProperties }); // What info can we pull from the game after simulating this move? - const info = { - isCheck: doGameOverChecks ? gamefile.inCheck : checkdetection.detectCheck(gamefile, colorToTestInCheck, []), - gameConclusion: doGameOverChecks ? wincondition.getGameConclusion(gamefile) : undefined - }; + const info = callback(); // Undo the move, REWIND. // We don't have to worry about the index changing, it is the same. @@ -360,7 +344,26 @@ function simulateMove(gamefile, move, colorToTestInCheck, { doGameOverChecks = f // Only remove the move rewindMove(gamefile, true); - return info; // Info from simulating the move: { isCheck, gameConclusion } + return info; +} + +// TODO: doc +function getSimulatedCheck(gamefile, move, colorToTestInCheck) { + return simulateMoveWrapper( + gamefile, + move, + () => checkdetection.detectCheck(gamefile, colorToTestInCheck, []), + {updateProperties: false} + ); +} + +// TODO: doc +function getSimulatedConclusion(gamefile, move) { + return simulateMoveWrapper( + gamefile, + move, + () => wincondition.getGameConclusion(gamefile) + ); } export default { @@ -373,5 +376,7 @@ export default { calculateMoveFromShortmove, applyMove, rewindMove, - simulateMove, + simulateMoveWrapper, + getSimulatedCheck, + getSimulatedConclusion, }; \ No newline at end of file diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index b14c6f7ae..10ec54ea7 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -29,13 +29,12 @@ import type gamefile from "../../chess/logic/gamefile.js"; // @ts-ignore import type { Move } from "../../chess/util/moveutil.js"; +// TODO: doc function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, concludeGameIfOver = true} = {}) { - movepiece.generateMove(gamefile, move); movepiece.makeMove(gamefile, move); boardchanges.runMove(gamefile, move, meshChanges, true); - movepiece.updateTurn(gamefile); guigameinfo.updateWhosTurn(gamefile); if (!onlinegame.areInOnlineGame()) { @@ -55,6 +54,7 @@ function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, con arrows.clearListOfHoveredPieces(); } +// TODO: doc function animateMove(move: Move, forward = true) { const funcs = forward ? animatableChanges.forward : animatableChanges.backward; let clearanimations = true; @@ -65,6 +65,7 @@ function animateMove(move: Move, forward = true) { } } +// TODO: doc function rewindMove(gamefile: gamefile) { boardchanges.runMove(gamefile, gamefile.moves[gamefile.moveIndex], meshChanges, false); movepiece.rewindMove(gamefile); @@ -72,17 +73,20 @@ function rewindMove(gamefile: gamefile) { frametracker.onVisualChange(); } +// TODO: doc function viewFront(gamefile: gamefile) { movepiece.gotoMove(gamefile, gamefile.moves.length - 1, (m: Move) => viewMove(gamefile, m, true)); guinavigation.update_MoveButtons(); stats.showMoves(); } +// TODO: doc function viewMove(gamefile: gamefile, move: Move, forward = true) { boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); boardchanges.runMove(gamefile, move, meshChanges, forward); } +// TODO: doc function viewIndex(gamefile: gamefile, index: number) { movepiece.gotoMove(gamefile, index, (m: Move) => viewMove(gamefile, m, index >= gamefile.moveIndex)); guinavigation.update_MoveButtons(); From f350370065c1f5750f60085811eeed69f85dc5f7 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Sun, 22 Dec 2024 13:36:42 +0000 Subject: [PATCH 22/44] fix changes not being applied in reverse when moving backwards --- src/client/scripts/esm/chess/logic/boardchanges.ts | 3 ++- src/client/scripts/esm/chess/logic/checkdetection.js | 2 -- src/client/scripts/esm/chess/logic/movepiece.js | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 52a49b51e..ac7cb462a 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -105,7 +105,8 @@ function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionL // TODO: doc function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { const funcs = forward ? changeFuncs.forward : changeFuncs.backward; - applyChanges(gamefile, move.changes, funcs); + const changes = forward ? move.changes : [...move.changes].reverse(); + applyChanges(gamefile, changes, funcs); } // TODO: doc diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index d9d8e085d..898b980c9 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -240,8 +240,6 @@ function removeMovesThatPutYouInCheck(gamefile, moves, pieceSelected, color) { / // 2. Individual moves. We can iterate through these and use detectCheck() to test them. removeIndividualMovesThatPutYouInCheck(gamefile, moves.individual, pieceSelected, color); - - console.log(moves); } // Time complexity O(1) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index a2350afc9..0165c2ea6 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -275,13 +275,9 @@ function forEachMove(gamefile, targetIndex, callback) { if (gamefile.moves.length <= targetIndex || targetIndex < 0) throw new Error("Target index is outside of the movelist!"); - console.trace(gamefile.moves.length - 1, targetIndex); - while (i !== targetIndex) { i = math.moveTowards(i, targetIndex, 1); const move = gamefile.moves[i]; - - console.log(move.compact, forwards); if (move === undefined) { console.log(`Undefined! ${i}, ${targetIndex}`); From a58e5d6170a8056a361775cf7e451097e2ff2647 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:11:56 +0000 Subject: [PATCH 23/44] Add state changes and document code --- src/client/scripts/cjs/game/htmlscript.js | 2 +- src/client/scripts/cjs/views/createaccount.js | 52 ++--- .../scripts/esm/chess/logic/boardchanges.ts | 127 +++++----- .../scripts/esm/chess/logic/checkdetection.js | 2 +- .../scripts/esm/chess/logic/checkmate.js | 1 - .../scripts/esm/chess/logic/gamefile.js | 2 +- .../esm/chess/logic/insufficientmaterial.js | 12 +- .../scripts/esm/chess/logic/movepiece.js | 177 ++++++++------ .../scripts/esm/chess/logic/organizedlines.js | 26 +-- .../scripts/esm/chess/logic/specialdetect.js | 2 +- .../scripts/esm/chess/logic/specialmove.js | 7 +- src/client/scripts/esm/chess/logic/state.ts | 221 ++++++++++++++++++ .../chess/rendering/spritesheetGenerator.ts | 2 +- src/client/scripts/esm/chess/util/moveutil.js | 42 ++-- .../scripts/esm/chess/variants/gamerules.js | 1 - .../scripts/esm/components/header/settings.js | 3 +- .../esm/game/chess/graphicalchanges.ts | 2 +- .../scripts/esm/game/chess/movesequence.ts | 73 ++++-- src/client/scripts/esm/game/gui/guiclock.js | 15 +- src/client/scripts/esm/game/gui/guiguide.js | 2 - .../scripts/esm/game/gui/guinavigation.js | 20 +- src/client/scripts/esm/game/gui/guititle.js | 2 - src/client/scripts/esm/game/gui/stats.js | 1 - src/client/scripts/esm/game/gui/statustext.js | 1 - src/client/scripts/esm/game/misc/invites.js | 11 - src/client/scripts/esm/game/misc/sound.js | 8 +- .../scripts/esm/game/rendering/arrows.js | 8 +- .../scripts/esm/game/rendering/buffermodel.js | 4 +- .../scripts/esm/game/rendering/camera.js | 4 +- .../scripts/esm/game/rendering/movement.js | 17 +- .../scripts/esm/game/rendering/transition.js | 2 + src/client/scripts/esm/util/localstorage.js | 4 +- src/client/scripts/esm/util/thread.js | 26 --- src/server/api/AdminPanel.ts | 17 +- src/server/api/GitHub.js | 113 ++++++++- src/server/config/env.js | 2 + src/server/game/gamemanager/gamemanager.js | 4 +- src/server/game/statlogger.js | 16 +- src/server/middleware/middleware.js | 6 + src/server/utility/HTMLScriptInjector.js | 11 - src/server/utility/lockFile.js | 6 +- tsconfig.json | 2 +- 42 files changed, 684 insertions(+), 372 deletions(-) create mode 100644 src/client/scripts/esm/chess/logic/state.ts delete mode 100644 src/client/scripts/esm/util/thread.js diff --git a/src/client/scripts/cjs/game/htmlscript.js b/src/client/scripts/cjs/game/htmlscript.js index 17181e030..962d2c34b 100644 --- a/src/client/scripts/cjs/game/htmlscript.js +++ b/src/client/scripts/cjs/game/htmlscript.js @@ -1,7 +1,7 @@ 'use strict'; -/* global sound */ +/* global main sound */ /** * The server injects this script directly into the html document diff --git a/src/client/scripts/cjs/views/createaccount.js b/src/client/scripts/cjs/views/createaccount.js index 2022a866a..de2cee09e 100644 --- a/src/client/scripts/cjs/views/createaccount.js +++ b/src/client/scripts/cjs/views/createaccount.js @@ -55,31 +55,31 @@ element_usernameInput.addEventListener('input', () => { // When username field c updateSubmitButton(); }); element_usernameInput.addEventListener('focusout', () => { - // Check username availability... - if (element_usernameInput.value.length === 0 || usernameHasError) return; - - fetch(`/createaccount/username/${element_usernameInput.value}`, fetchOptions) - .then((response) => response.json()) - .then((result) => { - // { allowed, reason } - // We've got the result back from the server, - // Is this username available to use? - if (result.allowed === true) return; // Not in use - - // ERROR! In use! - usernameHasError = true; - createErrorElement('usernameerror', "usernameinputline"); - // Change input box to red outline - element_usernameInput.style.outline = 'solid 1px red'; - // Reset variable because it now exists. - const usernameError = document.getElementById("usernameerror"); - - // translate the message from the server if a translation is available - let result_message = result.reason; - if (translations[result_message]) result_message = translations[result_message]; - usernameError.textContent = result_message; - updateSubmitButton(); - }); + // Check username availability... + if (element_usernameInput.value.length === 0 || usernameHasError) return; + + fetch(`/createaccount/username/${element_usernameInput.value}`, fetchOptions) + .then((response) => response.json()) + .then((result) => { + // { allowed, reason } + // We've got the result back from the server, + // Is this username available to use? + if (result.allowed === true) return; // Not in use + + // ERROR! In use! + usernameHasError = true; + createErrorElement('usernameerror', "usernameinputline"); + // Change input box to red outline + element_usernameInput.style.outline = 'solid 1px red'; + // Reset variable because it now exists. + const usernameError = document.getElementById("usernameerror"); + + // translate the message from the server if a translation is available + let result_message = result.reason; + if (translations[result_message]) result_message = translations[result_message]; + usernameError.textContent = result_message; + updateSubmitButton(); + }); }); @@ -255,7 +255,7 @@ function validEmail(string) { function validPassword(string) { - const regex = /^[a-zA-Z0-9!@#$%^&*\?]+$/; + const regex = /^[a-zA-Z0-9!@#$%^&*?]+$/; if (regex.test(string) === true) return true; return false; diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index ac7cb462a..5a59981c2 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -18,7 +18,7 @@ interface Piece { } interface Change { - action: string + action: string, [changeData: string]: any } @@ -42,8 +42,6 @@ const changeFuncs: ChangeApplication = { "delete": deletePiece, "movePiece": movePiece, "capturePiece": capturePiece, - "setRights": setRights, - "setPassant": setPassant, }, backward: { @@ -51,50 +49,37 @@ const changeFuncs: ChangeApplication = { "add": deletePiece, "movePiece": returnPiece, "capturePiece": uncapturePiece, - "setRights": revertRights, - "setPassant": revertPassant, } }; -// TODO: doc +// All queue functions queue a change to the board. +// They add to a changelist which is then executed using a set of changefuncs function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); // Need to differentiate this from move so animations can work return changes; } -// TODO: doc function queueAddPiece(changes: Array, piece: Piece) { changes.push({action: 'add', piece: piece}); return changes; }; -// TODO: doc function queueDeletePiece(changes: Array, piece: Piece) { changes.push({action: 'delete', piece: piece}); return changes; } -// TODO: doc function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); return changes; } -// TODO: doc -function queueSetSpecialRights(changes: Array, coords: string, curRights: any, rights: any) { - if (curRights === rights) return changes; // Nothing has changed - changes.push({action: "setRights", coords: coords, curRights: curRights, rights: rights}); - return changes; -} - -// TODO: doc -function queueSetEnPassant(changes: Array, curPassant: any, newPassant: any) { - if (curPassant === newPassant) return changes; // Nothing has changed - changes.push({action: "setPassant", curPassant: curPassant, newPassant: newPassant}); - return changes; -} - -// TODO: doc +/** + * Apply changes in changelist according to changefuncs + * @param gamefile the gamefile + * @param changes the changes to apply + * @param funcs the object contain change funcs + */ function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { for (const c of changes) { if (!(c.action in funcs)) continue; @@ -102,27 +87,40 @@ function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionL } } -// TODO: doc +/** + * + * @param gamefile + * @param move + * @param changeFuncs + * @param forward + */ function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { const funcs = forward ? changeFuncs.forward : changeFuncs.backward; const changes = forward ? move.changes : [...move.changes].reverse(); applyChanges(gamefile, changes, funcs); } -// TODO: doc +/** + * Most basic add-a-piece method. Adds it the gamefile's piece list, + * organizes the piece in the organized lists + * @param gamefile + * @param change the add data + * change.piece is the piece to add + * the pieces index is optional and will get assigned one if none are present + */ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional const piece = change['piece']; const list = gamefile.ourPieces[piece.type]; // If no index specified, make the default the first undefined in the list! - if (piece.index == null) change['piece'].index = list.undefineds[0]; + if (piece.index === undefined) change['piece'].index = list.undefineds[0]; - if (piece.index == null) { + if (piece.index === undefined) { list.push(piece.coords); } else { // desiredIndex specified - const isPieceAtCoords = gamefileutility.getPieceTypeAtCoords(gamefile, piece.coords) != null; + const isPieceAtCoords = gamefileutility.getPieceTypeAtCoords(gamefile, piece.coords) !== undefined; if (isPieceAtCoords) throw new Error("Can't add a piece on top of another piece!"); // Remove the undefined from the undefineds list @@ -135,7 +133,12 @@ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -// TODO: doc +/** + * Most basic delete-a-piece method. Deletes it from the gamefile's piece list, + * from the organized lists. + * @param gamefile + * @param change + */ function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, index } const piece = change['piece']; @@ -146,7 +149,16 @@ function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, ind organizedlines.removeOrganizedPiece(gamefile, piece.coords); } -// TODO: doc + +/** + * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, + * reorganizes the piece in the organized lists, and updates its mesh data. + * @param gamefile - The gamefile + * @param change - the move data + * change.piece - the piece to move + * the piece coords is the start coords + * change,endCoords - the coords the piece is moved to + */ function movePiece(gamefile: gamefile, change: Change) { const piece = change['piece']; const endCoords = change['endCoords']; @@ -161,7 +173,11 @@ function movePiece(gamefile: gamefile, change: Change) { organizedlines.organizePiece(piece.type, endCoords, gamefile); } -// TODO: doc +/** + * Reverses `movePiece` + * @param gamefile + * @param change + */ function returnPiece(gamefile: gamefile, change: Change) { const piece = change['piece']; const endCoords = change['endCoords']; @@ -176,48 +192,21 @@ function returnPiece(gamefile: gamefile, change: Change) { organizedlines.organizePiece(piece.type, piece.coords, gamefile); } -// TODO: doc +/** + * Captures a piece + * This is differentiated from move changes so it can be animated + * @param gamefile + * @param change + */ function capturePiece(gamefile: gamefile, change: Change) { - deletePiece(gamefile, {piece: change['capturedPiece'], action: ""}); + deletePiece(gamefile, {piece: change['capturedPiece'], action: "add"}); movePiece(gamefile, change); } -// TODO: doc +// Undoes a capture function uncapturePiece(gamefile: gamefile, change: Change) { returnPiece(gamefile, change); - addPiece(gamefile, {piece: change['capturedPiece'], action:""}); -} - -// TODO: doc -function setRights(gamefile: gamefile, change: Change) { - if (change['rights'] === undefined) { - delete gamefile.specialRights[change['coords']]; - } else { - gamefile.specialRights[change['coords']] = change['rights']; - } -} - -// TODO: doc -function revertRights(gamefile: gamefile, change: Change) { - if (change['curRights'] === undefined) { - delete gamefile.specialRights[change['coords']]; - } else { - gamefile.specialRights[change['coords']] = change['curRights']; - } -} - -// TODO: doc -function setPassant(gamefile: gamefile, change: Change) { - gamefile.enpassant = change['newPassant']; -} - -// TODO: doc -function revertPassant(gamefile: gamefile, change: Change) { - if (change['curPassant'] === undefined) { - delete gamefile.enpassant; - } else { - gamefile.enpassant = change['curPassant']; - } + addPiece(gamefile, {piece: change['capturedPiece'], action:"add"}); } export type { @@ -231,8 +220,6 @@ export default { queueDeletePiece, queueMovePiece, queueCaputure, - queueSetSpecialRights, - queueSetEnPassant, runMove, applyChanges, diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index 898b980c9..dc437c80c 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -42,7 +42,7 @@ function detectCheck(gamefile, color, attackers) { // Input validation if (!gamefile) throw new Error("Cannot detect check of an undefined game!"); if (color !== 'white' && color !== 'black') throw new Error(`Cannot detect check of the team of color ${color}!`); - if (attackers != null && attackers.length !== 0) throw new Error(`Attackers parameter must be an empty array []! Received: ${JSON.stringify(attackers)}`); + if (attackers !== undefined && attackers.length !== 0) throw new Error(`Attackers parameter must be an empty array []! Received: ${JSON.stringify(attackers)}`); // Coordinates of ALL royals of this color! const royalCoords = gamefileutility.getRoyalCoordsOfColor(gamefile, color); // [ coords1, coords2 ] diff --git a/src/client/scripts/esm/chess/logic/checkmate.js b/src/client/scripts/esm/chess/logic/checkmate.js index 8d6b10cda..5c74b1522 100644 --- a/src/client/scripts/esm/chess/logic/checkmate.js +++ b/src/client/scripts/esm/chess/logic/checkmate.js @@ -4,7 +4,6 @@ import gamefileutility from '../util/gamefileutility.js'; import moveutil from '../util/moveutil.js'; import legalmoves from './legalmoves.js'; import typeutil from '../util/typeutil.js'; -import colorutil from '../util/colorutil.js'; // Import End /** diff --git a/src/client/scripts/esm/chess/logic/gamefile.js b/src/client/scripts/esm/chess/logic/gamefile.js index 7e586f9c7..d2dc929cd 100644 --- a/src/client/scripts/esm/chess/logic/gamefile.js +++ b/src/client/scripts/esm/chess/logic/gamefile.js @@ -255,7 +255,7 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion, clockV // Do we need to convert any checkmate win conditions to royalcapture? if (!wincondition.isCheckmateCompatibleWithGame(this)) gamerules.swapCheckmateForRoyalCapture(this.gameRules); - organizedlines.initOrganizedPieceLists(this, { appendUndefineds: false }); + organizedlines.initOrganizedPieceLists(this); // THIS HAS TO BE AFTER gamerules.swapCheckmateForRoyalCapture() AS THIS DOES GAME-OVER CHECKS!!! movepiece.makeAllMovesInGame(this, moves); /** The game's conclusion, if it is over. For example, `'white checkmate'` diff --git a/src/client/scripts/esm/chess/logic/insufficientmaterial.js b/src/client/scripts/esm/chess/logic/insufficientmaterial.js index 7a13527a4..0d2dc1722 100644 --- a/src/client/scripts/esm/chess/logic/insufficientmaterial.js +++ b/src/client/scripts/esm/chess/logic/insufficientmaterial.js @@ -133,7 +133,7 @@ function has_more_pieces(a, b) { * @returns {number} sum of tuple entries */ function sum_tuple_coords(tuple) { - return tuple[0] + tuple [1]; + return tuple[0] + tuple[1]; } /** @@ -153,7 +153,7 @@ function ordered_tuple_descending(tuple) { function detectInsufficientMaterial(gamefile) { // Only make the draw check if the win condition is checkmate for both players if (!gamerules.doesColorHaveWinCondition(gamefile.gameRules, 'white', 'checkmate') || !gamerules.doesColorHaveWinCondition(gamefile.gameRules, 'black', 'checkmate')) return false; - if (gamerules.getWinConditionCountOfColor(gamefile.gameRules, 'white') != 1 || gamerules.getWinConditionCountOfColor(gamefile.gameRules, 'black') != 1) return false; + if (gamerules.getWinConditionCountOfColor(gamefile.gameRules, 'white') !== 1 || gamerules.getWinConditionCountOfColor(gamefile.gameRules, 'black') !== 1) return false; // Only make the draw check if the last move was a capture or if there is no last move const lastMove = moveutil.getLastMove(gamefile.moves); @@ -181,8 +181,8 @@ function detectInsufficientMaterial(gamefile) { } // add bishop tuples to scenario, and make sure the first entry of the bishop lists is the largest one - if (sum_tuple_coords(bishopsW_count) != 0) scenario.bishopsW = ordered_tuple_descending(bishopsW_count); - if (sum_tuple_coords(bishopsB_count) != 0) scenario.bishopsB = ordered_tuple_descending(bishopsB_count); + if (sum_tuple_coords(bishopsW_count) !== 0) scenario.bishopsW = ordered_tuple_descending(bishopsW_count); + if (sum_tuple_coords(bishopsB_count) !== 0) scenario.bishopsB = ordered_tuple_descending(bishopsB_count); // Temporary: Short-circuit insuffmat check if a player has a pawn that he can promote // This is fully enough for the checkmate practice mode, for now @@ -190,8 +190,8 @@ function detectInsufficientMaterial(gamefile) { if (gamefile.gameRules.promotionRanks) { const promotionListWhite = gamefile.gameRules.promotionsAllowed.white; const promotionListBlack = gamefile.gameRules.promotionsAllowed.black; - if ("pawnsW" in scenario && promotionListWhite.length != 0) return false; - if ("pawnsB" in scenario && promotionListBlack.length != 0) return false; + if ("pawnsW" in scenario && promotionListWhite.length !== 0) return false; + if ("pawnsB" in scenario && promotionListBlack.length !== 0) return false; } // Create scenario object with inverted colors diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 0165c2ea6..fda236832 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -4,13 +4,12 @@ import legalmoves from './legalmoves.js'; import gamefileutility from '../util/gamefileutility.js'; import specialdetect from './specialdetect.js'; import boardchanges from './boardchanges.js'; -import clock from './clock.js'; +import state from './state.js'; import math from '../../util/math.js'; import moveutil from '../util/moveutil.js'; import checkdetection from './checkdetection.js'; import formatconverter from './formatconverter.js'; import colorutil from '../util/colorutil.js'; -import jsutil from '../../util/jsutil.js'; import coordutil from '../util/coordutil.js'; import wincondition from './wincondition.js'; // Import End @@ -29,16 +28,20 @@ import wincondition from './wincondition.js'; /** Here lies the universal methods for moving pieces, forward or rewinding. */ -// TODO: doc +/** + * Generates all move data needed before move execution + * @param {gamefile} gamefile + * @param {Move} move + */ function generateMove(gamefile, move) { move.changes = []; + move.generateIndex = gamefile.moveIndex + 1; + state.initMoveStates(move); const piece = gamefileutility.getPieceAtCoords(gamefile, move.startCoords); if (!piece) throw new Error(`Cannot make move because no piece exists at coords ${move.startCoords}.`); move.type = piece.type; const trimmedType = colorutil.trimColorExtensionFromType(move.type); // "queens" - - storeRewindInfoOnMove(gamefile, move, piece.index); // Keep track if important stuff to remember, for rewinding the game if we undo moves // Do this before making the move, so that if its a pawn double push, enpassant can be reinstated and not deleted. deleteEnpassantAndSpecialRightsProperties(gamefile, move); @@ -46,60 +49,54 @@ function generateMove(gamefile, move) { let specialMoveMade; if (gamefile.specialMoves[trimmedType]) specialMoveMade = gamefile.specialMoves[trimmedType](gamefile, piece, move); if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move); // Move piece regularly (no special tag) + + let wasACapture = false; + for (const c of move.changes) { + if (c.action === 'capturePiece') { + wasACapture = true; + break; + } + } + + incrementMoveRule(gamefile, move, wasACapture); + + state.collectState(move); } -// TODO: doc +/** + * Changes the board with move.changes + * Does not apply any graphical effects + * @param {*} gamefile + * @param {*} move + * @param {*} forward + */ function applyMove(gamefile, move, forward = true) { boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); } -// TODO: doc -function makeMove(gamefile, move, { updateProperties = true } = {}) { +/** + * **Universal** function for executing forward (not rewinding) moves. + * Called when we move the selected piece, receive our opponent's move, + * or need to simulate a move within the checkmate algorithm. + * @param {gamefile} gamefile + * @param {Move} move + */ +function makeMove(gamefile, move) { applyMove(gamefile, move); + state.applyMove(gamefile, move, true, {globalChange: true}); gamefile.moveIndex++; + if (gamefile.moveIndex !== move.generateIndex) console.warn(`Move was expected at index ${move.generateIndex} but applied at ${gamefile.moveIndex}, this is unintended and may cause some issues!`); gamefile.moves.push(move); updateTurn(gamefile); + // The "check" property will be added inside updateInCheck()... // The "mate" property will be added inside our game conclusion checks... - if (updateProperties) { - let wasACapture = false; - for (const c of move.changes) { - if (c.action === 'capturePiece') { - wasACapture = true; - break; - } - } - incrementMoveRule(gamefile, move.type, wasACapture); - } - // ALWAYS DO THIS NOW, no matter what. - updateInCheck(gamefile); -} - -/** - * Stores crucial game information for rewinding this move on the move object. - * Upon rewinding, this information will be deleted. - * @param {gamefile} gamefile - The gamefile - * @param {Move} move - The move - * @param {Object} options - An object that may contain the following options: - * - `simulated`: Whether you plan on undo'ing this move. If *true*, then `capturedIndex` and `pawnIndex` will also be remembered, so the mesh doesn't get screwed up when rewinding. Default: *false* - */ -function storeRewindInfoOnMove(gamefile, move, pieceIndex, { simulated = false } = {}) { - const rewindInfoAlreadyPresent = move.rewindInfo != null; - const rewindInfo = move.rewindInfo || {}; - - if (simulated && move.promotion) rewindInfo.pawnIndex = pieceIndex; // `capturedIndex` is saved elsewhere within movePiece_NoSpecial() - if (!rewindInfoAlreadyPresent) { - rewindInfo.inCheck = jsutil.deepCopyObject(gamefile.inCheck); - rewindInfo.gameConclusion = gamefile.gameConclusion; - if (gamefile.attackers) rewindInfo.attackers = jsutil.deepCopyObject(gamefile.attackers); - if (gamefile.moveRuleState !== undefined) rewindInfo.moveRuleState = gamefile.moveRuleState; - if (gamefile.checksGiven) rewindInfo.checksGiven = gamefile.checksGiven; - } - - move.rewindInfo = rewindInfo; + // ALWAYS DO THIS NOW, no matter what. + createCheckState(gamefile, move); + if (gamefile.inCheck) moveutil.flagLastMoveAsCheck(gamefile); } /** @@ -109,11 +106,11 @@ function storeRewindInfoOnMove(gamefile, move, pieceIndex, { simulated = false } * @param {Move} move */ function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { - boardchanges.queueSetEnPassant(move.changes, gamefile.enpassant, undefined); + state.queueSetState(move.changes, "enpassant", gamefile.enpassant, undefined); let key = coordutil.getKeyFromCoords(move.startCoords); - boardchanges.queueSetSpecialRights(move.changes, key, gamefile.specialRights[key], undefined); + state.queueSetState(move.changes, `specialRights.${key}`, gamefile.specialRights[key], undefined); key = coordutil.getKeyFromCoords(move.endCoords); - boardchanges.queueSetSpecialRights(move.changes, key, gamefile.specialRights[key], undefined); // We also delete the captured pieces specialRights for ANY move. + state.queueSetState(move.changes, `specialRights.${key}`, gamefile.specialRights[key], undefined); // We also delete the captured pieces specialRights for ANY move. } /** @@ -140,15 +137,15 @@ function movePiece_NoSpecial(gamefile, piece, move) { /** * Increments the gamefile's moveRuleStatus property, if the move-rule is in use. * @param {gamefile} gamefile - The gamefile - * @param {string} typeMoved - The type of piece moved + * @param {Move} move - The move * @param {boolean} wasACapture Whether the move made a capture */ -function incrementMoveRule(gamefile, typeMoved, wasACapture) { +function incrementMoveRule(gamefile, move, wasACapture) { if (!gamefile.gameRules.moveRule) return; // Not using the move-rule // Reset if it was a capture or pawn movement - if (wasACapture || typeMoved.startsWith('pawns')) gamefile.moveRuleState = 0; - else gamefile.moveRuleState++; + const newMoveRule = (wasACapture || move.type.startsWith('pawns')) ? 0 : gamefile.moveRuleState + 1; + state.queueSetState(move.changes, 'moveRuleState', gamefile.moveRuleState, newMoveRule, {global: true}); } /** @@ -159,14 +156,28 @@ function updateTurn(gamefile) { gamefile.whosTurn = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); } +function createCheckState(gamefile, move) { + let attackers = undefined; + // Only pass in attackers array to be filled by the checking pieces if we're using checkmate win condition. + const whosTurnItWasAtMoveIndex = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); + const oppositeColor = colorutil.getOppositeColor(whosTurnItWasAtMoveIndex); + if (gamefile.gameRules.winConditions[oppositeColor].includes('checkmate')) attackers = []; + + state.setState( + gamefile, + move, + "inCheck", + checkdetection.detectCheck(gamefile, whosTurnItWasAtMoveIndex, attackers) + ); // Passes in the gamefile as an argument + state.setState(gamefile, move, "attackers", attackers || []); // Erase the checking pieces calculated from previous turn and pass in new on +} + /** * Updates the `inCheck` and `attackers` properties of the gamefile after making a move or rewinding. * Needs to be called AFTER flipping the `whosTurn` property. * @param {gamefile} gamefile - The gamefile -* @param {boolean} [flagMoveAsCheck] - If *true*, flags the last played move as a check. Default: true */ function updateInCheck(gamefile) { - let attackers = undefined; // Only pass in attackers array to be filled by the checking pieces if we're using checkmate win condition. const whosTurnItWasAtMoveIndex = moveutil.getWhosTurnAtMoveIndex(gamefile, gamefile.moveIndex); @@ -175,8 +186,6 @@ function updateInCheck(gamefile) { gamefile.inCheck = checkdetection.detectCheck(gamefile, whosTurnItWasAtMoveIndex, attackers); // Passes in the gamefile as an argument gamefile.attackers = attackers || []; // Erase the checking pieces calculated from previous turn and pass in new ones! - - if (gamefile.inCheck && gamefile.moveIndex !== -1) moveutil.flagLastMoveAsCheck(gamefile); } /** @@ -214,6 +223,7 @@ function makeAllMovesInGame(gamefile, moves) { updateTurn(gamefile); updateInCheck(gamefile); + if (gamefile.inCheck && gamefile.moveIndex !== -1) moveutil.flagLastMoveAsCheck(gamefile); gamefileutility.doGameOverChecks(gamefile); // Update the gameConclusion } @@ -265,7 +275,12 @@ function calculateMoveFromShortmove(gamefile, shortmove) { return move; } -// TODO: doc +/** + * Iterates from a certain move to the target index + * @param {gamefile} gamefile + * @param {number} targetIndex + * @param {CallableFunction} callback + */ function forEachMove(gamefile, targetIndex, callback) { if (targetIndex === gamefile.moveIndex) return; @@ -288,7 +303,12 @@ function forEachMove(gamefile, targetIndex, callback) { } } -// TODO: doc +/** + * Iterates to a certain move index. Callable should be a move application function + * @param {gamefile} gamefile + * @param {number} index + * @param {CallableFunction} callback + */ function gotoMove(gamefile, index, callback) { forEachMove(gamefile, index, callback); gamefile.moveIndex = index; @@ -304,32 +324,30 @@ function gotoMove(gamefile, index, callback) { * - `removeMove`: Whether to delete the move from the gamefile's move list. Should be true if we're undo'ing simulated moves. * - `animate`: Whether to animate this rewinding. */ -function rewindMove(gamefile, { removeMove = true } = {} ) { +function rewindMove(gamefile) { const move = moveutil.getMoveFromIndex(gamefile.moves, gamefile.moveIndex); // { type, startCoords, endCoords, captured } boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, false); - - // inCheck and attackers are always restored, no matter if we're deleting the move or not. - gamefile.inCheck = move.rewindInfo.inCheck; - if (move.rewindInfo.attackers) gamefile.attackers = move.rewindInfo.attackers; - if (removeMove) { // Restore original values - gamefile.moveRuleState = move.rewindInfo.moveRuleState; - gamefile.checksGiven = move.rewindInfo.checksGiven; - gamefile.gameConclusion = move.rewindInfo.gameConclusion; // Simulated moves may or may not have performed game over checks. - } + state.applyMove(gamefile, move, false, {globalChange: true}); // Finally, delete the move off the top of our moves [] array list - if (removeMove) moveutil.deleteLastMove(gamefile.moves); + moveutil.deleteLastMove(gamefile.moves); gamefile.moveIndex--; updateTurn(gamefile); } -// TODO: doc -function simulateMoveWrapper(gamefile, move, callback, {updateProperties = true} = {}) { +/** + * Wraps a function in a simulated move + * @param {gamefile} gamefile + * @param {Move} move + * @param {CallableFunction} callback + * @returns whatever is returned by the callback + */ +function simulateMoveWrapper(gamefile, move, callback) { // Moves the piece without unselecting it or regenerating the pieces model. generateMove(gamefile, move); - makeMove(gamefile, move, { updateProperties: updateProperties }); + makeMove(gamefile, move); // What info can we pull from the game after simulating this move? const info = callback(); @@ -343,17 +361,27 @@ function simulateMoveWrapper(gamefile, move, callback, {updateProperties = true} return info; } -// TODO: doc +/** + * Simulates a move to get the check + * @param {gamefile} gamefile + * @param {Move} move + * @param {*} colorToTestInCheck + * @returns + */ function getSimulatedCheck(gamefile, move, colorToTestInCheck) { return simulateMoveWrapper( gamefile, move, () => checkdetection.detectCheck(gamefile, colorToTestInCheck, []), - {updateProperties: false} ); } -// TODO: doc +/** + * Simulates a move to get the gameConclusion + * @param {gamefile} gamefile + * @param {Move} move + * @returns the gameConclusion + */ function getSimulatedConclusion(gamefile, move) { return simulateMoveWrapper( gamefile, @@ -363,6 +391,7 @@ function getSimulatedConclusion(gamefile, move) { } export default { + updateInCheck, generateMove, makeMove, updateTurn, diff --git a/src/client/scripts/esm/chess/logic/organizedlines.js b/src/client/scripts/esm/chess/logic/organizedlines.js index f0d7f7ecb..4843a9ca4 100644 --- a/src/client/scripts/esm/chess/logic/organizedlines.js +++ b/src/client/scripts/esm/chess/logic/organizedlines.js @@ -34,9 +34,8 @@ import coordutil from '../util/coordutil.js'; * if we know the coordinates of a piece, we don't have to iterate * through the entire list of pieces to find its type. * @param {gamefile} gamefile - The gamefile - * @param {Object} [options] - An object that may contain the `appendUndefineds` option. If false, no undefined *null* placeholder pieces will be left for the mesh generation. Defaults to *true*. Set to false if you're planning on regenerating manually. */ -function initOrganizedPieceLists(gamefile, { appendUndefineds = true} = {}) { +function initOrganizedPieceLists(gamefile) { if (!gamefile.ourPieces) return console.error("Cannot init the organized lines before ourPieces is defined."); // console.log("Begin organizing lists...") @@ -49,10 +48,7 @@ function initOrganizedPieceLists(gamefile, { appendUndefineds = true} = {}) { // console.log("Finished organizing lists!") - // Add extra undefined pieces into each type array! initUndefineds(gamefile); - - if (appendUndefineds) appendUndefineds(gamefile); } function resetOrganizedLists(gamefile) { @@ -137,26 +133,6 @@ function initUndefineds(gamefile) { // } } - -/** - * Adds more undefined placeholders, or *null* pieces, into the piece lists, - * to allocate more space in the mesh of all the pieces. - * Only called within `initOrganizedPieceLists()` because this assumes - * each piece list has zero, so it adds the exact same amount to each list. - * These placeholders are used up when pawns promote. - * @param {gamefile} gamefile - The gamefile - */ -function appendUndefineds(gamefile) { - typeutil.forEachPieceType(append); - - function append(listType) { - if (!isTypeATypeWereAppendingUndefineds(gamefile, listType)) return; - - const list = gamefile.ourPieces[listType]; - for (let i = 0; i < pieces.extraUndefineds; i++) insertUndefinedIntoList(list); - } -} - function areWeShortOnUndefineds(gamefile) { let weShort = false; diff --git a/src/client/scripts/esm/chess/logic/specialdetect.js b/src/client/scripts/esm/chess/logic/specialdetect.js index fcc942eea..775194ad6 100644 --- a/src/client/scripts/esm/chess/logic/specialdetect.js +++ b/src/client/scripts/esm/chess/logic/specialdetect.js @@ -338,7 +338,7 @@ function transferSpecialFlags_FromMoveToCoords(move, coords) { */ function transferSpecialFlags_FromCoordsToCoords(srcCoords, destCoords) { for (const special of allSpecials) { - if (srcCoords[special] != null) destCoords[special] = jsutil.deepCopyObject(srcCoords[special]); + if (srcCoords[special] !== undefined) destCoords[special] = jsutil.deepCopyObject(srcCoords[special]); } } diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index 055353fc6..b7105c50f 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -2,6 +2,7 @@ import gamefileutility from '../util/gamefileutility.js'; import boardchanges from './boardchanges.js'; import coordutil from '../util/coordutil.js'; +import state from './state.js'; // Import End "use strict"; @@ -51,7 +52,7 @@ function kings(gamefile, piece, move) { const landSquare = [move.endCoords[0] - specialTag.dir, move.endCoords[1]]; // Delete the rook's special move rights const key = coordutil.getKeyFromCoords(pieceToCastleWith.coords); - boardchanges.queueSetSpecialRights(moveChanges, pieceToCastleWith, gamefile.specialRights[key], undefined); + state.queueSetState(moveChanges, `specialRights.${key}`, gamefile.specialRights[key], undefined); boardchanges.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move @@ -65,7 +66,7 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // If it was a double push, then add the enpassant flag to the gamefile, and remove its special right! if (updateProperties && isPawnMoveADoublePush(piece.coords, move.endCoords)) { - boardchanges.queueSetEnPassant(moveChanges, gamefile.enpassant, getEnPassantSquare(piece.coords, move.endCoords)); + state.queueSetState(moveChanges, "enpassant", gamefile.undefined, getEnPassantSquare(piece.coords, move.endCoords)); } const enpassantTag = move.enpassant; // -1/1 @@ -90,7 +91,7 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // Delete original pawn boardchanges.queueDeletePiece(moveChanges, {type: piece.type, coords: move.endCoords, index: piece.index}); - boardchanges.queueAddPiece(moveChanges, {type: promotionTag, coords: move.endCoords, index: null}); + boardchanges.queueAddPiece(moveChanges, {type: promotionTag, coords: move.endCoords, index: undefined}); } diff --git a/src/client/scripts/esm/chess/logic/state.ts b/src/client/scripts/esm/chess/logic/state.ts new file mode 100644 index 000000000..a0ca0a845 --- /dev/null +++ b/src/client/scripts/esm/chess/logic/state.ts @@ -0,0 +1,221 @@ +import type { Change } from "./boardchanges.js"; +// @ts-ignore +import type { Move } from "../util/moveutil.js"; +// @ts-ignore +import type gamefile from "./gamefile.js"; + +// This is oh so very cursed, however it works + +interface StateChange { + action: "stateChange", + path: string + currentState: any + futureState: any + global: boolean // Whether it can be reverted by viewing moves +} + +interface State { + [path: string]: any +} + +/** + * Sets the state by queueing it in the changelist + * @param gamefile the gamefile + * @param changes the changelist + * @param path the path of variable to change in the format of `a.b.c.d` can be indicies of arrays + * @param futureState what the variable is set to + * @param global // See StateChange.global + */ +function queueSetState(gamefile: gamefile, changes: Array, path: string, futureState: any, global: boolean = false) { + const currentState = getPath(gamefile, path); + if (currentState === futureState) return changes; // Nothing has changed + changes.push({action: "stateChange", path: path, currentState: currentState, futureState: futureState, global: global}); + return changes; +} + +/** + * Initialises state objects in move for generation + * @param move the move + */ +function initMoveStates(move: Move) { + move.state = move.state || {}; + move.state.local = move.state.local || {}; + move.state.local.current = move.state.local.current || {}; + move.state.local.future = move.state.local.future || {}; + + move.state.global = move.state.global || {}; + move.state.global.current = move.state.global.current || {}; + move.state.global.future = move.state.global.future || {}; +} + +/** + * ads the statechanges to the moves state + * @param move + * @param path + * @param currentState + * @param futureState + */ +function addToState(move: Move, path: string, currentState: any, futureState: any, { global = false } = {}) { + const states = global ? move.state.global : move.state.local; + + if (path in states.current && states.future[path] === currentState) { + states.future[path] = futureState; + return; + } + + states.current[path] = currentState; + states.future[path] = futureState; +} + +/** + * Collects every StateChange into move states + * Removes all StateChanges from the change list + * @param move move + */ +function collectState(move: Move) { + const changes = move.changes.filter((c: Change) => {return c.action === "stateChange";}); + + for (const change of changes) { + addToState(move, change.path, change.currentState, change.futureState, {global: change.global}); + } + + move.changes = move.changes.filter((c: Change) => {return c.action !== "stateChange";}); +} + +/** + * Applies a single change to the gamefile + * Traverses the gamefile to assign a s + * It will delete a varable when value is `undefined` + * @param gamefile the gamefile + * @param path the path to the variable + * @param value + */ +function setPath(gamefile: gamefile, path: string, value: any) { + let traversal = gamefile; + const pathSteps = path.split("."); + for (const idx in pathSteps) { + if (Number(idx) !== pathSteps.length - 1) { + if (!(pathSteps[idx]! in traversal)) throw Error("Can't alter state of non-existant gamefile variable"); + traversal = traversal[pathSteps[idx]]; + } else { + if ( value === undefined ) { + delete traversal[pathSteps[idx]]; + return; + } + traversal[pathSteps[idx]] = value; + } + } +} +/** + * traverse along the string path and returns the value present + * @param gamefile the gamefile + * @param path the varaible path + * @returns the value of the variable + */ +function getPath(gamefile: gamefile, path: string): any { + let traversal = gamefile; + const pathSteps = path.split("."); + for (const idx in pathSteps) { + if (Number(idx) !== pathSteps.length - 1) { + if (!(pathSteps[idx]! in traversal)) throw Error("Can't access state of non-existant gamefile variable"); + traversal = traversal[pathSteps[idx]]; + } else { + return traversal[pathSteps[idx]]; + } + } +} + +function applyState(gamefile: gamefile, state: State) { + for (const path in state) { + setPath(gamefile, path, state[path]); + } +} + +/** + * Applies the moves states to the gamefile + * @param gamefile the gamefile + * @param move the move + * @param forward if we need to apply the current or next state + */ +function applyMove(gamefile: gamefile, move: Move, forward: boolean, { globalChange = false } = {}) { + const state = forward ? "future" : "current"; + applyState(gamefile, move.state.local[state]); + if (globalChange) applyState(gamefile, move.state.global[state]); +} + +/** + * Sets the gamefile variable and adds it to the state. + * This is used after the move is generated + * @param gamefile + * @param move + * @param path + * @param value + */ +function setState(gamefile: gamefile, move: Move, path: string, value: any, { global = false } = {}) { + const curState = getPath(gamefile, path); + if (curState === value) return; + setPath(gamefile, path, value); + addToState(move, path, curState, value, {global: global}); +} + +/** + * Merges the previous moves state and the current moves state for this turn + * @param previous previous moves state + * @param current current moves state + * @returns the merged state + */ +function mergeStates(previous: State, current: State, { validate = false } = {}): State { + const newState: State = {}; + for (const key in previous) { + newState[key] = previous[key]!; + } + for (const key in current) { + if ((key in newState)) { + if (validate && (JSON.stringify(newState[key]!) !== JSON.stringify(current[key]!))) { + throw Error("Cannot merge states: states do not match"); + } + continue; + } + newState[key] = current[key]!; + } + return newState; +} + +/** + * Merges two moves states and sets both to the same state + * This can be done to save memory as they both have states for the same turn. + * @param previous + * @param current + */ +function mergeMoveStates(previous: Move, current: Move) { + previous.state.local.future = current.state.local.current = mergeStates(previous.state.local.future, current.state.local.current); + previous.state.global.future = current.state.global.current = mergeStates(previous.state.global.future, current.state.global.current); +} + +function unmergeState(current: State, future: State, forwardLink: boolean = true): State { + const ref = forwardLink ? current : future; + const mergedState = forwardLink ? future : current; + const newState: State = {}; + for (const path in ref) { + if (!(path in mergedState)) continue; + if (JSON.stringify(ref[path]) === JSON.stringify(mergedState[path])) continue; + newState[path] = mergedState[path]; + } + return newState; +} + +function unmergeMoveStates(move: Move, forwardLink: boolean = true) { + const mergedState = forwardLink ? "future" : "current"; + move.state.global[mergedState] = unmergeState(move.state.global.future, move.state.global.current, forwardLink); + move.state.local[mergedState] = unmergeState(move.state.local.future, move.state.local.current, forwardLink); +} + +export default { + initMoveStates, + queueSetState, + collectState, + applyMove, + setState, + // mergeMoveStates, Not using them yet cause they can slow down move simulation + // unmergeMoveStates, +}; \ No newline at end of file diff --git a/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts b/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts index 77d7ca8a6..f3bc538a2 100644 --- a/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts +++ b/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts @@ -57,7 +57,7 @@ async function generateSpritesheet(gl: WebGL2RenderingContext, images: HTMLImage canvas.width = canvasWidth; canvas.height = canvasHeight; const ctx = canvas.getContext('2d'); - if (ctx === null) throw new Error('2D context null.') + if (ctx === null) throw new Error('2D context null.'); // Positioning variables let xIndex = 0; diff --git a/src/client/scripts/esm/chess/util/moveutil.js b/src/client/scripts/esm/chess/util/moveutil.js index 7b32ea3fe..7bf126c64 100644 --- a/src/client/scripts/esm/chess/util/moveutil.js +++ b/src/client/scripts/esm/chess/util/moveutil.js @@ -24,6 +24,21 @@ function Move() { this.type = undefined; /** @type {Array} */ this.changes = undefined; + + this.state = { + local: { + current: {}, + future: {}, + }, + global: { + current: {}, + future: {}, + } + }; + + /** @type {number} */ + this.generateIndex = undefined; + /** The start coordinates of the piece: `[x,y]` */ this.startCoords = undefined; /** The end coordinates of the piece: `[x,y]` */ @@ -44,33 +59,6 @@ function Move() { * object: `{ coord, dir }` where `coord` is the starting coordinates of the * rook being castled with, and `dir` is the direction castled, 1 for right and -1 for left. */ this.castle = undefined; - /** Contains information for undoing simulated moves. - * Several of these properties are impossible to recalculate without - * looking at previous moves, or replaying the whole game. */ - this.rewindInfo = { - /** The index of the captured piece within the gamefile's piece list. - * Required to not screw up the mesh when simulating. */ - capturedIndex: undefined, - /** The index of the promoted pawn within the gamefile's piece list. - * Required to not screw up the mesh when simulating. */ - pawnIndex: undefined, - /** Whether the moved piece had its special right before moving. */ - specialRightStart: undefined, - /** Whether the piece on the destination had its special rights before being captured. */ - specialRightEnd: undefined, - /** The gamefile's `enpassant` property before this move was made. */ - enpassant: undefined, - /** The gamefile's `moveRuleState` property before this move was made. */ - moveRuleState: undefined, - /** The gamefile's `checksGiven` property before this move was made. */ - checksGiven: undefined, - /** The gamefile's `inCheck` property before this move was made. */ - inCheck: undefined, - /** The gamefile's `attackers` property before this move was made. */ - attackers: undefined, - /** The gamefile's `gameConclusion` property before this move was made. */ - gameConclusion: undefined, - }; /** The move in most compact notation: `8,7>8,8Q` */ this.compact = undefined; } diff --git a/src/client/scripts/esm/chess/variants/gamerules.js b/src/client/scripts/esm/chess/variants/gamerules.js index aadf9ab94..911ec7ecb 100644 --- a/src/client/scripts/esm/chess/variants/gamerules.js +++ b/src/client/scripts/esm/chess/variants/gamerules.js @@ -1,5 +1,4 @@ import jsutil from "../../util/jsutil.js"; -import winconutil from "../util/winconutil.js"; /** * This script contains the gameRules constructor, diff --git a/src/client/scripts/esm/components/header/settings.js b/src/client/scripts/esm/components/header/settings.js index 5b349d171..cb4cd67ec 100644 --- a/src/client/scripts/esm/components/header/settings.js +++ b/src/client/scripts/esm/components/header/settings.js @@ -5,9 +5,10 @@ import languagedropdown from "./dropdowns/languagedropdown.js"; import boarddropdown from "./dropdowns/boarddropdown.js"; import legalmovedropdown from "./dropdowns/legalmovedropdown.js"; import perspectivedropdown from "./dropdowns/perspectivedropdown.js"; +import preferences from "./preferences.js"; // Only imported so its code runs +// eslint-disable-next-line no-unused-vars import pingmeter from "./pingmeter.js"; -import preferences from "./preferences.js"; // Document Elements ------------------------------------------------------------------------- diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index 384c7a14a..b9c094bd9 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -85,7 +85,7 @@ function captureMeshPiece(gamefile: gamefile, change: Change) { function uncaptureMeshPiece(gamefile: gamefile, change: Change) { returnMeshPiece(gamefile, change); - addMeshPiece(gamefile, {action: "addPiece", piece: change['capturedPiece']}); + addMeshPiece(gamefile, {action: "add", piece: change['capturedPiece']}); } export { diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 10ec54ea7..435397d3c 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -21,6 +21,7 @@ import guiclock from "../gui/guiclock.js"; // @ts-ignore import clock from "../../chess/logic/clock.js"; +import state from "../../chess/logic/state.js"; import boardchanges from "../../chess/logic/boardchanges.js"; import { animatableChanges, meshChanges } from "./graphicalchanges.js"; @@ -29,7 +30,13 @@ import type gamefile from "../../chess/logic/gamefile.js"; // @ts-ignore import type { Move } from "../../chess/util/moveutil.js"; -// TODO: doc +/** + * The universal move function for the client's game. + * + * @param gamefile the gamefile + * @param move + * @param options + */ function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, concludeGameIfOver = true} = {}) { movepiece.generateMove(gamefile, move); movepiece.makeMove(gamefile, move); @@ -54,10 +61,19 @@ function makeMove(gamefile: gamefile, move: Move, { doGameOverChecks = true, con arrows.clearListOfHoveredPieces(); } -// TODO: doc +/** + * Animates a given move. + * We don't use boardchanges because custom functionality is needed. + * @param move the move to animate + * @param forward whether this is a forward or back animation + */ function animateMove(move: Move, forward = true) { const funcs = forward ? animatableChanges.forward : animatableChanges.backward; - let clearanimations = true; + let clearanimations = true; // The first animation of a turn should clear prev turns animation + // TODO: figure out a way to animate multiple moves of the same piece + // Keyframing or smth + + // How does the rose animate? for (const c of move.changes) { if (!(c.action in funcs)) continue; funcs[c.action]!(c, clearanimations); @@ -65,35 +81,66 @@ function animateMove(move: Move, forward = true) { } } -// TODO: doc +/** + * Updates turn gui elements and updates check highlights whenever we look at a different turn. + * @param gamefile the gamefile + */ +function updateGui(): void { + guinavigation.update_MoveButtons(); + stats.showMoves(); + frametracker.onVisualChange(); +} + +/** + * + * @param gamefile + */ function rewindMove(gamefile: gamefile) { boardchanges.runMove(gamefile, gamefile.moves[gamefile.moveIndex], meshChanges, false); movepiece.rewindMove(gamefile); - guinavigation.update_MoveButtons(); - frametracker.onVisualChange(); + updateGui(); } -// TODO: doc +/** + * Makes the game view the last move + * @param gamefile + */ function viewFront(gamefile: gamefile) { movepiece.gotoMove(gamefile, gamefile.moves.length - 1, (m: Move) => viewMove(gamefile, m, true)); - guinavigation.update_MoveButtons(); - stats.showMoves(); + updateGui(); } -// TODO: doc +/** + * Apply the move to the board state and the mesh + * @param gamefile + * @param move + * @param forward + */ function viewMove(gamefile: gamefile, move: Move, forward = true) { boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); boardchanges.runMove(gamefile, move, meshChanges, forward); + state.applyMove(gamefile, move, forward); } -// TODO: doc +/** + * Makes the game veiw a set move index + * @param gamefile the gamefile + * @param index the move index to goto + */ function viewIndex(gamefile: gamefile, index: number) { movepiece.gotoMove(gamefile, index, (m: Move) => viewMove(gamefile, m, index >= gamefile.moveIndex)); - guinavigation.update_MoveButtons(); - stats.showMoves(); + updateGui(); +} + +function navigateMove(gamefile: gamefile, forward: boolean): void { + const idx = forward ? gamefile.moveIndex++ + 1 : gamefile.moveIndex--; // change move index and get the idx of the move we are supposed to apply + viewMove(gamefile, gamefile.moves[idx], forward); + animateMove(gamefile.moves[idx], forward); + updateGui(); } export default { + navigateMove, makeMove, rewindMove, viewMove, diff --git a/src/client/scripts/esm/game/gui/guiclock.js b/src/client/scripts/esm/game/gui/guiclock.js index 2ba8bb94d..e13582751 100644 --- a/src/client/scripts/esm/game/gui/guiclock.js +++ b/src/client/scripts/esm/game/gui/guiclock.js @@ -225,10 +225,9 @@ function set(gamefile) { // The 10s drum countdown... /** Reschedules the timer to play the 10-second countdown effect. */ function rescheduleCountdown(gamefile) { - const now = Date.now(); - rescheduleDrum(gamefile, now); - rescheduleTicking(gamefile, now); - rescheduleTick(gamefile, now); + rescheduleDrum(gamefile); + rescheduleTicking(gamefile); + rescheduleTick(gamefile); } /** @@ -249,7 +248,7 @@ function push(gamefile) { } } -function rescheduleDrum(gamefile, now) { +function rescheduleDrum(gamefile) { clearTimeout(countdown.drum.timeoutID); if (onlinegame.areInOnlineGame() && gamefile.clocks.colorTicking !== onlinegame.getOurColor()) return; // Don't play the sound effect for our opponent. const timeUntil10SecsRemain = gamefile.clocks.currentTime[gamefile.clocks.colorTicking] - 10000; @@ -263,7 +262,7 @@ function rescheduleDrum(gamefile, now) { countdown.drum.timeoutID = setTimeout(playDrumAndQueueNext, timeNextDrum, gamefile, secsRemaining); } -function rescheduleTicking(gamefile, now) { +function rescheduleTicking(gamefile) { clearTimeout(countdown.ticking.timeoutID); countdown.ticking.sound?.fadeOut(countdown.ticking.fadeOutDuration); if (onlinegame.areInOnlineGame() && gamefile.clocks.colorTicking !== onlinegame.getOurColor()) return; // Don't play the sound effect for our opponent. @@ -277,11 +276,11 @@ function rescheduleTicking(gamefile, now) { } // Tick sound effect right BEFORE 10 seconds is hit -function rescheduleTick(gamefile, now) { +function rescheduleTick(gamefile) { clearTimeout(countdown.tick.timeoutID); countdown.tick.sound?.fadeOut(countdown.tick.fadeOutDuration); if (onlinegame.areInOnlineGame() && gamefile.clocks.colorTicking !== onlinegame.getOurColor()) return; // Don't play the sound effect for our opponent. - const timeRemain = gamefile.clocks.currentTime[gamefile.clocks.colorTicking] - countdown.tick.timeToStartFromEnd;; + const timeRemain = gamefile.clocks.currentTime[gamefile.clocks.colorTicking] - countdown.tick.timeToStartFromEnd; if (timeRemain > 0) countdown.tick.timeoutID = setTimeout(playTickEffect, timeRemain); else { const offset = -timeRemain; diff --git a/src/client/scripts/esm/game/gui/guiguide.js b/src/client/scripts/esm/game/gui/guiguide.js index 0e919220f..c4935dba8 100644 --- a/src/client/scripts/esm/game/gui/guiguide.js +++ b/src/client/scripts/esm/game/gui/guiguide.js @@ -57,7 +57,6 @@ function callback_Back() { } function callback_FairyBack(event) { - event = event || window.event; if (fairyIndex === 0) return; hideCurrentFairy(); fairyIndex--; @@ -66,7 +65,6 @@ function callback_FairyBack(event) { } function callback_FairyForward(event) { - event = event || window.event; if (fairyIndex === maxFairyIndex) return; hideCurrentFairy(); fairyIndex++; diff --git a/src/client/scripts/esm/game/gui/guinavigation.js b/src/client/scripts/esm/game/gui/guinavigation.js index 3c392198a..4bde3d473 100644 --- a/src/client/scripts/esm/game/gui/guinavigation.js +++ b/src/client/scripts/esm/game/gui/guinavigation.js @@ -190,26 +190,21 @@ function callback_CoordsChange() { } function callback_Back(event) { - event = event || window.event; transition.telToPrevTel(); } function callback_Expand(event) { - event = event || window.event; const allCoords = gamefileutility.getCoordsOfAllPieces(game.getGamefile()); area.initTelFromCoordsList(allCoords); } function callback_Recenter(event) { - event = event || window.event; - const boundingBox = game.getGamefile().startSnapshot.box; if (!boundingBox) return console.error("Cannot recenter when the bounding box of the starting position is undefined!"); area.initTelFromUnpaddedBox(boundingBox); // If you know the bounding box, you don't need a coordinate list } function callback_MoveRewind(event) { - event = event || window.event; if (rewindIsLocked) return; if (!isItOkayToRewindOrForward()) return; lastRewindOrForward = Date.now(); @@ -217,7 +212,6 @@ function callback_MoveRewind(event) { } function callback_MoveForward(event) { - event = event || window.event; if (!isItOkayToRewindOrForward()) return; lastRewindOrForward = Date.now(); forwardMove(); @@ -244,7 +238,6 @@ function update_MoveButtons() { } function callback_Pause(event) { - event = event || window.event; guipause.open(); } @@ -385,18 +378,15 @@ function testIfForwardMove() { } /** Rewinds the currently-loaded gamefile by 1 move. Unselects any piece, updates the rewind/forward move buttons. */ -function rewindMove() {4 - const gamefile = game.getGamefile() +function rewindMove() { + const gamefile = game.getGamefile(); if (gamefile.mesh.locked) return statustext.pleaseWaitForTask(); if (!moveutil.isDecrementingLegal(game.getGamefile())) return stats.showMoves(); frametracker.onVisualChange(); - const idx = gamefile.moveIndex; - gamefile.moveIndex--; - movesequence.viewMove(gamefile, gamefile.moves[idx], false); - movesequence.animateMove(gamefile.moves[idx], false); + movesequence.navigateMove(gamefile, false); selection.unselectPiece(); @@ -412,9 +402,7 @@ function forwardMove() { if (gamefile.mesh.locked) return statustext.pleaseWaitForTask(); if (!moveutil.isIncrementingLegal(gamefile)) return stats.showMoves(); - gamefile.moveIndex++; - movesequence.viewMove(gamefile, gamefile.moves[gamefile.moveIndex], true); - movesequence.animateMove(gamefile.moves[gamefile.moveIndex], true); + movesequence.navigateMove(gamefile, true); // transition.teleportToLastMove() diff --git a/src/client/scripts/esm/game/gui/guititle.js b/src/client/scripts/esm/game/gui/guititle.js index 1e0822ca1..05fb20feb 100644 --- a/src/client/scripts/esm/game/gui/guititle.js +++ b/src/client/scripts/esm/game/gui/guititle.js @@ -58,13 +58,11 @@ function closeListeners() { } function callback_Play(event) { - event = event || window.event; close(); guiplay.open(); } function callback_Guide(event) { - event = event || window.event; close(); guiguide.open(); } diff --git a/src/client/scripts/esm/game/gui/stats.js b/src/client/scripts/esm/game/gui/stats.js index ea6ac6de8..925f120d9 100644 --- a/src/client/scripts/esm/game/gui/stats.js +++ b/src/client/scripts/esm/game/gui/stats.js @@ -20,7 +20,6 @@ import config from '../config.js'; const element_Statuses = document.getElementById('stats'); // Various statuses -const elementStatusMoveLooking = document.getElementById('status-move-looking'); const elementStatusFPS = document.getElementById('status-fps'); const elementStatusPiecesMesh = document.getElementById('status-pieces-mesh'); const elementStatusRotateMesh = document.getElementById('status-rotate-mesh'); diff --git a/src/client/scripts/esm/game/gui/statustext.js b/src/client/scripts/esm/game/gui/statustext.js index b17bac608..c9ea60e68 100644 --- a/src/client/scripts/esm/game/gui/statustext.js +++ b/src/client/scripts/esm/game/gui/statustext.js @@ -33,7 +33,6 @@ function showStatus(text, isError, durationMultiplier = 1) { */ function showStatusForDuration(text, durationMillis, isError) { if (!text) return; // Not defined (can happen if translation unavailable) - if (text == null) return console.error("Cannot show status of undefined text!!"); layers++; diff --git a/src/client/scripts/esm/game/misc/invites.js b/src/client/scripts/esm/game/misc/invites.js index 567046726..426a7df3b 100644 --- a/src/client/scripts/esm/game/misc/invites.js +++ b/src/client/scripts/esm/game/misc/invites.js @@ -341,17 +341,6 @@ function click(element) { } } -function getInviteFromID(id) { - if (!id) return console.error('Cannot find the invite with undefined id!'); - - for (let i = 0; i < activeInvites.length; i++) { - const invite = activeInvites[i]; - if (invite.id === id) return invite; - } - - console.error(`Could not find invite with id ${id} in the document!`); -} - function updateCreateInviteButton() { if (guiplay.getModeSelected() !== 'online') return; if (weHaveInvite) guiplay.setElement_CreateInviteTextContent(translations.invites.cancel_invite); diff --git a/src/client/scripts/esm/game/misc/sound.js b/src/client/scripts/esm/game/misc/sound.js index aaab04f07..1162be183 100644 --- a/src/client/scripts/esm/game/misc/sound.js +++ b/src/client/scripts/esm/game/misc/sound.js @@ -120,7 +120,7 @@ function playSound(soundName, { volume = 1, delay = 0, offset = 0, fadeInDuratio // 2. If reverb is specified, we also need a source for that effect! // We will play them both! if (!reverbVolume) return fadeInAndReturn(); // No reverb effect if volume is falsey or zero :) - if (reverbDuration == null) return console.error("Need to specify a reverb duration."); + if (reverbDuration === undefined) return console.error("Need to specify a reverb duration."); const sourceReverb = createBufferSource(reverbVolume, 1, reverbDuration); sourceReverb.start(startAt, startTime, duration); soundObject.sourceReverb = sourceReverb; @@ -128,7 +128,7 @@ function playSound(soundName, { volume = 1, delay = 0, offset = 0, fadeInDuratio return fadeInAndReturn(); function fadeInAndReturn() { - if (fadeInDuration == null) return soundObject; // No fade-in effect + if (fadeInDuration === undefined) return soundObject; // No fade-in effect fadeIn(soundObject.source, volume, fadeInDuration); if (soundObject.sourceReverb) fadeIn(soundObject.sourceReverb, reverbVolume, fadeInDuration); return soundObject; @@ -157,7 +157,7 @@ function getStampDuration(stamp) { // [ startTimeSecs, endTimeSecs ] */ function createBufferSource(volume, playbackRate = 1, reverbDurationSecs) { const source = audioContext.createBufferSource(); - if (audioDecodedBuffer == null) throw new Error("audioDecodedBuffer should never be undefined! This usually happens when soundspritesheet.mp3 starts loading but the document finishes loading in the middle of the audio loading."); + if (!audioDecodedBuffer) throw new Error("audioDecodedBuffer should never be undefined! This usually happens when soundspritesheet.mp3 starts loading but the document finishes loading in the middle of the audio loading."); source.buffer = audioDecodedBuffer; // Assuming `decodedBuffer` is defined elsewhere // What nodes do we want? @@ -170,7 +170,7 @@ function createBufferSource(volume, playbackRate = 1, reverbDurationSecs) { source.gainNode = gain; // Attach to the source object so that it can be faded out/in on demand. // Reverb node (if specified) - if (reverbDurationSecs != null) { + if (reverbDurationSecs !== undefined) { const convolver = generateConvolverNode(audioContext, reverbDurationSecs); nodes.push(convolver); } diff --git a/src/client/scripts/esm/game/rendering/arrows.js b/src/client/scripts/esm/game/rendering/arrows.js index 882634bc8..92283cadc 100644 --- a/src/client/scripts/esm/game/rendering/arrows.js +++ b/src/client/scripts/esm/game/rendering/arrows.js @@ -187,9 +187,9 @@ function update() { const x = piece.coords[0]; const y = piece.coords[1]; - const axis = line[0] == 0 ? 1 : 0; + const axis = line[0] === 0 ? 1 : 0; - const rightSide = x > paddedBoundingBox.right || y > rightCorner[1] == (rightCorner[1] == paddedBoundingBox.top); + const rightSide = x > paddedBoundingBox.right || y > rightCorner[1] === (rightCorner[1] === paddedBoundingBox.top); if (rightSide) { if (!right) right = piece; else if (piece.coords[axis] < right.coords[axis]) right = piece; @@ -308,7 +308,7 @@ function removeUnnecessaryArrows(arrows) { function doesTypeHaveMoveset(gamefile, type, direction) { const moveset = legalmoves.getPieceMoveset(gamefile, type); if (!moveset.sliding) return false; - return moveset.sliding[direction] != null; + return moveset.sliding[direction] !== undefined; } } @@ -409,7 +409,7 @@ function applyTransform(points, rotation, translation) { function renderThem() { if (mode === 0) return; - if (model == null) return; + if (!model) return; // render.renderModel(model, undefined, undefined, "TRIANGLES", spritesheet.getSpritesheet()) model.render(); diff --git a/src/client/scripts/esm/game/rendering/buffermodel.js b/src/client/scripts/esm/game/rendering/buffermodel.js index 0799d9ec4..a1af7a30e 100644 --- a/src/client/scripts/esm/game/rendering/buffermodel.js +++ b/src/client/scripts/esm/game/rendering/buffermodel.js @@ -53,7 +53,7 @@ function createModel_Colored(data, numPositionComponents, mode) { */ function createModel_Textured(data, numPositionComponents, mode, texture) { if (numPositionComponents < 2 || numPositionComponents > 3) return console.error(`Unsupported numPositionComponents ${numPositionComponents}`); - if (texture == null) return console.error("Cannot create a textured buffer model without a texture!"); + if (!texture) return console.error("Cannot create a textured buffer model without a texture!"); const stride = numPositionComponents + 2; const prepDrawFunc = getPrepDrawFunc(shaders.programs.textureProgram, numPositionComponents, true, false); return new BufferModel(shaders.programs.textureProgram, data, stride, mode, texture, prepDrawFunc); @@ -88,7 +88,7 @@ function createModel_ColorTextured(data, numPositionComponents, mode, texture) { */ function createModel_TintTextured(data, numPositionComponents, mode, texture) { if (numPositionComponents < 2 || numPositionComponents > 3) return console.error(`Unsupported numPositionComponents ${numPositionComponents}`); - if (texture == null) return console.error("Cannot create a tinted textured buffer model without a texture!"); + if (!texture) return console.error("Cannot create a tinted textured buffer model without a texture!"); const stride = numPositionComponents + 2; const prepDrawFunc = getPrepDrawFunc(shaders.programs.tintedTextureProgram, numPositionComponents, true, false); return new BufferModel(shaders.programs.tintedTextureProgram, data, stride, mode, texture, prepDrawFunc); diff --git a/src/client/scripts/esm/game/rendering/camera.js b/src/client/scripts/esm/game/rendering/camera.js index 8a197340d..fce3565a5 100644 --- a/src/client/scripts/esm/game/rendering/camera.js +++ b/src/client/scripts/esm/game/rendering/camera.js @@ -247,7 +247,7 @@ function sendViewMatrixToGPU() { /** @type {ShaderProgram} */ const program = shaders.programs[programName]; const viewMatrixLocation = program.uniformLocations.viewMatrix; - if (viewMatrixLocation == null) continue; // This shader program doesn't have the viewMatrix uniform, skip. + if (viewMatrixLocation === undefined) continue; // This shader program doesn't have the viewMatrix uniform, skip. gl.useProgram(program.program); gl.uniformMatrix4fv(viewMatrixLocation, false, viewMatrix); } @@ -261,7 +261,7 @@ function initProjMatrix() { /** @type {ShaderProgram} */ const program = shaders.programs[programName]; const projMatrixLocation = program.uniformLocations.projectionMatrix; - if (projMatrixLocation == null) continue; // This shader program doesn't have the projectionMatrix uniform, skip. + if (projMatrixLocation === undefined) continue; // This shader program doesn't have the projectionMatrix uniform, skip. gl.useProgram(program.program); gl.uniformMatrix4fv(projMatrixLocation, gl.FALSE, projectionMatrix); } diff --git a/src/client/scripts/esm/game/rendering/movement.js b/src/client/scripts/esm/game/rendering/movement.js index 0fd408e14..c0b994b6a 100644 --- a/src/client/scripts/esm/game/rendering/movement.js +++ b/src/client/scripts/esm/game/rendering/movement.js @@ -176,9 +176,8 @@ function checkIfBoardDropped() { if (boardIsGrabbed === 1) { // Mouse grabbed if (!input.isMouseHeld_Left()) { // Dropped board - boardIsGrabbed = 0; throwBoard(); // Mouse throws the board - clearPositionHistory(); + cancelBoardDrag(); } return; } @@ -193,12 +192,19 @@ function checkIfBoardDropped() { throwBoard(now); //Both fingers have been released. // Drop board - boardIsGrabbed = 0; boardPosFingerTwoGrabbed = undefined; - clearPositionHistory(); + cancelBoardDrag(); return; } +/** + * Forcefully terminates a board drag WITHOUT throwing the board. + */ +function cancelBoardDrag() { + boardIsGrabbed = 0; + clearPositionHistory(); +} + /** Called after letting go of the board. Applies velocity to the board according to how fast the mouse was moving */ function throwBoard(time) { removeOldPositions(time); @@ -240,7 +246,7 @@ function removeOldPositions(time) { // Checks if the mouse or finger has started dragging the board. Keep in mind if the // user clicked a piece, then the click event has been removed, so you can't do both at once. function checkIfBoardDragged() { - if (perspective.getEnabled()) return; + if (perspective.getEnabled() || transition.areWeTeleporting()) return; if (boardIsGrabbed === 0) { // Not already grabbed if (input.isMouseDown_Left()) grabBoard_WithMouse(); @@ -541,4 +547,5 @@ export default { eraseMomentum, setPositionToArea, checkIfBoardDragged, + cancelBoardDrag, }; \ No newline at end of file diff --git a/src/client/scripts/esm/game/rendering/transition.js b/src/client/scripts/esm/game/rendering/transition.js index 60f86871e..12939ff00 100644 --- a/src/client/scripts/esm/game/rendering/transition.js +++ b/src/client/scripts/esm/game/rendering/transition.js @@ -94,6 +94,8 @@ function teleport(tel1, tel2, ignoreHistory) { // tel2 can be undefined, if only // Reset velocities to zero movement.eraseMomentum(); + // We don't want to allow dragging during a transition. + movement.cancelBoardDrag(); } function update() { // Animate if we are currently teleporting diff --git a/src/client/scripts/esm/util/localstorage.js b/src/client/scripts/esm/util/localstorage.js index 7fb55c7f6..48dff3813 100644 --- a/src/client/scripts/esm/util/localstorage.js +++ b/src/client/scripts/esm/util/localstorage.js @@ -23,7 +23,7 @@ function saveItem(key, value, expiryMillis = defaultExpiryTimeMillis) { function loadItem(key) { const stringifiedSave = localStorage.getItem(key); // "{ value, expiry }" - if (stringifiedSave == null) return; + if (stringifiedSave === null) return; let save; try { save = JSON.parse(stringifiedSave); // { value, expires } @@ -45,7 +45,7 @@ function deleteItem(key) { } function hasItemExpired(save) { - if (save.expires == null) { + if (save.expires === undefined) { console.log(`Local storage item was in an old format. Deleting it! Value: ${JSON.stringify(save)}}`); return true; } diff --git a/src/client/scripts/esm/util/thread.js b/src/client/scripts/esm/util/thread.js deleted file mode 100644 index 96da9ac7a..000000000 --- a/src/client/scripts/esm/util/thread.js +++ /dev/null @@ -1,26 +0,0 @@ - -/** - * This script contains a sleep method for the javascript thread. - * - * Javascript is single-threated, when we sleep, we don't actually - * sleep the thread, but we delay the execution of the current function, - * to allow other functions on the call stack to be executed before we continue. - * - * ZERO dependancies - */ - -/** - * Pauses the current function execution for the given amount of time, allowing - * other functions in the call stack to execute before it resumes. - * - * This function returns a promise that resolves after the specified number of milliseconds. - * @param {number} ms - The number of milliseconds to sleep before continuing execution. - * @returns {Promise} A promise that resolves after the specified delay. - */ -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -export default { - sleep -}; \ No newline at end of file diff --git a/src/server/api/AdminPanel.ts b/src/server/api/AdminPanel.ts index bba23521d..381157b93 100644 --- a/src/server/api/AdminPanel.ts +++ b/src/server/api/AdminPanel.ts @@ -14,6 +14,8 @@ import { deleteAccount } from "../controllers/deleteAccountController.js"; // @ts-ignore import { deleteAllSessionsOfUser } from "../controllers/authenticationTokens/sessionManager.js"; // @ts-ignore +import { refreshGitHubContributorsList } from "./GitHub.js"; +// @ts-ignore import { areRolesHigherInPriority } from "../controllers/roles.js"; import type { CustomRequest } from "../../types.js"; @@ -31,7 +33,8 @@ const validCommands = [ "invites", "announce", "userinfo", - "help" + "updatecontributors", + "help", ]; function processCommand(req: CustomRequest, res: Response): void { @@ -72,6 +75,9 @@ function processCommand(req: CustomRequest, res: Response): void { case "userinfo": getUserInfo(command, commandAndArgs, req, res); return; + case "updatecontributors": + updateContributorsCommand(command, req, res); + return; case "help": helpCommand(commandAndArgs, res); return; @@ -211,6 +217,12 @@ function getUserInfo(command: string, commandAndArgs: string[], req: CustomReque } } +function updateContributorsCommand(command: string, req: CustomRequest, res: Response) { + logCommand(command, req); + refreshGitHubContributorsList(); + sendAndLogResponse(res, 200, "Contributors should now be updated!"); +} + function helpCommand(commandAndArgs: string[], res: Response) { if (commandAndArgs.length === 1) { res.status(200).send("Commands: " + validCommands.join(", ") + "\nUse help to get more information about a command."); @@ -244,6 +256,9 @@ function helpCommand(commandAndArgs: string[], res: Response) { case "userinfo": res.status(200).send("Syntax: userinfo \nPrints info about a user."); return; + case "updatecontributors": + res.status(200).send("Syntax: updatecontributors\nManually update to the most recent contributors list from the Github API. Should be used for testing"); + return; case "help": res.status(200).send("Syntax: help [command]\nPrints the list of commands or information about a command."); return; diff --git a/src/server/api/GitHub.js b/src/server/api/GitHub.js index ddb528265..5dd2d3ebb 100644 --- a/src/server/api/GitHub.js +++ b/src/server/api/GitHub.js @@ -5,22 +5,117 @@ * probably below our patron donors. */ +import { request } from 'node:https'; +import process from 'node:process'; +import { logEvents } from '../middleware/logEvents.js'; +import { join } from 'node:path'; +import { readFileIfExists } from '../utility/fileUtils.js'; +import { writeFile } from 'node:fs/promises'; +import { HOST_NAME } from '../config/config.js'; +const dirname = import.meta.dirname; + + +// Variables --------------------------------------------------------------------------- + + +const PATH_TO_CONTRIBUTORS_FILE = '../../../database/contributors.json'; /** A list of contributors on the infinitechess.org [repository](https://github.com/Infinite-Chess/infinitechess.org). - * This should be periodically refreshed. */ -const contributors = []; + * This should be periodically refreshed. @type {object[]} + * example contributor { + name: 'Naviary2', + iconUrl: 'https://avatars.githubusercontent.com/u/163621561?v=4', + linkUrl: 'https://github.com/Naviary2', + contributionCount: 1502 + } + */ +let contributors = (() => { + const fileIfExists = readFileIfExists(join(dirname, PATH_TO_CONTRIBUTORS_FILE)); + if (fileIfExists) return JSON.parse(fileIfExists); + return []; +})(); +// console.log(contributors); /** The interval, in milliseconds, to use GitHub's API to refresh the contributor list. */ -const intervalToRefreshContributorsMillis = 1000 * 60 * 60; // 1 hour +const intervalToRefreshContributorsMillis = 1000 * 60 * 60 * 3; // 3 hours +// const intervalToRefreshContributorsMillis = 1000 * 20; // 20s for dev testing + +/** The id of the interval to update contributors. Can be used to cancel it if the API token isn't specified. */ +const intervalId = setInterval(refreshGitHubContributorsList, intervalToRefreshContributorsMillis); +// refreshGitHubContributorsList(); // Initial refreshal for dev testing + +if (process.env.GITHUB_API_KEY === undefined || process.env.GITHUB_REPO === undefined) throw new Error('.env file is missing GITHUB_API_KEY or GITHUB_REPO, please regenerate the file or add the lines manually.'); + + +// Functions --------------------------------------------------------------------------- + /** * Uses GitHub's API to fetch all contributors on the infinitechess.org [repository](https://github.com/Infinite-Chess/infinitechess.org), * and updates our list! - * - * STILL TO BE WRITTEN */ function refreshGitHubContributorsList() { + if (process.env.GITHUB_API_KEY.length === 0 || process.env.GITHUB_REPO.length === 0) { + logEvents("Either Github API key not detected, or repository not specified. Stopping updating contributor list.", 'errLog.txt', { print: true }); + clearInterval(intervalId); + return; + } + const options = { + "method": "GET", + "hostname": "api.github.com", + "port": null, + "path": `/repos/${process.env.GITHUB_REPO}/contributors`, + "headers": { + "Accept": "application/vnd.github+json", + "Authorization": `Bearer ${process.env.GITHUB_API_KEY}`, + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": HOST_NAME, + "Content-Length": "0" + } + }; + + const req = request(options, function(res) { + const chunks = []; + + res.on("data", function(chunk) { + chunks.push(chunk); + }); + + res.on("end", function() { + const body = Buffer.concat(chunks); + + if (res.statusCode !== 200) return logEvents(`Response from GitHub when using API to get contributor list: ${body.toString()}`, 'errLog.txt', { print: true }); + + const response = body.toString(); + try { + const json = JSON.parse(response); + + const currentContributors = []; + for (const contributor of json) { + currentContributors.push( + { + name: contributor.login, + iconUrl: contributor.avatar_url, + linkUrl: contributor.html_url, + contributionCount: contributor.contributions, + } + ); + } + if (currentContributors.length > 0) { + contributors = currentContributors; + writeFile(join(dirname, PATH_TO_CONTRIBUTORS_FILE), JSON.stringify(contributors, null, 2)) + .then(() => { + // console.log("Contributors updated!"); + }); + } + } catch { + logEvents("Error parsing contributors JSON: " + response, 'errLog.txt', { print: true }); + } + }); + }); + + req.end(); } /** @@ -28,9 +123,13 @@ function refreshGitHubContributorsList() { * updated every {@link intervalToRefreshContributorsMillis}. * @returns {string[]} */ -function getContributors() { return contributors; } +function getContributors() { + return contributors; +} + export { - getContributors + refreshGitHubContributorsList, + getContributors, }; \ No newline at end of file diff --git a/src/server/config/env.js b/src/server/config/env.js index 0fd8d9f52..c30ac5615 100644 --- a/src/server/config/env.js +++ b/src/server/config/env.js @@ -36,6 +36,8 @@ HTTPPORT=80 HTTPSPORT=443 HTTPPORT_LOCAL=3000 HTTPSPORT_LOCAL=3443 +GITHUB_API_KEY= +GITHUB_REPO= `; fs.writeFileSync(envPath, content.trim()); console.log('Generated .env file'); diff --git a/src/server/game/gamemanager/gamemanager.js b/src/server/game/gamemanager/gamemanager.js index 8e63610d8..6c73a3103 100644 --- a/src/server/game/gamemanager/gamemanager.js +++ b/src/server/game/gamemanager/gamemanager.js @@ -90,7 +90,7 @@ function addGameToActiveGames(game) { */ function unsubClientFromGameBySocket(ws, { unsubNotByChoice = true } = {}) { const gameID = ws.metadata.subscriptions.game?.id; - if (gameID == null) return console.error("Cannot unsub client from game when it's not subscribed to one."); + if (gameID === undefined) return console.error("Cannot unsub client from game when it's not subscribed to one."); const game = getGameByID(gameID); if (!game) return console.log(`Cannot unsub client from game when game doesn't exist! Metadata: ${socketUtility.stringifySocketMetadata(ws)}`); @@ -143,7 +143,7 @@ function getGameBySocket(ws) { // Is the client in a game? What's their username/browser-id? const player = socketUtility.getOwnerFromSocket(ws); - if (player.member == null && player.browser == null) return console.error(`Cannot get game by socket when they don't have authentication! We should not have allowed this socket creation. Socket: ${socketUtility.stringifySocketMetadata(ws)}`); + if (player.member === undefined && player.browser === undefined) return console.error(`Cannot get game by socket when they don't have authentication! We should not have allowed this socket creation. Socket: ${socketUtility.stringifySocketMetadata(ws)}`); return getGameByPlayer(player); } diff --git a/src/server/game/statlogger.js b/src/server/game/statlogger.js index c24fc2204..3f0325cf4 100644 --- a/src/server/game/statlogger.js +++ b/src/server/game/statlogger.js @@ -47,7 +47,7 @@ const stats = await readFile('database/stats.json', 'Unable to read stats.json o * @returns */ async function logGame(game) { - if (game == null) return console.error("Cannot log a null game!"); + if (!game) return console.error("Cannot log a null game!"); // Only log the game if atleast 2 moves were played! (resignable) // Black-moves-first games are logged if atleast 1 move is played! @@ -66,18 +66,18 @@ async function logGame(game) { // Now record the number of moves played const plyCount = game.moves.length; - if (stats.moveCount.all == null) stats.moveCount.all = 0; + if (stats.moveCount.all === undefined) stats.moveCount.all = 0; stats.moveCount.all += plyCount; - if (stats.moveCount[variant] == null) stats.moveCount[variant] = 0; + if (stats.moveCount[variant] === undefined) stats.moveCount[variant] = 0; stats.moveCount[variant] += plyCount; - if (stats.moveCount[month] == null) stats.moveCount[month] = 0; + if (stats.moveCount[month] === undefined) stats.moveCount[month] = 0; stats.moveCount[month] += plyCount; // Increment the games played today - if (stats.gamesPlayed.byDay[day] == null) stats.gamesPlayed.byDay[day] = 1; + if (stats.gamesPlayed.byDay[day] === undefined) stats.gamesPlayed.byDay[day] = 1; else stats.gamesPlayed.byDay[day]++; @@ -95,14 +95,14 @@ async function logGame(game) { function incrementMonthsGamesPlayed(parent, month, variant) { // allTime / yyyy-mm= // Does this month's property exist yet? - if (parent[month] == null) parent[month] = {}; + if (parent[month] === undefined) parent[month] = {}; // Increment this month's all-variants by 1 - if (parent[month].all == null) parent[month].all = 1; + if (parent[month].all === undefined) parent[month].all = 1; else parent[month].all++; // Increment this month's this variant by 1 - if (parent[month][variant] == null) parent[month][variant] = 1; + if (parent[month][variant] === undefined) parent[month][variant] = 1; else parent[month][variant]++; } diff --git a/src/server/middleware/middleware.js b/src/server/middleware/middleware.js index 8f1b7551d..bc5870bb3 100644 --- a/src/server/middleware/middleware.js +++ b/src/server/middleware/middleware.js @@ -38,6 +38,7 @@ import { checkEmailAssociated, checkUsernameAvailable, createNewMember } from '. import { removeAccount } from '../controllers/deleteAccountController.js'; import { assignOrRenewBrowserID } from '../controllers/browserIDManager.js'; import { processCommand } from "../api/AdminPanel.js"; +import { getContributors } from '../api/GitHub.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); /** @@ -127,6 +128,11 @@ function configureMiddleware(app) { res.send(""); // Doesn't work without this for some reason }); + app.get("/api/contributors", (req, res) => { + const contributors = getContributors(); + res.send(JSON.stringify(contributors)); + }); + // Token Authenticator ------------------------------------------------------- /** diff --git a/src/server/utility/HTMLScriptInjector.js b/src/server/utility/HTMLScriptInjector.js index 6944a70bf..45d45ef96 100644 --- a/src/server/utility/HTMLScriptInjector.js +++ b/src/server/utility/HTMLScriptInjector.js @@ -3,17 +3,6 @@ */ -/** - * Injects a string after a certain string segment found within a source string. - * @param {string} src - The source string - * @param {string} after - The string segment for which the first match is found within the src, where the inject string will be inserted. - * @param {string} inject - The string to inject - * @returns {string} The injected string. If no match was found, the string will have not changed. - */ -function injectStringIntoStringAfter(src, after, inject) { - return src.replace(after, `${after}${inject}`); -} - /** * Takes an HTML document as a string, inserts a script tag into its head, * with the script content being the provided JavaScript code, and any corresponding attributes provided. diff --git a/src/server/utility/lockFile.js b/src/server/utility/lockFile.js index 6de016bc7..886282285 100644 --- a/src/server/utility/lockFile.js +++ b/src/server/utility/lockFile.js @@ -30,7 +30,7 @@ const readFile = async(path, errorString) => { }) .catch((e) => { // either lock could not be acquired or releasing it failed - const errText = `${errorString}${e.stack}`; + const errText = `Error when reading file in lockFile: ${errorString}${e.stack}`; logEvents(errText, 'errLog.txt', { print: true }); }); return data; @@ -55,7 +55,7 @@ const writeFile = async(path, object, errorString) => { }) .catch((e) => { // either lock could not be acquired or releasing it failed - const errText = `${errorString}${e.stack}`; + const errText = `Error while writing file in lockFile: ${errorString}${e.stack}`; logEvents(errText, 'errLog.txt', { print: true }); status = false; }); @@ -79,7 +79,7 @@ const editFile = async(path, callback, errorString) => { }) .catch((e) => { // either lock could not be acquired or releasing it failed - const errText = `${errorString}${e.stack}`; + const errText = `Error while editing file in lockFile: ${errorString}${e.stack}`; logEvents(errText, 'errLog.txt', { print: true }); status = false; }); diff --git a/tsconfig.json b/tsconfig.json index 68644c14d..5150eb358 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "noUncheckedIndexedAccess": true, "strict": true, "noEmitOnError": true, - "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", }, "include": [ "dist/**/*" ] } \ No newline at end of file From f23cb53bda167057f85ac40030c2651424301b2a Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:38:52 +0000 Subject: [PATCH 24/44] document `moveTowards` --- src/client/scripts/esm/util/math.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/esm/util/math.js b/src/client/scripts/esm/util/math.js index ba085ddac..95d87adf4 100644 --- a/src/client/scripts/esm/util/math.js +++ b/src/client/scripts/esm/util/math.js @@ -473,7 +473,7 @@ function roundUpToPowerOf2(num) { } /** - * + * Produce a number that is `progress` away from `s` in the direction of `e` form `s` * @param {Number} s * @param {Number} e * @param {Number} progress From ab67c215f7c468cce373fed1311408a1067fa2ef Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:40:24 +0000 Subject: [PATCH 25/44] Spelling mistakes --- src/client/scripts/esm/util/math.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/esm/util/math.js b/src/client/scripts/esm/util/math.js index 95d87adf4..310b2d6a2 100644 --- a/src/client/scripts/esm/util/math.js +++ b/src/client/scripts/esm/util/math.js @@ -473,7 +473,7 @@ function roundUpToPowerOf2(num) { } /** - * Produce a number that is `progress` away from `s` in the direction of `e` form `s` + * Produces a number that is `progress` away from `s` in the direction of `e` from `s` * @param {Number} s * @param {Number} e * @param {Number} progress From a72f35ecf79c1c86014c6beb80936f504fec0b60 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:35:32 +0000 Subject: [PATCH 26/44] fixed queue set state crashing game --- src/client/scripts/esm/chess/logic/movepiece.js | 8 ++++---- src/client/scripts/esm/chess/logic/specialmove.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index fda236832..d60d1ba60 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -106,11 +106,11 @@ function makeMove(gamefile, move) { * @param {Move} move */ function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { - state.queueSetState(move.changes, "enpassant", gamefile.enpassant, undefined); + state.queueSetState(gamefile, move.changes, "enpassant", undefined); let key = coordutil.getKeyFromCoords(move.startCoords); - state.queueSetState(move.changes, `specialRights.${key}`, gamefile.specialRights[key], undefined); + state.queueSetState(gamefile, move.changes, `specialRights.${key}`, undefined); key = coordutil.getKeyFromCoords(move.endCoords); - state.queueSetState(move.changes, `specialRights.${key}`, gamefile.specialRights[key], undefined); // We also delete the captured pieces specialRights for ANY move. + state.queueSetState(gamefile, move.changes, `specialRights.${key}`, undefined); // We also delete the captured pieces specialRights for ANY move. } /** @@ -145,7 +145,7 @@ function incrementMoveRule(gamefile, move, wasACapture) { // Reset if it was a capture or pawn movement const newMoveRule = (wasACapture || move.type.startsWith('pawns')) ? 0 : gamefile.moveRuleState + 1; - state.queueSetState(move.changes, 'moveRuleState', gamefile.moveRuleState, newMoveRule, {global: true}); + state.queueSetState(gamefile, move.changes, 'moveRuleState', newMoveRule, {global: true}); } /** diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index b7105c50f..f6f4bde21 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -52,7 +52,7 @@ function kings(gamefile, piece, move) { const landSquare = [move.endCoords[0] - specialTag.dir, move.endCoords[1]]; // Delete the rook's special move rights const key = coordutil.getKeyFromCoords(pieceToCastleWith.coords); - state.queueSetState(moveChanges, `specialRights.${key}`, gamefile.specialRights[key], undefined); + state.queueSetState(gamefile, moveChanges, `specialRights.${key}`, undefined); boardchanges.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move @@ -66,7 +66,7 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // If it was a double push, then add the enpassant flag to the gamefile, and remove its special right! if (updateProperties && isPawnMoveADoublePush(piece.coords, move.endCoords)) { - state.queueSetState(moveChanges, "enpassant", gamefile.undefined, getEnPassantSquare(piece.coords, move.endCoords)); + state.queueSetState(gamefile, moveChanges, "enpassant", getEnPassantSquare(piece.coords, move.endCoords)); } const enpassantTag = move.enpassant; // -1/1 From 1ffbf48b64a7810df4e5b9b100dfdf27b939285f Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:12:28 +0000 Subject: [PATCH 27/44] fix crash on reload of game --- src/client/scripts/esm/chess/logic/movepiece.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index d60d1ba60..ff611cade 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -214,15 +214,9 @@ function makeAllMovesInGame(gamefile, moves) { // Make the move in the game! - applyMove(gamefile, move); - incrementMoveRule(gamefile, move.type, move.captured !== undefined); - - gamefile.moveIndex++; - gamefile.moves.push(move); + makeMove(gamefile, move); } - updateTurn(gamefile); - updateInCheck(gamefile); if (gamefile.inCheck && gamefile.moveIndex !== -1) moveutil.flagLastMoveAsCheck(gamefile); gamefileutility.doGameOverChecks(gamefile); // Update the gameConclusion @@ -260,7 +254,9 @@ function calculateMoveFromShortmove(gamefile, shortmove) { const selectedPiece = gamefileutility.getPieceAtCoords(gamefile, move.startCoords); if (!selectedPiece) return move; // Return without any special move properties, this will automatically be an illegal move. - + + move.type = selectedPiece.type; + const legalSpecialMoves = legalmoves.calculate(gamefile, selectedPiece, { onlyCalcSpecials: true }).individual; for (let i = 0; i < legalSpecialMoves.length; i++) { const thisCoord = legalSpecialMoves[i]; From 37cbe982c73b256ba32aa1954eb8aa137472c8f3 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:18:29 +0000 Subject: [PATCH 28/44] Document types and clean up --- .../scripts/esm/chess/logic/boardchanges.ts | 113 ++++++++++++++---- .../scripts/esm/chess/logic/movepiece.js | 20 +--- .../scripts/esm/chess/logic/specialmove.js | 4 +- src/client/scripts/esm/chess/logic/state.ts | 86 +++++++------ .../scripts/esm/chess/logic/wincondition.js | 6 +- src/client/scripts/esm/chess/util/moveutil.js | 13 +- .../esm/game/chess/graphicalchanges.ts | 17 ++- 7 files changed, 151 insertions(+), 108 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 5a59981c2..46c55a98d 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -11,66 +11,110 @@ import type { Coords } from "./movesets.js"; // @ts-ignore import type { Move } from "../util/moveutil.js"; + interface Piece { type: string // - The type of the piece (e.g. `queensW`). coords: Coords // - The coordinates of the piece: `[x,y]` index: number // - The index of the piece within the gamefile's piece list. } +/** + * Generic type to describe any changes to the board + */ interface Change { - action: string, + // The action is used to differentiated the type of change made and the data it has + action: 'add' | "delete" | "move" | "capture", [changeData: string]: any } -interface ActionList { - [actionName: string]: T -} - +/** + * A change func takes change data and alters the board depending on the change + */ // I dislike eslint // eslint-disable-next-line no-unused-vars type genericChangeFunc = (gamefile: gamefile, change: any) => void; -interface ChangeApplication { - forward: ActionList +/** + * An actionlist is a dictionary links actions to functions. + * The function uses the change data for operations. Eg animation, updating mesh, logic + * It won't always include every action. + * If an action is looked up and there isn't a function for it, it's change is ignored + */ +interface ActionList { + [actionName: string]: F +} + +/** + * A change application is used for applying the changelist of a move in both directions. + */ +interface ChangeApplication { + forward: ActionList - backward: ActionList + backward: ActionList } -const changeFuncs: ChangeApplication = { +// This is the logical board change application +const changeFuncs: ChangeApplication = { forward: { "add": addPiece, "delete": deletePiece, - "movePiece": movePiece, - "capturePiece": capturePiece, + "move": movePiece, + "capture": capturePiece, }, backward: { "delete": addPiece, "add": deletePiece, - "movePiece": returnPiece, - "capturePiece": uncapturePiece, + "move": returnPiece, + "capture": uncapturePiece, } }; // All queue functions queue a change to the board. -// They add to a changelist which is then executed using a set of changefuncs -function queueCaputure(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { - changes.push({action: 'capturePiece', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); // Need to differentiate this from move so animations can work +// They add to a changelist which is then executed using a set of changefuncs, see above. + +/** + * Queues a move with catpure + * Need to differentiate this from move so animations can work and so that royal capture can be recognised + * @param changes + * @param piece The piece moved. Its coords are used as starting coords + * @param endCoords + * @param capturedPiece The piece captured + */ +function queueCapture(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { + changes.push({action: 'capture', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); return changes; } +/** + * Queues the addition of a piece to the board + * @param changes + * @param piece the piece to add + * the pieces index is optional and will get assigned one if none is present + */ function queueAddPiece(changes: Array, piece: Piece) { changes.push({action: 'add', piece: piece}); return changes; }; +/** + * Queues the removal of a piece + * @param changes + * @param piece + */ function queueDeletePiece(changes: Array, piece: Piece) { changes.push({action: 'delete', piece: piece}); return changes; } +/** + * Moves a piece without capture + * @param changes + * @param piece The piece moved. Its coords are used as starting coords + * @param endCoords + */ function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { - changes.push({action: 'movePiece', piece: piece, endCoords: endCoords}); + changes.push({action: 'move', piece: piece, endCoords: endCoords}); return changes; } @@ -88,13 +132,13 @@ function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionL } /** - * + * Applies a moves changes in a direction * @param gamefile * @param move * @param changeFuncs * @param forward */ -function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { +function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { const funcs = forward ? changeFuncs.forward : changeFuncs.backward; const changes = forward ? move.changes : [...move.changes].reverse(); applyChanges(gamefile, changes, funcs); @@ -104,9 +148,7 @@ function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, * Most basic add-a-piece method. Adds it the gamefile's piece list, * organizes the piece in the organized lists * @param gamefile - * @param change the add data - * change.piece is the piece to add - * the pieces index is optional and will get assigned one if none are present + * @param change */ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional const piece = change['piece']; @@ -209,7 +251,30 @@ function uncapturePiece(gamefile: gamefile, change: Change) { addPiece(gamefile, {piece: change['capturedPiece'], action:"add"}); } +const captureActions = new Set("capture"); +/** + * Gets every captured piece in changes + * @param move + * @returns + */ +function getCapturedPieces(move: Move): Piece[] { + const pieces: Piece[] = []; + for (const c of move.changes) { + if (!(c.action in captureActions)) continue; + pieces.push(c['capturedPiece']); + } + return pieces; +} + +function wasACapture(move: Move): boolean { + for (const c of move.changes) { + if ((c.action in captureActions)) return true; + } + return false; +} + export type { + genericChangeFunc, ActionList, ChangeApplication, Change, @@ -219,8 +284,10 @@ export default { queueAddPiece, queueDeletePiece, queueMovePiece, - queueCaputure, + queueCapture, + getCapturedPieces, + wasACapture, runMove, applyChanges, changeFuncs, diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 616155f0f..3f7ff1ca0 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -50,17 +50,7 @@ function generateMove(gamefile, move) { if (gamefile.specialMoves[trimmedType]) specialMoveMade = gamefile.specialMoves[trimmedType](gamefile, piece, move); if (!specialMoveMade) movePiece_NoSpecial(gamefile, piece, move); // Move piece regularly (no special tag) - let wasACapture = false; - for (const c of move.changes) { - if (c.action === 'capturePiece') { - wasACapture = true; - break; - } - } - - incrementMoveRule(gamefile, move, wasACapture); - - state.collectState(move); + incrementMoveRule(gamefile, move, boardchanges.wasACapture(move)); } /** @@ -106,11 +96,11 @@ function makeMove(gamefile, move) { * @param {Move} move */ function deleteEnpassantAndSpecialRightsProperties(gamefile, move) { - state.queueSetState(gamefile, move.changes, "enpassant", undefined); + state.queueSetState(gamefile, move, "enpassant", undefined); let key = coordutil.getKeyFromCoords(move.startCoords); - state.queueSetState(gamefile, move.changes, `specialRights.${key}`, undefined); + state.queueSetState(gamefile, move, `specialRights.${key}`, undefined); key = coordutil.getKeyFromCoords(move.endCoords); - state.queueSetState(gamefile, move.changes, `specialRights.${key}`, undefined); // We also delete the captured pieces specialRights for ANY move. + state.queueSetState(gamefile, move, `specialRights.${key}`, undefined); // We also delete the captured pieces specialRights for ANY move. } /** @@ -145,7 +135,7 @@ function incrementMoveRule(gamefile, move, wasACapture) { // Reset if it was a capture or pawn movement const newMoveRule = (wasACapture || move.type.startsWith('pawns')) ? 0 : gamefile.moveRuleState + 1; - state.queueSetState(gamefile, move.changes, 'moveRuleState', newMoveRule, {global: true}); + state.queueSetState(gamefile, move, 'moveRuleState', newMoveRule, {global: true}); } /** diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index f6f4bde21..5fcaf01c5 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -52,7 +52,7 @@ function kings(gamefile, piece, move) { const landSquare = [move.endCoords[0] - specialTag.dir, move.endCoords[1]]; // Delete the rook's special move rights const key = coordutil.getKeyFromCoords(pieceToCastleWith.coords); - state.queueSetState(gamefile, moveChanges, `specialRights.${key}`, undefined); + state.queueSetState(gamefile, move, `specialRights.${key}`, undefined); boardchanges.queueMovePiece(moveChanges, pieceToCastleWith, landSquare); // Make normal move @@ -66,7 +66,7 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // If it was a double push, then add the enpassant flag to the gamefile, and remove its special right! if (updateProperties && isPawnMoveADoublePush(piece.coords, move.endCoords)) { - state.queueSetState(gamefile, moveChanges, "enpassant", getEnPassantSquare(piece.coords, move.endCoords)); + state.queueSetState(gamefile, move, "enpassant", getEnPassantSquare(piece.coords, move.endCoords)); } const enpassantTag = move.enpassant; // -1/1 diff --git a/src/client/scripts/esm/chess/logic/state.ts b/src/client/scripts/esm/chess/logic/state.ts index a0ca0a845..0592441a8 100644 --- a/src/client/scripts/esm/chess/logic/state.ts +++ b/src/client/scripts/esm/chess/logic/state.ts @@ -1,4 +1,3 @@ -import type { Change } from "./boardchanges.js"; // @ts-ignore import type { Move } from "../util/moveutil.js"; // @ts-ignore @@ -6,31 +5,29 @@ import type gamefile from "./gamefile.js"; // This is oh so very cursed, however it works +/** + * A statechange contains the changes in gamefile from one turn to another + * When a statechange is applied gamefile variables are set directly + */ interface StateChange { - action: "stateChange", - path: string - currentState: any - futureState: any - global: boolean // Whether it can be reverted by viewing moves -} - -interface State { [path: string]: any } /** - * Sets the state by queueing it in the changelist - * @param gamefile the gamefile - * @param changes the changelist - * @param path the path of variable to change in the format of `a.b.c.d` can be indicies of arrays - * @param futureState what the variable is set to - * @param global // See StateChange.global + * Contains the statechanges for the turn before and after a move is made + * Local statechanges are always applied + * Global statechanges are not applied when VIEWING a move + * They are only applied when they are rewound or made. */ -function queueSetState(gamefile: gamefile, changes: Array, path: string, futureState: any, global: boolean = false) { - const currentState = getPath(gamefile, path); - if (currentState === futureState) return changes; // Nothing has changed - changes.push({action: "stateChange", path: path, currentState: currentState, futureState: futureState, global: global}); - return changes; +interface MoveState { + local: { + current: StateChange, + future: StateChange, + }, + global: { + current: StateChange, + future: StateChange, + } } /** @@ -67,21 +64,6 @@ function addToState(move: Move, path: string, currentState: any, futureState: an states.future[path] = futureState; } -/** - * Collects every StateChange into move states - * Removes all StateChanges from the change list - * @param move move - */ -function collectState(move: Move) { - const changes = move.changes.filter((c: Change) => {return c.action === "stateChange";}); - - for (const change of changes) { - addToState(move, change.path, change.currentState, change.futureState, {global: change.global}); - } - - move.changes = move.changes.filter((c: Change) => {return c.action !== "stateChange";}); -} - /** * Applies a single change to the gamefile * Traverses the gamefile to assign a s @@ -125,7 +107,7 @@ function getPath(gamefile: gamefile, path: string): any { } } -function applyState(gamefile: gamefile, state: State) { +function applyState(gamefile: gamefile, state: StateChange) { for (const path in state) { setPath(gamefile, path, state[path]); } @@ -143,6 +125,21 @@ function applyMove(gamefile: gamefile, move: Move, forward: boolean, { globalCha if (globalChange) applyState(gamefile, move.state.global[state]); } +/** + * Used when generating moves. Adds state changes to movestate + * @param gamefile + * @param move + * @param path + * @param value + * @returns if a change is needed + */ +function queueSetState(gamefile: gamefile, move: Move, path: string, value: any, {global = false } = {}): boolean { + const curState = getPath(gamefile, path); + if (curState === value) return false; + addToState(move, path, curState, value, {global: global}); + return true; +} + /** * Sets the gamefile variable and adds it to the state. * This is used after the move is generated @@ -152,10 +149,8 @@ function applyMove(gamefile: gamefile, move: Move, forward: boolean, { globalCha * @param value */ function setState(gamefile: gamefile, move: Move, path: string, value: any, { global = false } = {}) { - const curState = getPath(gamefile, path); - if (curState === value) return; - setPath(gamefile, path, value); - addToState(move, path, curState, value, {global: global}); + const similar = queueSetState(gamefile, move, path, value, {global: global}); + if (similar) setPath(gamefile, path, value); } /** @@ -164,8 +159,8 @@ function setState(gamefile: gamefile, move: Move, path: string, value: any, { gl * @param current current moves state * @returns the merged state */ -function mergeStates(previous: State, current: State, { validate = false } = {}): State { - const newState: State = {}; +function mergeStates(previous: StateChange, current: StateChange, { validate = false } = {}): StateChange { + const newState: StateChange = {}; for (const key in previous) { newState[key] = previous[key]!; } @@ -192,10 +187,10 @@ function mergeMoveStates(previous: Move, current: Move) { previous.state.global.future = current.state.global.current = mergeStates(previous.state.global.future, current.state.global.current); } -function unmergeState(current: State, future: State, forwardLink: boolean = true): State { +function unmergeState(current: StateChange, future: StateChange, forwardLink: boolean = true): StateChange { const ref = forwardLink ? current : future; const mergedState = forwardLink ? future : current; - const newState: State = {}; + const newState: StateChange = {}; for (const path in ref) { if (!(path in mergedState)) continue; if (JSON.stringify(ref[path]) === JSON.stringify(mergedState[path])) continue; @@ -210,10 +205,11 @@ function unmergeMoveStates(move: Move, forwardLink: boolean = true) { move.state.local[mergedState] = unmergeState(move.state.local.future, move.state.local.current, forwardLink); } +export type { MoveState }; + export default { initMoveStates, queueSetState, - collectState, applyMove, setState, // mergeMoveStates, Not using them yet cause they can slow down move simulation diff --git a/src/client/scripts/esm/chess/logic/wincondition.js b/src/client/scripts/esm/chess/logic/wincondition.js index 6ce2e73fb..8375224bd 100644 --- a/src/client/scripts/esm/chess/logic/wincondition.js +++ b/src/client/scripts/esm/chess/logic/wincondition.js @@ -8,6 +8,7 @@ import organizedlines from './organizedlines.js'; import moveutil from '../util/moveutil.js'; import colorutil from '../util/colorutil.js'; import typeutil from '../util/typeutil.js'; +import boardchanges from './boardchanges.js'; // Import End // Type Definitions... @@ -136,9 +137,8 @@ function wasLastMoveARoyalCapture(gamefile) { const capturedTypes = new Set(); - for (const c of lastMove.changes) { - if (c.action !== "capturePiece") continue; - capturedTypes.add(colorutil.trimColorExtensionFromType(c.capturedPiece.type)); + for (const pieceType of boardchanges.getCapturedPieces(lastMove)) { + capturedTypes.add(colorutil.trimColorExtensionFromType(pieceType)); } if (!capturedTypes.size) return false; // Last move not a capture diff --git a/src/client/scripts/esm/chess/util/moveutil.js b/src/client/scripts/esm/chess/util/moveutil.js index 7bf126c64..6aa399aee 100644 --- a/src/client/scripts/esm/chess/util/moveutil.js +++ b/src/client/scripts/esm/chess/util/moveutil.js @@ -10,6 +10,7 @@ import coordutil from './coordutil.js'; * Type Definitions * @typedef {import('../logic/gamefile.js').gamefile} gamefile * @typedef {import('../logic/boardchanges.js').Change} Change + * @typedef {import('../logic/state.js').MoveState} MoveState */ "use strict"; @@ -25,16 +26,8 @@ function Move() { /** @type {Array} */ this.changes = undefined; - this.state = { - local: { - current: {}, - future: {}, - }, - global: { - current: {}, - future: {}, - } - }; + /** @type {MoveState} */ + this.state = undefined; /** @type {number} */ this.generateIndex = undefined; diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index b9c094bd9..258c34bd1 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -6,19 +6,16 @@ import piecesmodel from "../rendering/piecesmodel.js"; import organizedlines from "../../chess/logic/organizedlines.js"; // @ts-ignore -import type { ChangeApplication, Change, ActionList } from "../../chess/logic/boardchanges.js"; +import type { ChangeApplication, Change, genericChangeFunc } from "../../chess/logic/boardchanges.js"; // @ts-ignore import type gamefile from "../../chess/logic/gamefile.js"; -// ESLint, THIS IS A TYPE INTERFACE SHUT UP -interface ChangeAnimations { - // eslint-disable-next-line no-unused-vars - forward: ActionList<(change: Change, clearanimations: boolean) => void> - // eslint-disable-next-line no-unused-vars - backward: ActionList<(change: Change, clearanimations: boolean) => void> -} -const animatableChanges: ChangeAnimations = { +// The functions for animating changes, does not use/alter gamefile directly +// eslint-disable-next-line no-unused-vars +type animationFunc = (change: Change, clearanimations: boolean) => void + +const animatableChanges: ChangeApplication = { forward: { "movePiece": animateMove, "capturePiece": animateCapture, @@ -42,7 +39,7 @@ function animateCapture(change: Change, clearanimations: boolean) { animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], change['capturedPiece'], clearanimations); } -const meshChanges: ChangeApplication = { +const meshChanges: ChangeApplication = { forward: { "add": addMeshPiece, "delete": deleteMeshPiece, From 958ee8ce51629ed327152a32ae8f790126133062 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:26:24 +0000 Subject: [PATCH 29/44] fix misspelling --- src/client/scripts/esm/chess/logic/movepiece.js | 2 +- .../scripts/esm/chess/logic/specialmove.js | 3 +-- .../scripts/esm/game/chess/graphicalchanges.ts | 16 ++++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 3f7ff1ca0..6eecc5199 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -115,7 +115,7 @@ function movePiece_NoSpecial(gamefile, piece, move) { const capturedPiece = gamefileutility.getPieceAtCoords(gamefile, move.endCoords); if (capturedPiece) { - boardchanges.queueCaputure(move.changes, piece, move.endCoords, capturedPiece); + boardchanges.queueCapture(move.changes, piece, move.endCoords, capturedPiece); return; }; diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index 5fcaf01c5..d3b0bafc2 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -30,7 +30,6 @@ function getFunctions() { // ALL FUNCTIONS NEED TO: // * Make the move // * Append the move -// * Animate the piece // Called when the piece moved is a king. @@ -81,7 +80,7 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // Delete the piece captured if (capturedPiece) { - boardchanges.queueCaputure(moveChanges, piece, move.endCoords, capturedPiece); + boardchanges.queueCapture(moveChanges, piece, move.endCoords, capturedPiece); } else { // Move the pawn boardchanges.queueMovePiece(moveChanges, piece, move.endCoords); diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index 258c34bd1..d7e2d20ca 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -17,13 +17,13 @@ type animationFunc = (change: Change, clearanimations: boolean) => void const animatableChanges: ChangeApplication = { forward: { - "movePiece": animateMove, - "capturePiece": animateCapture, + "move": animateMove, + "capture": animateCapture, }, backward: { - "movePiece": animateReturn, - "capturePiece": animateReturn, + "move": animateReturn, + "capture": animateReturn, } }; @@ -43,15 +43,15 @@ const meshChanges: ChangeApplication = { forward: { "add": addMeshPiece, "delete": deleteMeshPiece, - "movePiece": moveMeshPiece, - "capturePiece": captureMeshPiece, + "move": moveMeshPiece, + "capture": captureMeshPiece, }, backward: { "delete": addMeshPiece, "add": deleteMeshPiece, - "movePiece": returnMeshPiece, - "capturePiece": uncaptureMeshPiece, + "move": returnMeshPiece, + "capture": uncaptureMeshPiece, } }; From ebdd24bb5d9afa71c2b9f0a2f12d4388d07fe4f6 Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 04:36:31 -0700 Subject: [PATCH 30/44] Fixed double checlmate algorithm on game start --- src/client/scripts/esm/chess/logic/gamefile.js | 1 - src/client/scripts/esm/chess/logic/movepiece.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/gamefile.js b/src/client/scripts/esm/chess/logic/gamefile.js index d2dc929cd..d33dae9a8 100644 --- a/src/client/scripts/esm/chess/logic/gamefile.js +++ b/src/client/scripts/esm/chess/logic/gamefile.js @@ -256,7 +256,6 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion, clockV if (!wincondition.isCheckmateCompatibleWithGame(this)) gamerules.swapCheckmateForRoyalCapture(this.gameRules); organizedlines.initOrganizedPieceLists(this); - // THIS HAS TO BE AFTER gamerules.swapCheckmateForRoyalCapture() AS THIS DOES GAME-OVER CHECKS!!! movepiece.makeAllMovesInGame(this, moves); /** The game's conclusion, if it is over. For example, `'white checkmate'` * Server's gameConclusion should overwrite preexisting gameConclusion. */ diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 6eecc5199..79d675b2d 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -208,8 +208,6 @@ function makeAllMovesInGame(gamefile, moves) { } if (gamefile.inCheck && gamefile.moveIndex !== -1) moveutil.flagLastMoveAsCheck(gamefile); - - gamefileutility.doGameOverChecks(gamefile); // Update the gameConclusion } /** From a6d7fb44f485c209d69e9e3449fc430bbfad02f3 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:29:56 +0000 Subject: [PATCH 31/44] fix animations on reload --- src/client/scripts/esm/game/chess/game.js | 17 +++++++++++------ .../scripts/esm/game/chess/movesequence.ts | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/client/scripts/esm/game/chess/game.js b/src/client/scripts/esm/game/chess/game.js index 68b231701..53e04ef1e 100644 --- a/src/client/scripts/esm/game/chess/game.js +++ b/src/client/scripts/esm/game/chess/game.js @@ -36,6 +36,7 @@ import jsutil from '../../util/jsutil.js'; import winconutil from '../../chess/util/winconutil.js'; import sound from '../misc/sound.js'; import spritesheet from '../rendering/spritesheet.js'; +import movepiece from '../../chess/logic/movepiece.js'; import movesequence from './movesequence.js'; import loadingscreen from '../gui/loadingscreen.js'; import frametracker from '../rendering/frametracker.js'; @@ -226,13 +227,17 @@ async function loadGamefile(newGamefile) { } guipromotion.initUI(gamefile.gameRules.promotionsAllowed); - // A small delay to animate the very last move, so the loading screen - // spinny pawn animation has time to fade away. - animateLastMoveTimeoutID = setTimeout(() => { - const move = gamefile.moves[gamefile.moveIndex]; - if (move !== undefined) movesequence.animateMove(move, true); - }, delayOfLatestMoveAnimationOnRejoinMillis); + const lastmove = gamefile.moves[gamefile.moveIndex]; + if (lastmove !== undefined) { + movepiece.applyMove(gamefile, lastmove, false); + // A small delay to animate the very last move, so the loading screen + // spinny pawn animation has time to fade away. + animateLastMoveTimeoutID = setTimeout(() => { + movesequence.viewMove(gamefile, lastmove, true); + movesequence.animateMove(lastmove, true); + }, delayOfLatestMoveAnimationOnRejoinMillis); + } // Disable miniimages and arrows if there's over 50K pieces. They render too slow. if (newGamefile.startSnapshot.pieceCount >= gamefileutility.pieceCountToDisableCheckmate) { miniimage.disable(); diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 9ee87f400..deaab58f4 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -127,7 +127,7 @@ function viewFront(gamefile: gamefile) { * @param forward */ function viewMove(gamefile: gamefile, move: Move, forward = true) { - boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, forward); + movepiece.applyMove(gamefile, move, forward); boardchanges.runMove(gamefile, move, meshChanges, forward); state.applyMove(gamefile, move, forward); } From 3cdc5a64c0245f61f1b82b48ee8e6a4496af0c9b Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:40:03 +0000 Subject: [PATCH 32/44] stop animating dragged piece on conclusion --- src/client/scripts/esm/game/chess/selection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index 290c22114..bdf78ac6a 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -353,9 +353,10 @@ function moveGamefilePiece(coords) { specialdetect.transferSpecialFlags_FromCoordsToMove(coords, move); const compact = formatconverter.LongToShort_CompactMove(move); move.compact = compact; + const animateSelectedPiece = !draggingPiece; movesequence.makeMove(game.getGamefile(), move); - movesequence.animateMove(move, true, !draggingPiece); + movesequence.animateMove(move, true, animateSelectedPiece); onlinegame.sendMove(); unselectPiece(); From a70a685a57573fe1d371d821e63cd3ffe97de77d Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 18:08:53 -0700 Subject: [PATCH 33/44] Split line into multiple lines --- src/client/scripts/esm/game/chess/movesequence.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index deaab58f4..73ae9d633 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -143,7 +143,13 @@ function viewIndex(gamefile: gamefile, index: number) { } function navigateMove(gamefile: gamefile, forward: boolean): void { - const idx = forward ? gamefile.moveIndex++ + 1 : gamefile.moveIndex--; // change move index and get the idx of the move we are supposed to apply + // Adjust move index based on direction + if (forward) gamefile.moveIndex++; + else gamefile.moveIndex--; + + // Determine the index of the move to apply + const idx = forward ? gamefile.moveIndex : gamefile.moveIndex - 1; + viewMove(gamefile, gamefile.moves[idx], forward); animateMove(gamefile.moves[idx], forward); updateGui(); From 8f25df7d11e3e5701581e3352ac9e6011d057520 Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 18:40:51 -0700 Subject: [PATCH 34/44] Fixed bug I created --- src/client/scripts/esm/game/chess/movesequence.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 73ae9d633..6fedb88bf 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -143,12 +143,12 @@ function viewIndex(gamefile: gamefile, index: number) { } function navigateMove(gamefile: gamefile, forward: boolean): void { + // Determine the index of the move to apply + const idx = forward ? gamefile.moveIndex + 1 : gamefile.moveIndex; + // Adjust move index based on direction if (forward) gamefile.moveIndex++; else gamefile.moveIndex--; - - // Determine the index of the move to apply - const idx = forward ? gamefile.moveIndex : gamefile.moveIndex - 1; viewMove(gamefile, gamefile.moves[idx], forward); animateMove(gamefile.moves[idx], forward); From 134dd32ff32232cb49f6f9760d57649873f1ca13 Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 19:47:01 -0700 Subject: [PATCH 35/44] Semantic changes to boardchanges.ts --- .../scripts/esm/chess/logic/boardchanges.ts | 100 +++++++++--------- .../scripts/esm/chess/logic/specialmove.js | 3 +- 2 files changed, 49 insertions(+), 54 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 46c55a98d..9ff9135be 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -5,12 +5,15 @@ import gamefileutility from "../util/gamefileutility.js"; // @ts-ignore import jsutil from "../../util/jsutil.js"; + +// Type Definitions------------------------------------------------------------------------- + + // @ts-ignore import type { gamefile } from "./gamefile.js"; -import type { Coords } from "./movesets.js"; // @ts-ignore import type { Move } from "../util/moveutil.js"; - +import type { Coords } from "./movesets.js"; interface Piece { type: string // - The type of the piece (e.g. `queensW`). @@ -48,8 +51,7 @@ interface ActionList { * A change application is used for applying the changelist of a move in both directions. */ interface ChangeApplication { - forward: ActionList - + forward: ActionList, backward: ActionList } @@ -61,7 +63,6 @@ const changeFuncs: ChangeApplication = { "move": movePiece, "capture": capturePiece, }, - backward: { "delete": addPiece, "add": deletePiece, @@ -70,8 +71,9 @@ const changeFuncs: ChangeApplication = { } }; -// All queue functions queue a change to the board. -// They add to a changelist which is then executed using a set of changefuncs, see above. + +// Adding changes to a Move ---------------------------------------------------------------------------------------- + /** * Queues a move with catpure @@ -82,7 +84,7 @@ const changeFuncs: ChangeApplication = { * @param capturedPiece The piece captured */ function queueCapture(changes: Array, piece: Piece, endCoords: Coords, capturedPiece: Piece) { - changes.push({action: 'capture', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece}); + changes.push({ action: 'capture', piece: piece, endCoords: endCoords, capturedPiece: capturedPiece }); return changes; } @@ -93,17 +95,15 @@ function queueCapture(changes: Array, piece: Piece, endCoords: Coords, c * the pieces index is optional and will get assigned one if none is present */ function queueAddPiece(changes: Array, piece: Piece) { - changes.push({action: 'add', piece: piece}); + changes.push({ action: 'add', piece }); return changes; }; /** * Queues the removal of a piece - * @param changes - * @param piece */ function queueDeletePiece(changes: Array, piece: Piece) { - changes.push({action: 'delete', piece: piece}); + changes.push({ action: 'delete', piece }); return changes; } @@ -114,29 +114,16 @@ function queueDeletePiece(changes: Array, piece: Piece) { * @param endCoords */ function queueMovePiece(changes: Array, piece: Piece, endCoords: Coords) { - changes.push({action: 'move', piece: piece, endCoords: endCoords}); + changes.push({ action: 'move', piece: piece, endCoords }); return changes; } -/** - * Apply changes in changelist according to changefuncs - * @param gamefile the gamefile - * @param changes the changes to apply - * @param funcs the object contain change funcs - */ -function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { - for (const c of changes) { - if (!(c.action in funcs)) continue; - funcs[c.action]!(gamefile, c); - } -} + +// Executing changes of a Move ---------------------------------------------------------------------------------------- + /** - * Applies a moves changes in a direction - * @param gamefile - * @param move - * @param changeFuncs - * @param forward + * Applies the board changes of a move either forward or backward, modifying the piece lists. */ function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication, forward: boolean = true) { const funcs = forward ? changeFuncs.forward : changeFuncs.backward; @@ -144,11 +131,22 @@ function runMove(gamefile: gamefile, move: Move, changeFuncs: ChangeApplication< applyChanges(gamefile, changes, funcs); } +/** + * Applies the board changes of a change list in the provided order, modifying the piece lists. + * @param gamefile the gamefile + * @param changes the changes to apply + * @param funcs the object contain change funcs + */ +function applyChanges(gamefile: gamefile, changes: Array, funcs: ActionList) { + for (const change of changes) { + if (!(change.action in funcs)) throw Error(`Missing change function for likely-invalid change action "${change.action}"!`); + funcs[change.action]!(gamefile, change); + } +} + /** * Most basic add-a-piece method. Adds it the gamefile's piece list, * organizes the piece in the organized lists - * @param gamefile - * @param change */ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional const piece = change['piece']; @@ -178,8 +176,6 @@ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional /** * Most basic delete-a-piece method. Deletes it from the gamefile's piece list, * from the organized lists. - * @param gamefile - * @param change */ function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, index } const piece = change['piece']; @@ -195,11 +191,10 @@ function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, ind /** * Most basic move-a-piece method. Adjusts its coordinates in the gamefile's piece lists, * reorganizes the piece in the organized lists, and updates its mesh data. + * + * If the move is a capture, then use capturePiece() instead, so that we can animate it. * @param gamefile - The gamefile * @param change - the move data - * change.piece - the piece to move - * the piece coords is the start coords - * change,endCoords - the coords the piece is moved to */ function movePiece(gamefile: gamefile, change: Change) { const piece = change['piece']; @@ -217,8 +212,6 @@ function movePiece(gamefile: gamefile, change: Change) { /** * Reverses `movePiece` - * @param gamefile - * @param change */ function returnPiece(gamefile: gamefile, change: Change) { const piece = change['piece']; @@ -235,40 +228,44 @@ function returnPiece(gamefile: gamefile, change: Change) { } /** - * Captures a piece - * This is differentiated from move changes so it can be animated + * Captures a piece. + * + * This is differentiated from move changes so it can be animated. * @param gamefile * @param change */ function capturePiece(gamefile: gamefile, change: Change) { - deletePiece(gamefile, {piece: change['capturedPiece'], action: "add"}); + deletePiece(gamefile, { piece: change['capturedPiece'], action: "add" }); movePiece(gamefile, change); } -// Undoes a capture +/** + * Undos a capture + */ function uncapturePiece(gamefile: gamefile, change: Change) { returnPiece(gamefile, change); - addPiece(gamefile, {piece: change['capturedPiece'], action:"add"}); + addPiece(gamefile, { piece: change['capturedPiece'], action: "add" }); } const captureActions = new Set("capture"); /** * Gets every captured piece in changes - * @param move - * @returns */ function getCapturedPieces(move: Move): Piece[] { const pieces: Piece[] = []; - for (const c of move.changes) { - if (!(c.action in captureActions)) continue; - pieces.push(c['capturedPiece']); + for (const change of move.changes) { + if (!(change.action in captureActions)) continue; // This change isn't a capture + pieces.push(change['capturedPiece']); } return pieces; } +/** + * Returns true if any piece was captured by the move, whether directly or by special actions. + * */ function wasACapture(move: Move): boolean { - for (const c of move.changes) { - if ((c.action in captureActions)) return true; + for (const change of move.changes) { + if ((change.action in captureActions)) return true; // This was a capture action } return false; } @@ -285,7 +282,6 @@ export default { queueDeletePiece, queueMovePiece, queueCapture, - getCapturedPieces, wasACapture, runMove, diff --git a/src/client/scripts/esm/chess/logic/specialmove.js b/src/client/scripts/esm/chess/logic/specialmove.js index d3b0bafc2..218d0b5a1 100644 --- a/src/client/scripts/esm/chess/logic/specialmove.js +++ b/src/client/scripts/esm/chess/logic/specialmove.js @@ -90,8 +90,7 @@ function pawns(gamefile, piece, move, { updateProperties = true } = {}) { // Delete original pawn boardchanges.queueDeletePiece(moveChanges, {type: piece.type, coords: move.endCoords, index: piece.index}); - boardchanges.queueAddPiece(moveChanges, {type: promotionTag, coords: move.endCoords, index: undefined}); - + boardchanges.queueAddPiece(moveChanges, {type: promotionTag, coords: move.endCoords, index: undefined }); } // Special move was executed! From a9ef7cfc6cd756ee9a124fef0f9c011b433aa8ad Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 19:49:52 -0700 Subject: [PATCH 36/44] Patched all Piece type imports --- src/client/scripts/esm/chess/logic/boardchanges.ts | 1 + src/client/scripts/esm/chess/logic/checkdetection.js | 2 +- src/client/scripts/esm/chess/logic/legalmoves.js | 2 +- src/client/scripts/esm/chess/util/gamefileutility.js | 2 +- src/client/scripts/esm/game/chess/selection.js | 2 +- src/client/scripts/esm/game/rendering/animation.js | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 9ff9135be..a94d7fe71 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -275,6 +275,7 @@ export type { ActionList, ChangeApplication, Change, + Piece, }; export default { diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index dc437c80c..1705b4b2f 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -18,7 +18,7 @@ import moveutil from '../util/moveutil.js'; * @typedef {import('./gamefile.js').gamefile} gamefile * @typedef {import('../util/moveutil.js').Move} Move * @typedef {import('./legalmoves.js').LegalMoves} LegalMoves - * @typedef {import('./movepiece.js').Piece} Piece + * @typedef {import('./boardchanges.js').Piece} Piece * @typedef {import('../../util/math.js').BoundingBox} BoundingBox */ diff --git a/src/client/scripts/esm/chess/logic/legalmoves.js b/src/client/scripts/esm/chess/logic/legalmoves.js index f8878d596..0ef42d0f8 100644 --- a/src/client/scripts/esm/chess/logic/legalmoves.js +++ b/src/client/scripts/esm/chess/logic/legalmoves.js @@ -21,7 +21,7 @@ import movesets from './movesets.js'; * Type Definitions * @typedef {import('./gamefile.js').gamefile} gamefile * @typedef {import('../util/moveutil.js').Move} Move - * @typedef {import('./movepiece.js').Piece} Piece + * @typedef {import('./boardchanges.js').Piece} Piece * @typedef {import('./movesets.js').PieceMoveset} PieceMoveset * @typedef {import('./movesets.js').BlockingFunction} BlockingFunction */ diff --git a/src/client/scripts/esm/chess/util/gamefileutility.js b/src/client/scripts/esm/chess/util/gamefileutility.js index d1e5c9197..c5216f8f8 100644 --- a/src/client/scripts/esm/chess/util/gamefileutility.js +++ b/src/client/scripts/esm/chess/util/gamefileutility.js @@ -24,7 +24,7 @@ import gamerules from '../variants/gamerules.js'; /** * Type Definitions * @typedef {import('../logic/gamefile.js').gamefile} gamefile - * @typedef {import('../logic/movepiece.js').Piece} Piece + * @typedef {import('../logic/boardchanges.js').Piece} Piece */ diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index bdf78ac6a..9264a5d23 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -31,7 +31,7 @@ import preferences from '../../components/header/preferences.js'; * Type Definitions * @typedef {import('../../chess/util/moveutil.js').Move} Move * @typedef {import('../../chess/logic/legalmoves.js').LegalMoves} LegalMoves - * @typedef {import('../../chess/logic/movepiece.js').Piece} Piece + * @typedef {import('../../chess/logic/boardchanges.js').Piece} Piece */ "use strict"; diff --git a/src/client/scripts/esm/game/rendering/animation.js b/src/client/scripts/esm/game/rendering/animation.js index 283adc994..098fee85a 100644 --- a/src/client/scripts/esm/game/rendering/animation.js +++ b/src/client/scripts/esm/game/rendering/animation.js @@ -16,7 +16,7 @@ import spritesheet from './spritesheet.js'; * Type Definitions * @typedef {import('../../chess/util/moveutil.js').Move} Move * @typedef {import('./buffermodel.js').BufferModel} BufferModel - * @typedef {import('../../chess/logic/movepiece.js').Piece} Piece + * @typedef {import('../../chess/logic/boardchanges.js').Piece} Piece */ "use strict"; From 932a6af31354ef7cb2ca19b110bfc5e419e93aea Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 19:51:54 -0700 Subject: [PATCH 37/44] Added description to boardchanges.ts --- src/client/scripts/esm/chess/logic/boardchanges.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index a94d7fe71..3b2bc38c7 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -1,3 +1,10 @@ + +/* + * This script both contructs the changes list of a Move, and executes them + * when requested, modifying the piece lists according to what moved + * or was captured, forward or backward. + */ + // @ts-ignore import organizedlines from "./organizedlines.js"; // @ts-ignore From 768f679a0fbad0c217d64aff38036190443e2f8f Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 20:11:07 -0700 Subject: [PATCH 38/44] Semantic changes to graphicalchanges.ts --- .../scripts/esm/chess/logic/boardchanges.ts | 9 ++- .../esm/game/chess/graphicalchanges.ts | 77 ++++++++++++------- .../scripts/esm/game/rendering/piecesmodel.js | 4 +- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 3b2bc38c7..87f7121aa 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -38,9 +38,10 @@ interface Change { } /** - * A change func takes change data and alters the board depending on the change + * A generic function that takes the changes list of a move, and modifies either + * the piece lists to reflect that move, or modifies the mesh of the pieces, + * depending on the function, BUT NOT BOTH. */ -// I dislike eslint // eslint-disable-next-line no-unused-vars type genericChangeFunc = (gamefile: gamefile, change: any) => void; @@ -62,7 +63,9 @@ interface ChangeApplication { backward: ActionList } -// This is the logical board change application +/** + * An object mapping move changes to a function that performs the piece list changes for that action. + */ const changeFuncs: ChangeApplication = { forward: { "add": addPiece, diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index d7e2d20ca..42e83ede6 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -1,3 +1,6 @@ + + + // @ts-ignore import animation from "../rendering/animation.js"; // @ts-ignore @@ -5,55 +8,58 @@ import piecesmodel from "../rendering/piecesmodel.js"; // @ts-ignore import organizedlines from "../../chess/logic/organizedlines.js"; + +// Type Definitions ----------------------------------------------------------------------------------------- + + // @ts-ignore import type { ChangeApplication, Change, genericChangeFunc } from "../../chess/logic/boardchanges.js"; // @ts-ignore import type gamefile from "../../chess/logic/gamefile.js"; +/** + * An object mapping move changes to a function that performs the graphical mesh change for that action. + */ +const meshChanges: ChangeApplication = { + forward: { + "add": addMeshPiece, + "delete": deleteMeshPiece, + "move": moveMeshPiece, + "capture": captureMeshPiece, + }, + backward: { + "delete": addMeshPiece, + "add": deleteMeshPiece, + "move": returnMeshPiece, + "capture": uncaptureMeshPiece, + } +}; -// The functions for animating changes, does not use/alter gamefile directly +/** + * A generic function that animates a move change. + * + * DOES NOT ALTER the mesh or piece lists. + */ // eslint-disable-next-line no-unused-vars type animationFunc = (change: Change, clearanimations: boolean) => void +/** + * An object mapping move changes to a function that starts the animation for that action. + */ const animatableChanges: ChangeApplication = { forward: { "move": animateMove, "capture": animateCapture, }, - backward: { "move": animateReturn, "capture": animateReturn, } }; -function animateMove(change: Change, clearanimations: boolean) { - animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], undefined, clearanimations); -} - -function animateReturn(change: Change, clearanimations: boolean) { - animation.animatePiece(change['piece'].type, change['endCoords'], change['piece'].coords, undefined, clearanimations); -} - -function animateCapture(change: Change, clearanimations: boolean) { - animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], change['capturedPiece'], clearanimations); -} -const meshChanges: ChangeApplication = { - forward: { - "add": addMeshPiece, - "delete": deleteMeshPiece, - "move": moveMeshPiece, - "capture": captureMeshPiece, - }, +// Mesh Changes ----------------------------------------------------------------------------------------- - backward: { - "delete": addMeshPiece, - "add": deleteMeshPiece, - "move": returnMeshPiece, - "capture": uncaptureMeshPiece, - } -}; function addMeshPiece(gamefile: gamefile, change: Change) { piecesmodel.overwritebufferdata(gamefile, change['piece'], change['piece'].coords, change['piece'].type); @@ -85,6 +91,23 @@ function uncaptureMeshPiece(gamefile: gamefile, change: Change) { addMeshPiece(gamefile, {action: "add", piece: change['capturedPiece']}); } + +// Animate ----------------------------------------------------------------------------------------- + + +function animateMove(change: Change, clearanimations: boolean) { + animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], undefined, clearanimations); +} + +function animateReturn(change: Change, clearanimations: boolean) { + animation.animatePiece(change['piece'].type, change['endCoords'], change['piece'].coords, undefined, clearanimations); +} + +function animateCapture(change: Change, clearanimations: boolean) { + animation.animatePiece(change['piece'].type, change['piece'].coords, change['endCoords'], change['capturedPiece'], clearanimations); +} + + export { animatableChanges, meshChanges, diff --git a/src/client/scripts/esm/game/rendering/piecesmodel.js b/src/client/scripts/esm/game/rendering/piecesmodel.js index b7d42bf89..b3e88832c 100644 --- a/src/client/scripts/esm/game/rendering/piecesmodel.js +++ b/src/client/scripts/esm/game/rendering/piecesmodel.js @@ -315,8 +315,8 @@ function deletebufferdata(gamefile, piece) { * @param {string} type - The type of piece to write */ function overwritebufferdata(gamefile, undefinedPiece, coords, type) { - if (!gamefile.mesh.data64) return console.error("Should not be overwriting piece data when data64 is not defined!"); - if (!gamefile.mesh.data32) return console.error("Should not be overwriting piece data when data32 is not defined!"); + if (!gamefile.mesh.data64) throw Error("Should not be overwriting piece data when data64 is not defined!"); + if (!gamefile.mesh.data32) throw Error("Should not be overwriting piece data when data32 is not defined!"); const index = gamefileutility.calcPieceIndexInAllPieces(gamefile, undefinedPiece); From a05e5925858b00e99442c34c257fb319700605be Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 20:16:49 -0700 Subject: [PATCH 39/44] Added desc to graphicalchanges.ts --- src/client/scripts/esm/chess/logic/boardchanges.ts | 6 +++++- src/client/scripts/esm/game/chess/graphicalchanges.ts | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 87f7121aa..7e17deab6 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -1,8 +1,12 @@ -/* +/** * This script both contructs the changes list of a Move, and executes them * when requested, modifying the piece lists according to what moved * or was captured, forward or backward. + * + * The change functions here do NOT modify the mesh or animate anything, + * however, graphicalchanges.ts may rely on these changes present to + * know how to change the mesh, or what to animate. */ // @ts-ignore diff --git a/src/client/scripts/esm/game/chess/graphicalchanges.ts b/src/client/scripts/esm/game/chess/graphicalchanges.ts index 42e83ede6..e37eb9e53 100644 --- a/src/client/scripts/esm/game/chess/graphicalchanges.ts +++ b/src/client/scripts/esm/game/chess/graphicalchanges.ts @@ -1,5 +1,8 @@ - +/** + * This script contains the functions that know what mesh changes to make, + * and what animations to make, according to each action of a move's actions list. + */ // @ts-ignore import animation from "../rendering/animation.js"; From 711d874aad69cd6d7c01b1505b17e1096ef15f68 Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 23:28:09 -0700 Subject: [PATCH 40/44] Semantics changes, and commented-out unused methods --- .../scripts/esm/chess/logic/movepiece.js | 2 +- src/client/scripts/esm/chess/logic/state.ts | 122 ++++++++++-------- .../scripts/esm/game/chess/movesequence.ts | 11 +- 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 79d675b2d..23085ed65 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -311,7 +311,7 @@ function rewindMove(gamefile) { const move = moveutil.getMoveFromIndex(gamefile.moves, gamefile.moveIndex); // { type, startCoords, endCoords, captured } boardchanges.runMove(gamefile, move, boardchanges.changeFuncs, false); - state.applyMove(gamefile, move, false, {globalChange: true}); + state.applyMove(gamefile, move, false, { globalChange: true }); // Finally, delete the move off the top of our moves [] array list moveutil.deleteLastMove(gamefile.moves); diff --git a/src/client/scripts/esm/chess/logic/state.ts b/src/client/scripts/esm/chess/logic/state.ts index 0592441a8..230f4453b 100644 --- a/src/client/scripts/esm/chess/logic/state.ts +++ b/src/client/scripts/esm/chess/logic/state.ts @@ -15,9 +15,15 @@ interface StateChange { /** * Contains the statechanges for the turn before and after a move is made - * Local statechanges are always applied - * Global statechanges are not applied when VIEWING a move - * They are only applied when they are rewound or made. + * + * Local statechanges are unique to the position you're viewing, and are always applied. + * + * Global statechanges are a property of the game as a whole, not unique to the move, + * and are not applied when VIEWING a move. + * However, they are applied only when we make a new move, or rewind a simulated one. + * + * Local state change examples: (check, attackers, enpassant, specialrights) + * Global state change examples: (moverule state, running check counter) */ interface MoveState { local: { @@ -133,10 +139,10 @@ function applyMove(gamefile: gamefile, move: Move, forward: boolean, { globalCha * @param value * @returns if a change is needed */ -function queueSetState(gamefile: gamefile, move: Move, path: string, value: any, {global = false } = {}): boolean { +function queueSetState(gamefile: gamefile, move: Move, path: string, value: any, { global = false } = {}): boolean { const curState = getPath(gamefile, path); if (curState === value) return false; - addToState(move, path, curState, value, {global: global}); + addToState(move, path, curState, value, { global }); return true; } @@ -149,61 +155,63 @@ function queueSetState(gamefile: gamefile, move: Move, path: string, value: any, * @param value */ function setState(gamefile: gamefile, move: Move, path: string, value: any, { global = false } = {}) { - const similar = queueSetState(gamefile, move, path, value, {global: global}); + const similar = queueSetState(gamefile, move, path, value, { global }); if (similar) setPath(gamefile, path, value); } -/** - * Merges the previous moves state and the current moves state for this turn - * @param previous previous moves state - * @param current current moves state - * @returns the merged state - */ -function mergeStates(previous: StateChange, current: StateChange, { validate = false } = {}): StateChange { - const newState: StateChange = {}; - for (const key in previous) { - newState[key] = previous[key]!; - } - for (const key in current) { - if ((key in newState)) { - if (validate && (JSON.stringify(newState[key]!) !== JSON.stringify(current[key]!))) { - throw Error("Cannot merge states: states do not match"); - } - continue; - } - newState[key] = current[key]!; - } - return newState; -} - -/** - * Merges two moves states and sets both to the same state - * This can be done to save memory as they both have states for the same turn. - * @param previous - * @param current - */ -function mergeMoveStates(previous: Move, current: Move) { - previous.state.local.future = current.state.local.current = mergeStates(previous.state.local.future, current.state.local.current); - previous.state.global.future = current.state.global.current = mergeStates(previous.state.global.future, current.state.global.current); -} - -function unmergeState(current: StateChange, future: StateChange, forwardLink: boolean = true): StateChange { - const ref = forwardLink ? current : future; - const mergedState = forwardLink ? future : current; - const newState: StateChange = {}; - for (const path in ref) { - if (!(path in mergedState)) continue; - if (JSON.stringify(ref[path]) === JSON.stringify(mergedState[path])) continue; - newState[path] = mergedState[path]; - } - return newState; -} - -function unmergeMoveStates(move: Move, forwardLink: boolean = true) { - const mergedState = forwardLink ? "future" : "current"; - move.state.global[mergedState] = unmergeState(move.state.global.future, move.state.global.current, forwardLink); - move.state.local[mergedState] = unmergeState(move.state.local.future, move.state.local.current, forwardLink); -} +// Commented out unused methods for now + +// /** +// * Merges the previous moves state and the current moves state for this turn +// * @param previous previous moves state +// * @param current current moves state +// * @returns the merged state +// */ +// function mergeStates(previous: StateChange, current: StateChange, { validate = false } = {}): StateChange { +// const newState: StateChange = {}; +// for (const key in previous) { +// newState[key] = previous[key]!; +// } +// for (const key in current) { +// if ((key in newState)) { +// if (validate && (JSON.stringify(newState[key]!) !== JSON.stringify(current[key]!))) { +// throw Error("Cannot merge states: states do not match"); +// } +// continue; +// } +// newState[key] = current[key]!; +// } +// return newState; +// } + +// /** +// * Merges two moves states and sets both to the same state +// * This can be done to save memory as they both have states for the same turn. +// * @param previous +// * @param current +// */ +// function mergeMoveStates(previous: Move, current: Move) { +// previous.state.local.future = current.state.local.current = mergeStates(previous.state.local.future, current.state.local.current); +// previous.state.global.future = current.state.global.current = mergeStates(previous.state.global.future, current.state.global.current); +// } + +// function unmergeState(current: StateChange, future: StateChange, forwardLink: boolean = true): StateChange { +// const ref = forwardLink ? current : future; +// const mergedState = forwardLink ? future : current; +// const newState: StateChange = {}; +// for (const path in ref) { +// if (!(path in mergedState)) continue; +// if (JSON.stringify(ref[path]) === JSON.stringify(mergedState[path])) continue; +// newState[path] = mergedState[path]; +// } +// return newState; +// } + +// function unmergeMoveStates(move: Move, forwardLink: boolean = true) { +// const mergedState = forwardLink ? "future" : "current"; +// move.state.global[mergedState] = unmergeState(move.state.global.future, move.state.global.current, forwardLink); +// move.state.local[mergedState] = unmergeState(move.state.local.future, move.state.local.current, forwardLink); +// } export type { MoveState }; diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 6fedb88bf..850838216 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -121,10 +121,13 @@ function viewFront(gamefile: gamefile) { } /** - * Apply the move to the board state and the mesh - * @param gamefile - * @param move - * @param forward + * Apply the move to the board state and the mesh, whether forward or backward, + * as if we were wanting to *view* the move, instead of making it. + * + * This does not change the game state, for example, whos turn it is, + * what square enpassant is legal on, or the running count of checks given. + * + * But it does change the check state. */ function viewMove(gamefile: gamefile, move: Move, forward = true) { movepiece.applyMove(gamefile, move, forward); From 6f96f686f600c879e3b513c6d5529d33d6c8176f Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Sun, 29 Dec 2024 23:41:22 -0700 Subject: [PATCH 41/44] JSDoc update --- src/client/scripts/esm/chess/logic/checkdetection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/esm/chess/logic/checkdetection.js b/src/client/scripts/esm/chess/logic/checkdetection.js index 1705b4b2f..7122ce752 100644 --- a/src/client/scripts/esm/chess/logic/checkdetection.js +++ b/src/client/scripts/esm/chess/logic/checkdetection.js @@ -35,7 +35,7 @@ import moveutil from '../util/moveutil.js'; * Appends any attackers to the `attackers` list. * @param {gamefile} gamefile - The gamefile * @param {string} color - The side to test if their king is in check. "white" or "black" - * @param {Array} attackers - An empty array [] + * @param {[]} [attackers] - An empty array [], or undefined if we don't care about who is checking us, just whether we are in check or not, this can save compute. * @returns {boolean} true if in check */ function detectCheck(gamefile, color, attackers) { From c8f08969051ac59ba5a97a1a837d64e077179af6 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:15:20 +0000 Subject: [PATCH 42/44] Improvements --- src/client/scripts/esm/chess/logic/boardchanges.ts | 8 +++++++- src/client/scripts/esm/game/chess/game.js | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/esm/chess/logic/boardchanges.ts b/src/client/scripts/esm/chess/logic/boardchanges.ts index 7e17deab6..82d5f317c 100644 --- a/src/client/scripts/esm/chess/logic/boardchanges.ts +++ b/src/client/scripts/esm/chess/logic/boardchanges.ts @@ -171,6 +171,7 @@ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional if (piece.index === undefined) change['piece'].index = list.undefineds[0]; if (piece.index === undefined) { + piece.index = list.length; list.push(piece.coords); } else { // desiredIndex specified @@ -191,7 +192,12 @@ function addPiece(gamefile: gamefile, change: Change) { // desiredIndex optional * Most basic delete-a-piece method. Deletes it from the gamefile's piece list, * from the organized lists. */ -function deletePiece(gamefile: gamefile, change: Change) { // piece: { type, index } +function deletePiece(gamefile: gamefile, change: Change) { + if (change['piece'].index === undefined) { + console.warn("Deleted piece does not have index supplied! Attemping to get idx from other info"); + console.log(change); + change['piece'] = gamefileutility.getPieceFromTypeAndCoords(gamefile, change['piece'].type, change['piece'].coords); + } const piece = change['piece']; const list = gamefile.ourPieces[piece.type]; diff --git a/src/client/scripts/esm/game/chess/game.js b/src/client/scripts/esm/game/chess/game.js index 53e04ef1e..536045531 100644 --- a/src/client/scripts/esm/game/chess/game.js +++ b/src/client/scripts/esm/game/chess/game.js @@ -42,6 +42,7 @@ import loadingscreen from '../gui/loadingscreen.js'; import frametracker from '../rendering/frametracker.js'; import area from '../rendering/area.js'; import dragAnimation from '../rendering/draganimation.js'; +import state from '../../chess/logic/state.js'; // Import End /** @@ -229,13 +230,16 @@ async function loadGamefile(newGamefile) { const lastmove = gamefile.moves[gamefile.moveIndex]; if (lastmove !== undefined) { + gamefile.moveIndex--; movepiece.applyMove(gamefile, lastmove, false); + state.applyMove(gamefile, lastmove, false); // A small delay to animate the very last move, so the loading screen // spinny pawn animation has time to fade away. animateLastMoveTimeoutID = setTimeout(() => { movesequence.viewMove(gamefile, lastmove, true); movesequence.animateMove(lastmove, true); + gamefile.moveIndex++; }, delayOfLatestMoveAnimationOnRejoinMillis); } // Disable miniimages and arrows if there's over 50K pieces. They render too slow. From aacdb515cbaa02bdb9f65091679be6c1db8aa4e0 Mon Sep 17 00:00:00 2001 From: Idonotus <102253332+Idonotus@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:28:05 +0000 Subject: [PATCH 43/44] cleanup check flags --- src/client/scripts/esm/chess/logic/movepiece.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 23085ed65..5c6e689d9 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -86,7 +86,7 @@ function makeMove(gamefile, move) { // ALWAYS DO THIS NOW, no matter what. createCheckState(gamefile, move); - if (gamefile.inCheck) moveutil.flagLastMoveAsCheck(gamefile); + if (gamefile.inCheck) move.check = true; } /** @@ -206,8 +206,6 @@ function makeAllMovesInGame(gamefile, moves) { makeMove(gamefile, move); } - - if (gamefile.inCheck && gamefile.moveIndex !== -1) moveutil.flagLastMoveAsCheck(gamefile); } /** From 42e7573fd6a7dbbab2981a78af86156404764699 Mon Sep 17 00:00:00 2001 From: Naviary2 Date: Mon, 30 Dec 2024 16:08:57 -0700 Subject: [PATCH 44/44] Variable name change --- src/client/scripts/esm/game/chess/movesequence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/esm/game/chess/movesequence.ts b/src/client/scripts/esm/game/chess/movesequence.ts index 850838216..d9cb46ac3 100644 --- a/src/client/scripts/esm/game/chess/movesequence.ts +++ b/src/client/scripts/esm/game/chess/movesequence.ts @@ -116,7 +116,7 @@ function rewindMove(gamefile: gamefile) { * @param gamefile */ function viewFront(gamefile: gamefile) { - movepiece.gotoMove(gamefile, gamefile.moves.length - 1, (m: Move) => viewMove(gamefile, m, true)); + movepiece.gotoMove(gamefile, gamefile.moves.length - 1, (move: Move) => viewMove(gamefile, move, true)); updateGui(); }