From 71cdbe05bf0c4ce591333efc8ad242b59a3ea159 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 10 Jul 2024 21:16:31 +0100 Subject: [PATCH 01/58] customslices --- protected-owner/scripts/game/arrows.js | 12 +-- .../scripts/game/checkdetection.js | 44 ++++++--- protected-owner/scripts/game/gamefile.js | 13 +-- protected-owner/scripts/game/highlights.js | 96 ++++++++++++++++--- protected-owner/scripts/game/legalmoves.js | 59 ++++++++---- protected-owner/scripts/game/math.js | 21 ++-- protected-owner/scripts/game/movesets.js | 64 ++++++++----- .../scripts/game/organizedlines.js | 67 +++++-------- protected-owner/scripts/game/selection.js | 2 +- protected-owner/scripts/game/specialdetect.js | 2 +- 10 files changed, 241 insertions(+), 139 deletions(-) diff --git a/protected-owner/scripts/game/arrows.js b/protected-owner/scripts/game/arrows.js index fff221fb2..752ab5c8f 100644 --- a/protected-owner/scripts/game/arrows.js +++ b/protected-owner/scripts/game/arrows.js @@ -143,11 +143,11 @@ const arrows = (function() { // Up Diagonal. Same diag as one onscreen? { - const diagUp = math.getUpDiagonalFromCoords(coords) + const diagUp = math.getLineFromCoords([1,1], coords) const boardCornerTopLeft = [paddedBoundingBox.left, paddedBoundingBox.top] const boardCornerBottomRight = [paddedBoundingBox.right, paddedBoundingBox.bottom] - const boardDiagUpStart = math.getUpDiagonalFromCoords(boardCornerTopLeft) - const boardDiagUpEnd = math.getUpDiagonalFromCoords(boardCornerBottomRight) + const boardDiagUpStart = math.getLineFromCoords([1, 1], boardCornerTopLeft) + const boardDiagUpEnd = math.getLineFromCoords([1, 1], boardCornerBottomRight) if (diagUp < boardDiagUpStart && diagUp > boardDiagUpEnd) { const topRightSide = y > paddedBoundingBox.top || x > paddedBoundingBox.right; if (topRightSide) { @@ -162,11 +162,11 @@ const arrows = (function() { // Down Diagonal. Same diag as one onscreen? { - const diagDown = math.getDownDiagonalFromCoords(coords) + const diagDown = math.getLineFromCoords([1, -1],coords) const boardCornerBottomLeft = [paddedBoundingBox.left, paddedBoundingBox.bottom] const boardCornerTopRight = [paddedBoundingBox.right, paddedBoundingBox.top] - const boardDiagDownStart = math.getDownDiagonalFromCoords(boardCornerBottomLeft) - const boardDiagDownEnd = math.getDownDiagonalFromCoords(boardCornerTopRight) + const boardDiagDownStart = math.getLineFromCoords([1, -1], boardCornerBottomLeft) + const boardDiagDownEnd = math.getLineFromCoords([1, -1], boardCornerTopRight) // console.log(boardDiagDownStart, diagDown, boardDiagDownEnd) if (diagDown > boardDiagDownStart && diagDown < boardDiagDownEnd) { const topLeftSide = y > paddedBoundingBox.top || x < paddedBoundingBox.left; diff --git a/protected-owner/scripts/game/checkdetection.js b/protected-owner/scripts/game/checkdetection.js index b2f069936..2ef5d73e1 100644 --- a/protected-owner/scripts/game/checkdetection.js +++ b/protected-owner/scripts/game/checkdetection.js @@ -115,10 +115,22 @@ const checkdetection = (function(){ } // Returns true if there's any sliding piece that can capture on that square + /** + * + * @param {gamefile} gamefile + * @param {number[][]} coords + * @param {string} color + * @param {Array} attackers + * @returns + */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { let atleast1Attacker = false; - + const line = gamefile.slideMoves; + for (let i=0; i<0; i++) { + if (doesLineAttackSquare(gamefile,gamefile.piecesOrganizedByLines[line],line,color,attackers)) return true; + } + /** // Horizontal const row = gamefile.piecesOrganizedByRow[coords[1]]; if (doesLineAttackSquare(gamefile, row, 'horizontal', coords, color, attackers)) atleast1Attacker = true; @@ -136,13 +148,12 @@ const checkdetection = (function(){ key = math.getDownDiagonalFromCoords(coords) diag = gamefile.piecesOrganizedByDownDiagonal[key]; if (doesLineAttackSquare(gamefile, diag, 'diagonaldown', coords, color, attackers)) atleast1Attacker = true; - + */ return atleast1Attacker; } // Returns true if a piece on the specified line can capture on that square // THIS REQUIRES coords be already on the line. - // direction = 'horizontal' / 'vertical' / 'diagonalup' / 'diagonaldown' function doesLineAttackSquare(gamefile, line, direction, coords, colorOfFriendlys, attackers) { if (!line) return false; // No line, no pieces to attack @@ -155,12 +166,12 @@ const checkdetection = (function(){ if (thisPieceColor === 'neutral') continue; const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - const lineIsVertical = direction === 'vertical' ? true : false; - const moveset = direction === 'horizontal' ? thisPieceMoveset.horizontal + //const lineIsVertical = direction === 'vertical' ? true : false; + /**const moveset = direction === 'horizontal' ? thisPieceMoveset.horizontal : direction === 'vertical' ? thisPieceMoveset.vertical : direction === 'diagonalup' ? thisPieceMoveset.diagonalUp - : thisPieceMoveset.diagonalDown; - const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, moveset, thisPiece.coords, thisPieceColor) + : thisPieceMoveset.diagonalDown;*/ + const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction[0]==0, thisPieceMoveset.slideMoves[line], thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} @@ -230,10 +241,20 @@ const checkdetection = (function(){ return movepiece.simulateMove(gamefile, move, color).isCheck; } - // Time complexity: O(1) + // Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { + // O(1) + function isEmpty(obj) { + for (var prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + return false; + } + } + + return true + } - if (!moves.horizontal && !moves.vertical && !moves.diagonalUp && !moves.diagonalDown) return; // No sliding movesets to remove + if (true) return; // No sliding movesets to remove const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces @@ -297,10 +318,7 @@ const checkdetection = (function(){ return true; function eraseAllSlidingMoves() { - legalMoves.vertical = undefined; - legalMoves.horizontal = undefined; - legalMoves.diagonalUp = undefined; - legalMoves.diagonalDown = undefined; + legalMoves.slides = {} } } diff --git a/protected-owner/scripts/game/gamefile.js b/protected-owner/scripts/game/gamefile.js index 00ae8d126..80853d34f 100644 --- a/protected-owner/scripts/game/gamefile.js +++ b/protected-owner/scripts/game/gamefile.js @@ -67,14 +67,10 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) this.ourPieces = undefined; /** Pieces organized by key: `{ '1,2':'queensW', '2,3':'queensW' }` */ this.piecesOrganizedByKey = undefined; - /** Pieces organized by row: `{ 2:[{type:'queensW',coords:[1,2]}] }` */ - this.piecesOrganizedByRow = undefined; - /** Pieces organized by column: `{ 1:[{type:'queensW',coords:[1,2]}] }` */ - this.piecesOrganizedByColumn = undefined; - /** Pieces organized by up-diagonal (slope 1). Each diagonal is given the integer value of it's y-intercept on the grid. i.e. The '0' diagonal intersects (0,0), the '1' diagonal intersects (0,1)... */ - this.piecesOrganizedByUpDiagonal = undefined; - /** Pieces organized by down-diagonal (slope -1). Each diagonal is given the integar value of it's y-intercept on the grid. i.e. The '0' diagonal intersects (0,0), the '1' diagonal intersects (0,1)... */ - this.piecesOrganizedByDownDiagonal = undefined; + /** Pieces organized by lines: `{ '1,0' { 2:[{type:'queensW',coords:[1,2]}] } }` */ + this.piecesOrganizedByLines = undefined; + /** Legal slides*/ + this.slideMoves = [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]]; /** The object that contains the buffer model to render the pieces */ this.mesh = { @@ -159,7 +155,6 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) /** The number of half-moves played since the last capture or pawn push. */ this.moveRuleState = this.gameRules.moveRule ? this.startSnapshot.moveRuleState : undefined; area.initStartingAreaBox(this); - /** The move list. @type {Move[]} */ this.moves = []; /** Index of the move we're currently viewing in the moves list. -1 means we're looking at the very beginning of the game. */ diff --git a/protected-owner/scripts/game/highlights.js b/protected-owner/scripts/game/highlights.js index 9e84b0c2f..4ad41671e 100644 --- a/protected-owner/scripts/game/highlights.js +++ b/protected-owner/scripts/game/highlights.js @@ -220,23 +220,26 @@ const highlights = (function(){ // Calculate the data of the vertical slide concatData_HighlightedMoves_Sliding_Vert(coords, boundingBoxOfRenderRange.bottom, boundingBoxOfRenderRange.top) - + // Calculate the data of the diagonals + concatData_HighlightedMoves_Diagonals(coords, boundingBoxOfRenderRange, r, g, b, a) + /** // Calculate the data of the up diagonal concatData_HighlightedMoves_Diagonal_Up(coords, boundingBoxOfRenderRange, r, g, b, a) // Calculate the data of the down diagonal concatData_HighlightedMoves_Diagonal_Down(coords, boundingBoxOfRenderRange, r, g, b, a) + */ } function concatData_HighlightedMoves_Sliding_Horz(coords, left, right) { const legalMoves = selection.getLegalMovesOfSelectedPiece() - if (!legalMoves.horizontal) return; // Break if no legal horizontal slide + if (!legalMoves.slides['1,0']) return; // Break if no legal horizontal slide const [r,g,b,a] = options.getDefaultLegalMoveHighlight(); // Left - let startXWithoutOffset = legalMoves.horizontal[0] - board.gsquareCenter() + let startXWithoutOffset = legalMoves.slides['1,0'][0] - board.gsquareCenter() if (startXWithoutOffset < left - board.gsquareCenter()) startXWithoutOffset = left - board.gsquareCenter() let startX = startXWithoutOffset - model_Offset[0]; @@ -248,7 +251,7 @@ const highlights = (function(){ // Right - startXWithoutOffset = legalMoves.horizontal[1] + 1 - board.gsquareCenter() + startXWithoutOffset = legalMoves.slides['1,0'][1] + 1 - board.gsquareCenter() if (startXWithoutOffset > right + 1 - board.gsquareCenter()) startXWithoutOffset = right + 1 - board.gsquareCenter() startX = startXWithoutOffset - model_Offset[0]; @@ -261,13 +264,13 @@ const highlights = (function(){ function concatData_HighlightedMoves_Sliding_Vert (coords, bottom, top) { const legalMoves = selection.getLegalMovesOfSelectedPiece() - if (!legalMoves.vertical) return; // Break if there no legal vertical slide + if (!legalMoves.slides['0,1']) return; // Break if there no legal vertical slide const [r,g,b,a] = options.getDefaultLegalMoveHighlight(); // Bottom - let startYWithoutOffset = legalMoves.vertical[0] - board.gsquareCenter() + let startYWithoutOffset = legalMoves.slides['0,1'][0] - board.gsquareCenter() if (startYWithoutOffset < bottom - board.gsquareCenter()) startYWithoutOffset = bottom - board.gsquareCenter() let startY = startYWithoutOffset - model_Offset[1]; @@ -279,7 +282,7 @@ const highlights = (function(){ // Top - startYWithoutOffset = legalMoves.vertical[1] + 1 - board.gsquareCenter() + startYWithoutOffset = legalMoves.slides['0,1'][1] + 1 - board.gsquareCenter() if (startYWithoutOffset > top + 1 - board.gsquareCenter()) startYWithoutOffset = top + 1 - board.gsquareCenter() startY = startYWithoutOffset - model_Offset[1]; @@ -290,12 +293,78 @@ const highlights = (function(){ data.push(...bufferdata.getDataQuad_Color3D(startX, startY, endX, endY, z, r, g, b, a)) } + function concatData_HighlightedMoves_Diagonals (coords, renderBoundingBox, r, g, b, a) { + const legalMoves = selection.getLegalMovesOfSelectedPiece() + for (var line in legalMoves.slides) { + line = line.split(',') + if (line[1] == 0 || line[0] == 0) {console.log("A"); continue;} + const lineEqua = math.getLineFromCoords(line, coords)/line[0] + const intsect1Tile = math.getIntersectionEntryTile(line[1]/line[0], lineEqua, renderBoundingBox, 'bottomleft') + const intsect2Tile = math.getIntersectionEntryTile(line[1]/line[0], lineEqua, renderBoundingBox, 'topright') + + if (!intsect1Tile) return; // If there's no intersection point, it's off the screen, don't bother rendering. + if (!intsect2Tile) return; // Bruh + + { // Down moveset + let startTile = intsect2Tile + let endTile = intsect1Tile + + // Make sure it doesn't start before the tile right in front of us + if (startTile[0] > coords[0] - 1) startTile = [coords[0] - 1, coords[1] - 1] + let limit = legalMoves.slides[line][0] + + // Make sure it doesn't phase through our move limit + if (endTile[0] < limit) { + endTile[0] = limit + endTile[1] = startTile[1] + limit - startTile[0] + } + + // How many times will we iterate? + let iterateCount = startTile[0] - endTile[0] + 1 + if (iterateCount < 0) iterateCount = 0 + + // Init starting coords of the data, this will increment by 1 every iteration + let currentX = startTile[0] - board.gsquareCenter() + 1 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] + + // Generate data of each highlighted square + addDataDiagonalVariant(iterateCount, currentX, currentY, -Math.sign(line[0]), -Math.sign(line[1]), [-line[0], -line[1]], r, g, b, a) + } + + { // Up moveset + let startTile = intsect1Tile + let endTile = intsect2Tile + + // Make sure it doesn't start before the tile right in front of us + if (startTile[0] < coords[0] + 1) startTile = [coords[0] + 1, coords[1] + 1] + let limit = legalMoves.slides[line][1] + + // Make sure it doesn't phase through our move limit + if (endTile[0] > limit) { + endTile[0] = limit + endTile[1] = startTile[1] + limit - startTile[0] + } + + // How many times will we iterate? + let iterateCount = endTile[0] - startTile[0] + 1 + if (iterateCount < 0) iterateCount = 0 + + // Init starting coords of the data, this will increment by 1 every iteration + let currentX = startTile[0] - board.gsquareCenter() - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() - model_Offset[1] + + // Generate data of each highlighted square + addDataDiagonalVariant(iterateCount, currentX, currentY, Math.sign(line[0]), Math.sign(line[1]), line, r, g, b, a) + } + } + } + function concatData_HighlightedMoves_Diagonal_Up (coords, renderBoundingBox, r, g, b, a) { const legalMoves = selection.getLegalMovesOfSelectedPiece() if (!legalMoves.diagonalUp) return; // Calculate the intersection tile of this diagonal with the left/bottom and right/top sides of the screen. - const lineEqua = math.getUpDiagonalFromCoords(coords) // mx + b + const lineEqua = math.getLineFromCoords(coords) // mx + b const intsect1Tile = math.getIntersectionEntryTile(1, lineEqua, renderBoundingBox, 'bottomleft') const intsect2Tile = math.getIntersectionEntryTile(1, lineEqua, renderBoundingBox, 'topright') @@ -320,8 +389,8 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() + line[0] - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() + line[1] - model_Offset[1] // Generate data of each highlighted square addDataDiagonalVariant(iterateCount, currentX, currentY, -1, -1, r, g, b, a) @@ -420,7 +489,7 @@ const highlights = (function(){ } // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the hop values which are -1 or +1 dependant on the direction we're rendering - function addDataDiagonalVariant (iterateCount, currentX, currentY, xHop, yHop, r, g, b, a) { + function addDataDiagonalVariant (iterateCount, currentX, currentY, xHop, yHop, step, r, g, b, a) { for (let i = 0; i < iterateCount; i++) { const endX = currentX + xHop @@ -429,8 +498,9 @@ const highlights = (function(){ data.push(...bufferdata.getDataQuad_Color3D(currentX, currentY, endX, endY, z, r, g, b, a)) // Prepare for next iteration - currentX = endX - currentY = endY + currentX += step[0] + currentY += step[1] + console.log([currentX,currentY]) } } diff --git a/protected-owner/scripts/game/legalmoves.js b/protected-owner/scripts/game/legalmoves.js index fdb8b9394..c6258a49f 100644 --- a/protected-owner/scripts/game/legalmoves.js +++ b/protected-owner/scripts/game/legalmoves.js @@ -10,10 +10,7 @@ /** An object containing all the legal moves of a piece. * @typedef {Object} LegalMoves * @property {Object} individual - A list of the legal jumping move coordinates: `[[1,2], [2,1]]` - * @property {number[]} horizontal - A length-2 array containing the legal left and right slide limits: `[-5, Infinity]` - * @property {number[]} vertical - A length-2 array containing the legal bottom and top slide limits: `[-Infinity, 1]` - * @property {number[]} diagonalUp - A length-2 array containing the legal down-left, and up-right slides, where the number represents the `x` limit: `[-Infinity, 7]` - * @property {number[]} diagonalDown - A length-2 array containing the legal up-left, and down-right slides, where the number represents the `x` limit: `[3, 15]` + * @property {Object} slides - A dict containing length-2 arrays with the legal left and right slide limits: `{[1,0]:[-5, Infinity]}` */ const legalmoves = (function(){ @@ -34,7 +31,9 @@ const legalmoves = (function(){ // For every piece moveset... for (let i = 0; i < pieces.white.length; i++) { const thisPieceType = pieces.white[i] - const thisPieceIndividualMoveset = getPieceMoveset(gamefile, thisPieceType).individual; + var thisPieceIndividualMoveset + if (getPieceMoveset(gamefile, thisPieceType).individual) thisPieceIndividualMoveset = getPieceMoveset(gamefile, thisPieceType).individual; + else thisPieceIndividualMoveset = [] // For each individual move... for (let a = 0; a < thisPieceIndividualMoveset.length; a++) { @@ -87,10 +86,7 @@ const legalmoves = (function(){ const thisPieceMoveset = getPieceMoveset(gamefile, type) // Default piece moveset let legalIndividualMoves = []; - let legalHorizontalMoves; - let legalVerticalMoves; - let legalUpDiagonalMoves; - let legalDownDiagonalMoves; + let legalSlideMoves = {}; if (!onlyCalcSpecials) { @@ -98,9 +94,18 @@ const legalmoves = (function(){ shiftIndividualMovesetByCoords(thisPieceMoveset.individual, coords) legalIndividualMoves = moves_RemoveOccupiedByFriendlyPieceOrVoid(gamefile, thisPieceMoveset.individual, color) - + // Legal sliding moves - + if (thisPieceMoveset.slideMoves) { + let lines = gamefile.slideMoves; + for (let i=0; i=limits[0] && endCoords[0]<=limits[1] && line[0]!=0) return true; + else if (endCoords[1]>=limits[0] && endCoords[1]<=limits[1] && line[0]==0) return true; + } + } + /** // Do one of the horizontal moves match? const horizontal = legalMoves.horizontal; if (horizontal && endCoords[1] == startCoords[1]) { @@ -268,7 +282,7 @@ const legalmoves = (function(){ // Compare the clicked x tile with this diagonal moveset if (endCoords[0] >= diagonalDown[0] && endCoords[0] <= diagonalDown[1]) return true; } - + */ return false; } @@ -334,7 +348,7 @@ const legalmoves = (function(){ // Test if that piece's legal moves contain the destinationCoords. const legalMoves = legalmoves.calculate(gamefile, piecemoved); // This should pass on any special moves tags at the same time. - if (!legalmoves.checkIfMoveLegal(legalMoves, moveCopy.startCoords, moveCopy.endCoords)) { // Illegal move + if (!legalmoves.checkIfMoveLegal(gamefile, legalMoves, moveCopy.startCoords, moveCopy.endCoords)) { // Illegal move console.log(`Opponent's move is illegal because the destination coords are illegal. Move: ${JSON.stringify(moveCopy)}`) return rewindGameAndReturnReason(`Destination coordinates are illegal. inCheck: ${JSON.stringify(gamefile.inCheck)}. attackers: ${JSON.stringify(gamefile.attackers)}. originalMoveIndex: ${originalMoveIndex}. inCheckB4Forwarding: ${inCheckB4Forwarding}. attackersB4Forwarding: ${JSON.stringify(attackersB4Forwarding)}`); } @@ -380,15 +394,22 @@ const legalmoves = (function(){ return true; } - - // Accepts the calculated legal moves, tests to see if there are any + /** + * Accepts the calculated legal moves, tests to see if there are any + * @param {LegalMoves} moves + * @returns {boolean} + */ function hasAtleast1Move (moves) { // { individual, horizontal, vertical, ... } if (moves.individual.length > 0) return true; + for (var line in moves.slides) + if (doesSlideHaveWidth(moves.slides[line])) return true; + /** if (doesSlideHaveWidth(moves.horizontal)) return true; if (doesSlideHaveWidth(moves.vertical)) return true; if (doesSlideHaveWidth(moves.diagonalUp)) return true; if (doesSlideHaveWidth(moves.diagonalDown)) return true; + */ function doesSlideHaveWidth(slide) { // [-Infinity, Infinity] if (!slide) return false; diff --git a/protected-owner/scripts/game/math.js b/protected-owner/scripts/game/math.js index 857450090..8f180fe79 100644 --- a/protected-owner/scripts/game/math.js +++ b/protected-owner/scripts/game/math.js @@ -146,13 +146,17 @@ const math = (function() { return { left, right, bottom, top } } - function getUpDiagonalFromCoords (coords) { - // What is the diagonal? It is equal to 0 - x + y. It is determined by the y-intercept point of the diagonal. - return -coords[0] + coords[1] // -x + y - } - - function getDownDiagonalFromCoords (coords) { - return coords[0] + coords[1]; // x + y + /** + * Uses the calculation of ax + by = c + * c=b*y-intercept so is unique for each line + * Not unique when step can be factored + * eg [2,2] + * @param {number[]} step The gradient + * @param {number[]} coords + * @returns {number} integer c + */ + function getLineFromCoords(step, coords) { + return step[0]*coords[1]-step[1]*coords[0] } /** @@ -787,8 +791,7 @@ const math = (function() { roundPointToNearestGridpoint, boxContainsBox, boxContainsSquare, - getUpDiagonalFromCoords, - getDownDiagonalFromCoords, + getLineFromCoords, deepCopyObject, getKeyFromCoords, getCoordsFromKey, diff --git a/protected-owner/scripts/game/movesets.js b/protected-owner/scripts/game/movesets.js index 33de31339..99cce86ce 100644 --- a/protected-owner/scripts/game/movesets.js +++ b/protected-owner/scripts/game/movesets.js @@ -26,9 +26,15 @@ const movesets = (function() { knights: function () { return { individual: [ - [-2,1],[-1,2],[1,2],[2,1], - [-2,-1],[-1,-2],[1,-2],[2,-1] - ] + //[-2,1],[-1,2],[1,2],[2,1], + //[-2,-1],[-1,-2],[1,-2],[2,-1] + ], + slideMoves: { + '2,1': [-slideLimit, slideLimit], + '2,-1': [-slideLimit, slideLimit], + '1,2': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally + '1,-2': [-slideLimit, slideLimit] + } } }, hawks: function () { @@ -61,33 +67,41 @@ const movesets = (function() { rooks: function () { return { individual: [], - horizontal: [-slideLimit, slideLimit], - vertical: [-slideLimit, slideLimit] + slideMoves: { + '1,0': [-slideLimit, slideLimit], + '0,1': [-slideLimit, slideLimit] + } } }, bishops: function () { return { individual: [], - diagonalUp: [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally - diagonalDown: [-slideLimit, slideLimit] + slideMoves: { + '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally + '1,-1': [-slideLimit, slideLimit] + } } }, queens: function () { return { individual: [], - horizontal: [-slideLimit, slideLimit], - vertical: [-slideLimit, slideLimit], - diagonalUp: [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally - diagonalDown: [-slideLimit, slideLimit] + slideMoves: { + '1,0': [-slideLimit, slideLimit], + '0,1': [-slideLimit, slideLimit], + '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally + '1,-1': [-slideLimit, slideLimit] + } } }, royalQueens: function () { return { individual: [], - horizontal: [-slideLimit, slideLimit], - vertical: [-slideLimit, slideLimit], - diagonalUp: [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally - diagonalDown: [-slideLimit, slideLimit] + slideMoves: { + '1,0': [-slideLimit, slideLimit], + '0,1': [-slideLimit, slideLimit], + '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally + '1,-1': [-slideLimit, slideLimit] + } } }, chancellors: function () { @@ -96,8 +110,10 @@ const movesets = (function() { [-2,1],[-1,2],[1,2],[2,1], [-2,-1],[-1,-2],[1,-2],[2,-1] ], - horizontal: [-slideLimit, slideLimit], - vertical: [-slideLimit, slideLimit] + slideMoves: { + '1,0': [-slideLimit, slideLimit], + '0,1': [-slideLimit, slideLimit] + } } }, archbishops: function () { @@ -106,8 +122,8 @@ const movesets = (function() { [-2,1],[-1,2],[1,2],[2,1], [-2,-1],[-1,-2],[1,-2],[2,-1] ], - diagonalUp: [-slideLimit, slideLimit], - diagonalDown: [-slideLimit, slideLimit] + '1,1': [-slideLimit, slideLimit], + '1,-1': [-slideLimit, slideLimit] } }, amazons: function () { @@ -116,10 +132,12 @@ const movesets = (function() { [-2,1],[-1,2],[1,2],[2,1], [-2,-1],[-1,-2],[1,-2],[2,-1] ], - horizontal: [-slideLimit, slideLimit], - vertical: [-slideLimit, slideLimit], - diagonalUp: [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally - diagonalDown: [-slideLimit, slideLimit] + slideMoves: { + '1,0': [-slideLimit, slideLimit], + '0,1': [-slideLimit, slideLimit], + '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally + '1,-1': [-slideLimit, slideLimit] + } } }, camels: function () { diff --git a/protected-owner/scripts/game/organizedlines.js b/protected-owner/scripts/game/organizedlines.js index 30bcdb858..bbd2d1850 100644 --- a/protected-owner/scripts/game/organizedlines.js +++ b/protected-owner/scripts/game/organizedlines.js @@ -10,7 +10,6 @@ // Module const organizedlines = { - /** * Organizes all the pieces of the specified game into many different lists, * organized in different ways. For example, organized by key `'1,2'`, @@ -43,10 +42,12 @@ const organizedlines = { resetOrganizedLists: function(gamefile) { gamefile.piecesOrganizedByKey = {} - gamefile.piecesOrganizedByRow = {} - gamefile.piecesOrganizedByColumn = {} - gamefile.piecesOrganizedByUpDiagonal = {} - gamefile.piecesOrganizedByDownDiagonal = {} + gamefile.piecesOrganizedByLines = {} + + let lines = gamefile.slideMoves + for (let i = 0; i Date: Wed, 10 Jul 2024 21:59:36 +0100 Subject: [PATCH 02/58] render positive and negative gradients --- protected-owner/scripts/game/highlights.js | 21 +++++++++++---------- protected-owner/scripts/game/movesets.js | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/protected-owner/scripts/game/highlights.js b/protected-owner/scripts/game/highlights.js index 4ad41671e..498913f23 100644 --- a/protected-owner/scripts/game/highlights.js +++ b/protected-owner/scripts/game/highlights.js @@ -299,13 +299,16 @@ const highlights = (function(){ line = line.split(',') if (line[1] == 0 || line[0] == 0) {console.log("A"); continue;} const lineEqua = math.getLineFromCoords(line, coords)/line[0] - const intsect1Tile = math.getIntersectionEntryTile(line[1]/line[0], lineEqua, renderBoundingBox, 'bottomleft') - const intsect2Tile = math.getIntersectionEntryTile(line[1]/line[0], lineEqua, renderBoundingBox, 'topright') - - if (!intsect1Tile) return; // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect2Tile) return; // Bruh + const lineGrad = line[1]/line[0] + const corner1 = lineGrad>0 ? 'bottomleft' : 'topleft' + const corner2 = lineGrad>0 ? 'topleft' : 'bottomright' + const intsect1Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, 'bottomleft') + const intsect2Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, 'topright') + + if (!intsect1Tile) continue; // If there's no intersection point, it's off the screen, don't bother rendering. + if (!intsect2Tile) continue; // Bruh - { // Down moveset + { // Left moveset let startTile = intsect2Tile let endTile = intsect1Tile @@ -331,7 +334,7 @@ const highlights = (function(){ addDataDiagonalVariant(iterateCount, currentX, currentY, -Math.sign(line[0]), -Math.sign(line[1]), [-line[0], -line[1]], r, g, b, a) } - { // Up moveset + { // Rigth moveset let startTile = intsect1Tile let endTile = intsect2Tile @@ -490,17 +493,15 @@ const highlights = (function(){ // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the hop values which are -1 or +1 dependant on the direction we're rendering function addDataDiagonalVariant (iterateCount, currentX, currentY, xHop, yHop, step, r, g, b, a) { - + console.log(iterateCount, currentX, currentY, xHop, yHop, step) for (let i = 0; i < iterateCount; i++) { const endX = currentX + xHop const endY = currentY + yHop - data.push(...bufferdata.getDataQuad_Color3D(currentX, currentY, endX, endY, z, r, g, b, a)) // Prepare for next iteration currentX += step[0] currentY += step[1] - console.log([currentX,currentY]) } } diff --git a/protected-owner/scripts/game/movesets.js b/protected-owner/scripts/game/movesets.js index 99cce86ce..dfebeeba9 100644 --- a/protected-owner/scripts/game/movesets.js +++ b/protected-owner/scripts/game/movesets.js @@ -32,7 +32,7 @@ const movesets = (function() { slideMoves: { '2,1': [-slideLimit, slideLimit], '2,-1': [-slideLimit, slideLimit], - '1,2': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally + '1,2': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding '1,-2': [-slideLimit, slideLimit] } } From 56ade946ec80d482f5a33651397e13703fe3efd9 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Thu, 11 Jul 2024 10:51:25 +0100 Subject: [PATCH 03/58] =?UTF-8?q?Legal=20move=20highlighting=20works?= =?UTF-8?q?=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/game/checkdetection.js | 2 +- protected-owner/scripts/game/highlightline.js | 5 +- protected-owner/scripts/game/highlights.js | 107 ++++-------------- protected-owner/scripts/game/legalmoves.js | 2 +- 4 files changed, 25 insertions(+), 91 deletions(-) diff --git a/protected-owner/scripts/game/checkdetection.js b/protected-owner/scripts/game/checkdetection.js index 2ef5d73e1..e6978346e 100644 --- a/protected-owner/scripts/game/checkdetection.js +++ b/protected-owner/scripts/game/checkdetection.js @@ -254,7 +254,7 @@ const checkdetection = (function(){ return true } - if (true) return; // No sliding movesets to remove + if (true) return; // disabled as cba rn and it keep erroring when testing other elements const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces diff --git a/protected-owner/scripts/game/highlightline.js b/protected-owner/scripts/game/highlightline.js index 336f13f10..a33227d85 100644 --- a/protected-owner/scripts/game/highlightline.js +++ b/protected-owner/scripts/game/highlightline.js @@ -181,6 +181,7 @@ const highlightline = (function(){ point2 = [pieceCoords[0], moveset[1]] } + /** else if (closestPoint.direction === 'diagonalup') { // Calculate the intersection tile of this diagonal with the left/bottom and right/top sides of the screen. @@ -201,8 +202,8 @@ const highlightline = (function(){ point1 = moveset[0] === -Infinity ? intsect1Tile : [moveset[0], pieceCoords[1] + pieceCoords[0] - moveset[0]] point2 = moveset[1] === Infinity ? intsect2Tile : [moveset[1], pieceCoords[1] - (moveset[1] - pieceCoords[0])] - } - + } + */ let tileMouseFingerOver; if (input.getTouchClicked()) { // Set to what the finger tapped above // let touchClickedTile = input.getTouchClickedTile() // { id, x, y } diff --git a/protected-owner/scripts/game/highlights.js b/protected-owner/scripts/game/highlights.js index 498913f23..7a4891f87 100644 --- a/protected-owner/scripts/game/highlights.js +++ b/protected-owner/scripts/game/highlights.js @@ -296,90 +296,33 @@ const highlights = (function(){ function concatData_HighlightedMoves_Diagonals (coords, renderBoundingBox, r, g, b, a) { const legalMoves = selection.getLegalMovesOfSelectedPiece() for (var line in legalMoves.slides) { - line = line.split(',') + line = line.split(',') // can't hash array so conversion to string made, unconverting it + line = [Number(line[0]), Number(line[1])] // More reverting if (line[1] == 0 || line[0] == 0) {console.log("A"); continue;} const lineEqua = math.getLineFromCoords(line, coords)/line[0] const lineGrad = line[1]/line[0] const corner1 = lineGrad>0 ? 'bottomleft' : 'topleft' const corner2 = lineGrad>0 ? 'topleft' : 'bottomright' - const intsect1Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, 'bottomleft') - const intsect2Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, 'topright') + const intsect1Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, corner1) + const intsect2Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, corner2) if (!intsect1Tile) continue; // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect2Tile) continue; // Bruh + if (!intsect2Tile) continue; //bruh - { // Left moveset - let startTile = intsect2Tile - let endTile = intsect1Tile - - // Make sure it doesn't start before the tile right in front of us - if (startTile[0] > coords[0] - 1) startTile = [coords[0] - 1, coords[1] - 1] - let limit = legalMoves.slides[line][0] - - // Make sure it doesn't phase through our move limit - if (endTile[0] < limit) { - endTile[0] = limit - endTile[1] = startTile[1] + limit - startTile[0] - } - - // How many times will we iterate? - let iterateCount = startTile[0] - endTile[0] + 1 - if (iterateCount < 0) iterateCount = 0 - - // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] - - // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, -Math.sign(line[0]), -Math.sign(line[1]), [-line[0], -line[1]], r, g, b, a) - } + if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); + else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a) - { // Rigth moveset - let startTile = intsect1Tile - let endTile = intsect2Tile - - // Make sure it doesn't start before the tile right in front of us - if (startTile[0] < coords[0] + 1) startTile = [coords[0] + 1, coords[1] + 1] - let limit = legalMoves.slides[line][1] - - // Make sure it doesn't phase through our move limit - if (endTile[0] > limit) { - endTile[0] = limit - endTile[1] = startTile[1] + limit - startTile[0] - } - - // How many times will we iterate? - let iterateCount = endTile[0] - startTile[0] + 1 - if (iterateCount < 0) iterateCount = 0 - - // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() - model_Offset[1] - - // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, Math.sign(line[0]), Math.sign(line[1]), line, r, g, b, a) - } } } - function concatData_HighlightedMoves_Diagonal_Up (coords, renderBoundingBox, r, g, b, a) { - const legalMoves = selection.getLegalMovesOfSelectedPiece() - if (!legalMoves.diagonalUp) return; - - // Calculate the intersection tile of this diagonal with the left/bottom and right/top sides of the screen. - const lineEqua = math.getLineFromCoords(coords) // mx + b - const intsect1Tile = math.getIntersectionEntryTile(1, lineEqua, renderBoundingBox, 'bottomleft') - const intsect2Tile = math.getIntersectionEntryTile(1, lineEqua, renderBoundingBox, 'topright') - - if (!intsect1Tile) return; // If there's no intersection point, it's off the screen, don't bother rendering. - + function concatData_HighlightedMoves_Diagonal_Up (coords, intsect1Tile, intsect2Tile, limits, step, r, g, b, a) { { // Down Left moveset let startTile = intsect2Tile let endTile = intsect1Tile // Make sure it doesn't start before the tile right in front of us if (startTile[0] > coords[0] - 1) startTile = [coords[0] - 1, coords[1] - 1] - let diagonalUpLimit = legalMoves.diagonalUp[0] + let diagonalUpLimit = limits[0] // Make sure it doesn't phase through our move limit if (endTile[0] < diagonalUpLimit) { @@ -392,11 +335,11 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + line[0] - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + line[1] - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() + 1 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, -1, -1, r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, -1, -1, [-step[0], -step[1]], r, g, b, a) } { // Up Right moveset @@ -405,7 +348,7 @@ const highlights = (function(){ // Make sure it doesn't start before the tile right in front of us if (startTile[0] < coords[0] + 1) startTile = [coords[0] + 1, coords[1] + 1] - let diagonalUpLimit = legalMoves.diagonalUp[1] + let diagonalUpLimit = limits[1] // Make sure it doesn't phase through our move limit if (endTile[0] > diagonalUpLimit) { @@ -422,28 +365,18 @@ const highlights = (function(){ let currentY = startTile[1] - board.gsquareCenter() - model_Offset[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, step, r, g, b, a) } } - function concatData_HighlightedMoves_Diagonal_Down (coords, renderBoundingBox, r, g, b, a) { - const legalMoves = selection.getLegalMovesOfSelectedPiece() - if (!legalMoves.diagonalDown) return; // Quit if there isn't a diagonal down path. - - // Calculate the intersection tile of this diagonal with the left/top and right/bottom sides of the screen. - const lineEqua = math.getDownDiagonalFromCoords(coords) // mx + b - const intsect1Tile = math.getIntersectionEntryTile(-1, lineEqua, renderBoundingBox, 'topleft') - const intsect2Tile = math.getIntersectionEntryTile(-1, lineEqua, renderBoundingBox, 'bottomright') - - if (!intsect1Tile) return; // If there's no intersection point, it's off the screen, don't bother rendering. - + function concatData_HighlightedMoves_Diagonal_Down (coords, intsect1Tile, intsect2Tile, limits, step , r, g, b, a) { { // Up Left moveset let startTile = intsect2Tile let endTile = intsect1Tile // Make sure it doesn't start before the tile right in front of us if (startTile[0] > coords[0] - 1) startTile = [coords[0] - 1, coords[1] + 1] - let diagonalDownLimit = legalMoves.diagonalDown[0] + let diagonalDownLimit = limits[0] // Make sure it doesn't phase through our move limit if (endTile[0] < diagonalDownLimit) { @@ -460,7 +393,7 @@ const highlights = (function(){ let currentY = startTile[1] - board.gsquareCenter() - model_Offset[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, -1, +1, r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, -1, +1, [-step[0], -step[1]], r, g, b, a) } { // Down Right moveset @@ -470,7 +403,7 @@ const highlights = (function(){ // Make sure it doesn't start before the tile right in front of us if (startTile[0] < coords[0] + 1) startTile = [coords[0] + 1, coords[1] - 1] - let diagonalDownLimit = legalMoves.diagonalDown[1] + let diagonalDownLimit = limits[1] // Make sure it doesn't phase through our move limit if (endTile[0] > diagonalDownLimit) { @@ -487,7 +420,7 @@ const highlights = (function(){ let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, -1, r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, +1, -1, step, r, g, b, a) } } @@ -498,7 +431,7 @@ const highlights = (function(){ const endX = currentX + xHop const endY = currentY + yHop data.push(...bufferdata.getDataQuad_Color3D(currentX, currentY, endX, endY, z, r, g, b, a)) - + console.log(currentX,currentY) // Prepare for next iteration currentX += step[0] currentY += step[1] diff --git a/protected-owner/scripts/game/legalmoves.js b/protected-owner/scripts/game/legalmoves.js index c6258a49f..0d98cb95c 100644 --- a/protected-owner/scripts/game/legalmoves.js +++ b/protected-owner/scripts/game/legalmoves.js @@ -241,7 +241,7 @@ const legalmoves = (function(){ } for (var line in legalMoves.slides) { - line=line.split(',') + line=line.split(',') // can't hash array so conversion to string made, unconverting it let limits = legalMoves.slides[line]; let selectedPieceLine = math.getLineFromCoords(line,startCoords); let clickedCoordsLine = math.getLineFromCoords(line,endCoords); From 2df09f0259636b7c215383e2bc1892b1f5a38f1b Mon Sep 17 00:00:00 2001 From: Idonotus Date: Thu, 11 Jul 2024 11:20:07 +0100 Subject: [PATCH 04/58] Now highlighting is actually alligned --- protected-owner/scripts/game/highlights.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/protected-owner/scripts/game/highlights.js b/protected-owner/scripts/game/highlights.js index 7a4891f87..a4cdef011 100644 --- a/protected-owner/scripts/game/highlights.js +++ b/protected-owner/scripts/game/highlights.js @@ -335,8 +335,8 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() -step[0] + 2 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() -step[1] + 2 - model_Offset[1] // Generate data of each highlighted square addDataDiagonalVariant(iterateCount, currentX, currentY, -1, -1, [-step[0], -step[1]], r, g, b, a) @@ -361,8 +361,8 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() + step[0] - 1 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() + step[1] - 1 - model_Offset[1] // Generate data of each highlighted square addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, step, r, g, b, a) @@ -389,8 +389,8 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() - step[0] + 2 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() - step[1] - 1 - model_Offset[1] // Generate data of each highlighted square addDataDiagonalVariant(iterateCount, currentX, currentY, -1, +1, [-step[0], -step[1]], r, g, b, a) @@ -416,8 +416,8 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + 1 - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() + step[0] - 1 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() + step[1] + 2 - model_Offset[1] // Generate data of each highlighted square addDataDiagonalVariant(iterateCount, currentX, currentY, +1, -1, step, r, g, b, a) From 57acceeb1451ae40eb76527909f8f69a366cf8c5 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Thu, 11 Jul 2024 20:18:19 +0100 Subject: [PATCH 05/58] added knight rider and fixed slope discrepancy --- .../scripts/game/chess/formatconverter.js | 1 + src/client/scripts/game/misc/math.js | 29 +++++++++++++++++++ .../scripts/game/rendering/highlights.js | 22 ++++++-------- src/client/scripts/game/rendering/options.js | 2 +- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/client/scripts/game/chess/formatconverter.js b/src/client/scripts/game/chess/formatconverter.js index 4c08576fa..b1053b65c 100644 --- a/src/client/scripts/game/chess/formatconverter.js +++ b/src/client/scripts/game/chess/formatconverter.js @@ -31,6 +31,7 @@ const formatconverter = (function() { "centaursW": "CE", "centaursB": "ce", "royalQueensW": "RQ", "royalQueensB": "rq", "royalCentaursW": "RC", "royalCentaursB": "rc", + "knightRidersW": "NR", "knightRidersB": "nr", "obstaclesN": "ob", "voidsN": "vo" }; diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 8f180fe79..9fbd8c355 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -333,6 +333,34 @@ const math = (function() { return (value / (camera.getScreenBoundingBox(false).top - camera.getScreenBoundingBox(false).bottom)) * camera.getCanvasHeightVirtualPixels() } + function getLineIntersectionEntryTile (ax, by, c, boundingBox, corner) { + const { left, right, top, bottom } = boundingBox; + + // Check for intersection with left side of rectangle + if (corner.endsWith('left')) { + const yIntersectLeft = ((left * ax) + c) / by; + if (yIntersectLeft >= bottom && yIntersectLeft <= top) return [left, yIntersectLeft] + } + + // Check for intersection with bottom side of rectangle + if (corner.startsWith('bottom')) { + const xIntersectBottom = (c - (bottom * by)) / ax; + if (xIntersectBottom >= left && xIntersectBottom <= right) return [xIntersectBottom, bottom] + } + + // Check for intersection with right side of rectangle + if (corner.endsWith('right')) { + const yIntersectRight = ((right * ax) + c) / by; + if (yIntersectRight >= bottom && yIntersectRight <= top) return [right, yIntersectRight]; + } + + // Check for intersection with top side of rectangle + if (corner.startsWith('top')) { + const xIntersectTop = (c - (top * by)) / ax; + if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; + } + } + // Returns point, if there is one, of a line with specified slope "b" intersection screen edge on desired corner function getIntersectionEntryTile (slope, b, boundingBox, corner) { // corner: "topright"/"bottomright"... const { left, right, top, bottom } = boundingBox; @@ -805,6 +833,7 @@ const math = (function() { getBoundingBoxOfBoard, convertPixelsToWorldSpace_Virtual, convertWorldSpaceToPixels_Virtual, + getLineIntersectionEntryTile, getIntersectionEntryTile, convertWorldSpaceToGrid, euclideanDistance, diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index a4cdef011..0d11cab4c 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -298,17 +298,15 @@ const highlights = (function(){ for (var line in legalMoves.slides) { line = line.split(',') // can't hash array so conversion to string made, unconverting it line = [Number(line[0]), Number(line[1])] // More reverting - if (line[1] == 0 || line[0] == 0) {console.log("A"); continue;} - const lineEqua = math.getLineFromCoords(line, coords)/line[0] + if (line[1] == 0 || line[0] == 0) {continue;} + const lineEqua = math.getLineFromCoords(line, coords) const lineGrad = line[1]/line[0] - const corner1 = lineGrad>0 ? 'bottomleft' : 'topleft' - const corner2 = lineGrad>0 ? 'topleft' : 'bottomright' - const intsect1Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, corner1) - const intsect2Tile = math.getIntersectionEntryTile(lineGrad, lineEqua, renderBoundingBox, corner2) - - if (!intsect1Tile) continue; // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect2Tile) continue; //bruh - + const corner2 = lineGrad>0 ? 'bottomleft' : 'topleft' + const corner1 = lineGrad>0 ? 'topleft' : 'bottomright' + const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1) + const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2) + if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. + if (!intsect1Tile || !intsect2Tile) {console.log(intsect1Tile,intsect2Tile, line, renderBoundingBox);} // This should not happen if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a) @@ -337,7 +335,6 @@ const highlights = (function(){ // Init starting coords of the data, this will increment by 1 every iteration let currentX = startTile[0] - board.gsquareCenter() -step[0] + 2 - model_Offset[0] let currentY = startTile[1] - board.gsquareCenter() -step[1] + 2 - model_Offset[1] - // Generate data of each highlighted square addDataDiagonalVariant(iterateCount, currentX, currentY, -1, -1, [-step[0], -step[1]], r, g, b, a) } @@ -426,12 +423,11 @@ const highlights = (function(){ // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the hop values which are -1 or +1 dependant on the direction we're rendering function addDataDiagonalVariant (iterateCount, currentX, currentY, xHop, yHop, step, r, g, b, a) { - console.log(iterateCount, currentX, currentY, xHop, yHop, step) + if (currentX===NaN || currentY===NaN) throw new Error('CurrentX or CurrentY (${CurrentX},${CurrentY}) are NaN') for (let i = 0; i < iterateCount; i++) { const endX = currentX + xHop const endY = currentY + yHop data.push(...bufferdata.getDataQuad_Color3D(currentX, currentY, endX, endY, z, r, g, b, a)) - console.log(currentX,currentY) // Prepare for next iteration currentX += step[0] currentY += step[1] diff --git a/src/client/scripts/game/rendering/options.js b/src/client/scripts/game/rendering/options.js index 3eef40f34..4eae74e09 100644 --- a/src/client/scripts/game/rendering/options.js +++ b/src/client/scripts/game/rendering/options.js @@ -10,7 +10,7 @@ const options = (function() { // When enabled, your view is expanded to show what you normally can't see beyond the edge of the screen. // Useful for making sure rendering methods are as expected. - let debugMode = false // Must be toggled by calling toggleDeveloperMode() + let debugMode = true // Must be toggled by calling toggleDeveloperMode() let navigationVisible = true From 88cd83d44e45e2b58a5377065f6ea9a22fe1c756 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 12 Jul 2024 10:43:46 +0100 Subject: [PATCH 06/58] some checkdetection and stop highlights crash --- .../scripts/game/chess/checkdetection.js | 138 ++++++------------ .../scripts/game/chess/organizedlines.js | 5 +- src/client/scripts/game/misc/math.js | 27 ++++ .../scripts/game/rendering/highlights.js | 2 +- src/client/scripts/game/rendering/options.js | 2 +- 5 files changed, 75 insertions(+), 99 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index e6978346e..cb3cc6b32 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -44,20 +44,18 @@ const checkdetection = (function(){ if (!coord) return false; if (colorOfFriendly !== 'white' && colorOfFriendly !== 'black') throw new Error(`Cannot detect if an opponent is attacking the square of the team of color ${colorOfFriendly}!`) - let atleast1Attacker = false; - // How do we find out if this square is attacked? // 1. We check every square within a 3 block radius to see if there's any attacking pieces. - if (doesVicinityAttackSquare(gamefile, coord, colorOfFriendly, attackers)) atleast1Attacker = true; + if (doesVicinityAttackSquare(gamefile, coord, colorOfFriendly, attackers)) return true; // What about pawns? Could they capture us? - if (doesPawnAttackSquare(gamefile, coord, colorOfFriendly, attackers)) atleast1Attacker = true; + if (doesPawnAttackSquare(gamefile, coord, colorOfFriendly, attackers)) return true; // 2. We check every orthogonal and diagonal to see if there's any attacking pieces. - if (doesSlideAttackSquare(gamefile, coord, colorOfFriendly, attackers)) atleast1Attacker = true; + if (doesSlideAttackSquare(gamefile, coord, colorOfFriendly, attackers)) return true; - return atleast1Attacker; // Being attacked if true + return false; // Being attacked if true } // Checks to see if any piece within a 3-block radius can capture. Ignores sliding movesets. @@ -124,32 +122,15 @@ const checkdetection = (function(){ * @returns */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { - - let atleast1Attacker = false; - const line = gamefile.slideMoves; + const lines = gamefile.slideMoves; for (let i=0; i<0; i++) { - if (doesLineAttackSquare(gamefile,gamefile.piecesOrganizedByLines[line],line,color,attackers)) return true; + const line = lines[i] + const strline = math.getKeyFromCoords(line) + const key = math.getLineFromCoords(line, coords) + if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) return true; } - /** - // Horizontal - const row = gamefile.piecesOrganizedByRow[coords[1]]; - if (doesLineAttackSquare(gamefile, row, 'horizontal', coords, color, attackers)) atleast1Attacker = true; - - // Vertical - const column = gamefile.piecesOrganizedByColumn[coords[0]]; - if (doesLineAttackSquare(gamefile, column, 'vertical', coords, color, attackers)) atleast1Attacker = true; - - // Diagonal Up - let key = math.getUpDiagonalFromCoords(coords) - let diag = gamefile.piecesOrganizedByUpDiagonal[key]; - if (doesLineAttackSquare(gamefile, diag, 'diagonalup', coords, color, attackers)) atleast1Attacker = true; - - // Diagonal Down - key = math.getDownDiagonalFromCoords(coords) - diag = gamefile.piecesOrganizedByDownDiagonal[key]; - if (doesLineAttackSquare(gamefile, diag, 'diagonaldown', coords, color, attackers)) atleast1Attacker = true; - */ - return atleast1Attacker; + + return false; } // Returns true if a piece on the specified line can capture on that square @@ -166,12 +147,12 @@ const checkdetection = (function(){ if (thisPieceColor === 'neutral') continue; const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - //const lineIsVertical = direction === 'vertical' ? true : false; + const lineIsVertical = direction[0]==0 /**const moveset = direction === 'horizontal' ? thisPieceMoveset.horizontal : direction === 'vertical' ? thisPieceMoveset.vertical : direction === 'diagonalup' ? thisPieceMoveset.diagonalUp : thisPieceMoveset.diagonalDown;*/ - const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction[0]==0, thisPieceMoveset.slideMoves[line], thisPiece.coords, thisPieceColor) + const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, thisPieceMoveset.slideMoves[line], thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} @@ -181,6 +162,7 @@ const checkdetection = (function(){ if (isWithinMoveset) { if (attackers) appendAttackerToList(attackers, { coords: thisPiece.coords, slidingCheck: true }) return true; // There'll never be more than 1 checker on the same line + // There are 2 sides? s<-k->s } } @@ -243,18 +225,8 @@ const checkdetection = (function(){ // Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { - // O(1) - function isEmpty(obj) { - for (var prop in obj) { - if (Object.prototype.hasOwnProperty.call(obj, prop)) { - return false; - } - } - - return true - } - if (true) return; // disabled as cba rn and it keep erroring when testing other elements + if (math.isEmpty) return; const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces @@ -325,66 +297,42 @@ const checkdetection = (function(){ function removeSlidingMovesThatOpenDiscovered (gamefile, moves, kingCoords, pieceSelected, color) { const selectedPieceCoords = pieceSelected.coords; + let sameLines = []; + for (const line of gamefile.slideMoves) { // Only check current possible slides + if (math.getLineFromCoords(line, kingCoords) !== math.getLineFromCoords(line, selectedPieceCoords)) continue; + sameLines.push(line); + }; - const sameRow = kingCoords[1] === selectedPieceCoords[1]; - const sameColumn = kingCoords[0] === selectedPieceCoords[0]; - - const kingDiagUp = math.getUpDiagonalFromCoords(kingCoords); - const selectedPieceDiagUp = math.getUpDiagonalFromCoords(selectedPieceCoords); - const sameUpDiagonal = kingDiagUp === selectedPieceDiagUp; - - const kingDiagDown = math.getDownDiagonalFromCoords(kingCoords); - const selectedPieceDiagDown = math.getDownDiagonalFromCoords(selectedPieceCoords); - const sameDownDiagonal = kingDiagDown === selectedPieceDiagDown; // If not sharing any common line, there's no way we can open a discovered - if (!sameRow && !sameColumn && !sameUpDiagonal && !sameDownDiagonal) return; + if (sameLines.length===0) return; // Delete the piece, and add it back when we're done! const deletedPiece = math.deepCopyObject(pieceSelected); - movepiece.deletePiece(gamefile, pieceSelected, { updateData: false }) - - if (sameRow) { - // Does a movement off the row expose a discovered check? - const row = gamefile.piecesOrganizedByRow[kingCoords[1]] - const opensDiscovered = doesLineAttackSquare(gamefile, row, 'horizontal', kingCoords, color, []) - if (opensDiscovered) { // Remove the selected piece's vertical, and both diagonal movesets - moves.vertical = undefined; - moves.diagonalUp = undefined; - moves.diagonalDown = undefined; - } - } - - else if (sameColumn) { - const column = gamefile.piecesOrganizedByColumn[kingCoords[0]] - const opensDiscovered = doesLineAttackSquare(gamefile, column, 'vertical', kingCoords, color, []) - if (opensDiscovered) { // Remove the selected piece's horizontal, and both diagonal movesets - moves.horizontal = undefined; - moves.diagonalUp = undefined; - moves.diagonalDown = undefined; - } - } - - else if (sameUpDiagonal) { - const key = math.getUpDiagonalFromCoords(kingCoords) - const diagUp = gamefile.piecesOrganizedByUpDiagonal[key] - const opensDiscovered = doesLineAttackSquare(gamefile, diagUp, 'diagonalup', kingCoords, color, []) - if (opensDiscovered) { // Remove the selected piece's horizontal, vertical, and down diagonal movesets - moves.horizontal = undefined; - moves.vertical = undefined; - moves.diagonalDown = undefined; - } + movepiece.deletePiece(gamefile, pieceSelected, { updateData: false }); + + let checklines = []; + for (const line of sameLines) { + const strline = math.getKeyFromCoords(line); + const key = math.getLineFromCoords(line,kingCoords); + const opensDiscovered = doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], 'horizontal', kingCoords, color, []) + if (!opensDiscovered) continue; + checklines.push(line); } - - else { // sameDownDiagonal === true - const key = math.getDownDiagonalFromCoords(kingCoords) - const diagDown = gamefile.piecesOrganizedByDownDiagonal[key] - const opensDiscovered = doesLineAttackSquare(gamefile, diagDown, 'diagonaldown', kingCoords, color, []) - if (opensDiscovered) { // Remove the selected piece's horizontal, vertical, and up diagonal movesets - moves.horizontal = undefined; - moves.vertical = undefined; - moves.diagonalUp = undefined; + const tempslides = {} + if (checklines.length > 1) { + if (math.areLinesCollinear(checklines)) { + for (const line of checklines) { + const strline = math.getKeyFromCoords(line) + tempslides[strline] = moves.slideMoves[strline] + } + } else { + // Cannot slide to block all attack lines so blank the slides + // Could probably blank regular attacks too } + } else if (checklines.length === 1) { + const strline = math.getKeyFromCoords(checklines[0]) + tempslides[strline] = moves.slideMoves[strline] } // Add the piece back with the EXACT SAME index it had before!! diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index bbd2d1850..842f6961e 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -68,9 +68,10 @@ const organizedlines = { for (let i = 0; i 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a) diff --git a/src/client/scripts/game/rendering/options.js b/src/client/scripts/game/rendering/options.js index 4eae74e09..3eef40f34 100644 --- a/src/client/scripts/game/rendering/options.js +++ b/src/client/scripts/game/rendering/options.js @@ -10,7 +10,7 @@ const options = (function() { // When enabled, your view is expanded to show what you normally can't see beyond the edge of the screen. // Useful for making sure rendering methods are as expected. - let debugMode = true // Must be toggled by calling toggleDeveloperMode() + let debugMode = false // Must be toggled by calling toggleDeveloperMode() let navigationVisible = true From 3acb519937d18d81b098ebfd0514d41e8667003b Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 12 Jul 2024 12:31:42 +0100 Subject: [PATCH 07/58] general fixes + prep slide tacking --- .../scripts/game/chess/checkdetection.js | 22 +++++----- src/client/scripts/game/chess/gamefile.js | 5 ++- src/client/scripts/game/chess/legalmoves.js | 41 ++----------------- .../scripts/game/chess/organizedlines.js | 6 +-- src/client/scripts/game/chess/variant.js | 2 +- .../scripts/game/rendering/highlights.js | 7 ++-- 6 files changed, 24 insertions(+), 59 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index cb3cc6b32..a3cc1bfb0 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -122,9 +122,8 @@ const checkdetection = (function(){ * @returns */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { - const lines = gamefile.slideMoves; - for (let i=0; i<0; i++) { - const line = lines[i] + for (const line of gamefile.startSnapshot.slideMovesPossible) { + console.log(line) const strline = math.getKeyFromCoords(line) const key = math.getLineFromCoords(line, coords) if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) return true; @@ -138,7 +137,7 @@ const checkdetection = (function(){ function doesLineAttackSquare(gamefile, line, direction, coords, colorOfFriendlys, attackers) { if (!line) return false; // No line, no pieces to attack - + const lineIsVertical = direction[0]==0 for (let a = 0; a < line.length; a++) { // { coords, type } const thisPiece = line[a]; @@ -147,12 +146,11 @@ const checkdetection = (function(){ if (thisPieceColor === 'neutral') continue; const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - const lineIsVertical = direction[0]==0 - /**const moveset = direction === 'horizontal' ? thisPieceMoveset.horizontal - : direction === 'vertical' ? thisPieceMoveset.vertical - : direction === 'diagonalup' ? thisPieceMoveset.diagonalUp - : thisPieceMoveset.diagonalDown;*/ - const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, thisPieceMoveset.slideMoves[line], thisPiece.coords, thisPieceColor) + + if (!thisPieceMoveset.slideMoves) continue; + const moveset = thisPieceMoveset.slideMoves[line]; + if (!moveset) continue; + const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} @@ -226,7 +224,7 @@ const checkdetection = (function(){ // Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { - if (math.isEmpty) return; + if (math.isEmpty(moves.slides)) return; const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces @@ -298,7 +296,7 @@ const checkdetection = (function(){ const selectedPieceCoords = pieceSelected.coords; let sameLines = []; - for (const line of gamefile.slideMoves) { // Only check current possible slides + for (const line of gamefile.startSnapshot.slideMovesPossible) { // Only check current possible slides if (math.getLineFromCoords(line, kingCoords) !== math.getLineFromCoords(line, selectedPieceCoords)) continue; sameLines.push(line); }; diff --git a/src/client/scripts/game/chess/gamefile.js b/src/client/scripts/game/chess/gamefile.js index 80853d34f..fca729748 100644 --- a/src/client/scripts/game/chess/gamefile.js +++ b/src/client/scripts/game/chess/gamefile.js @@ -49,6 +49,9 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) /** The bounding box surrounding the starting position, without padding. * @type {BoundingBox} */ box: undefined, + /** Possible slide mopves*/ + slideMovesPossible: [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]], + } this.gameRules = { @@ -69,8 +72,6 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) this.piecesOrganizedByKey = undefined; /** Pieces organized by lines: `{ '1,0' { 2:[{type:'queensW',coords:[1,2]}] } }` */ this.piecesOrganizedByLines = undefined; - /** Legal slides*/ - this.slideMoves = [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]]; /** The object that contains the buffer model to render the pieces */ this.mesh = { diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 0d98cb95c..0f07c1b2e 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -97,7 +97,7 @@ const legalmoves = (function(){ // Legal sliding moves if (thisPieceMoveset.slideMoves) { - let lines = gamefile.slideMoves; + let lines = gamefile.startSnapshot.slideMovesPossible; for (let i=0; i=limits[0] && endCoords[1]<=limits[1] && line[0]==0) return true; } } - /** - // Do one of the horizontal moves match? - const horizontal = legalMoves.horizontal; - if (horizontal && endCoords[1] == startCoords[1]) { - // Compare the clicked x tile with this horizontal moveset - if (endCoords[0] >= horizontal[0] && endCoords[0] <= horizontal[1]) return true; - } - - // Do one of the vertical moves match? - const vertical = legalMoves.vertical; - if (vertical && endCoords[0] == startCoords[0]) { - // Compare the clicked y tile with this vertical moveset - if (endCoords[1] >= vertical[0] && endCoords[1] <= vertical[1]) return true; - } - - // Do one of the up-diagonal moves match? - const diagonalUp = legalMoves.diagonalUp; - let selectedPieceDiagonal = math.getUpDiagonalFromCoords(startCoords); - let clickedCoordsDiagonal = math.getUpDiagonalFromCoords(endCoords); - if (diagonalUp && selectedPieceDiagonal == clickedCoordsDiagonal) { - // Compare the clicked x tile with this diagonal moveset - if (endCoords[0] >= diagonalUp[0] && endCoords[0] <= diagonalUp[1]) return true; - } - - // Do one of the down-diagonal moves match? - const diagonalDown = legalMoves.diagonalDown; - selectedPieceDiagonal = math.getDownDiagonalFromCoords(startCoords); - clickedCoordsDiagonal = math.getDownDiagonalFromCoords(endCoords); - if (diagonalDown && selectedPieceDiagonal == clickedCoordsDiagonal) { - // Compare the clicked x tile with this diagonal moveset - if (endCoords[0] >= diagonalDown[0] && endCoords[0] <= diagonalDown[1]) return true; - } - */ return false; } diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index 842f6961e..a6ae5c7e1 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -44,7 +44,7 @@ const organizedlines = { gamefile.piecesOrganizedByKey = {} gamefile.piecesOrganizedByLines = {} - let lines = gamefile.slideMoves + let lines = gamefile.startSnapshot.slideMovesPossible for (let i = 0; i Date: Fri, 12 Jul 2024 16:13:49 +0100 Subject: [PATCH 08/58] Check detection and blocking --- .../scripts/game/chess/checkdetection.js | 140 +++--------------- src/client/scripts/game/misc/math.js | 18 +++ 2 files changed, 39 insertions(+), 119 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index a3cc1bfb0..c1a6db08d 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -147,9 +147,9 @@ const checkdetection = (function(){ const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - if (!thisPieceMoveset.slideMoves) continue; - const moveset = thisPieceMoveset.slideMoves[line]; - if (!moveset) continue; + if (!thisPieceMoveset.slideMoves) {console.log(thisPiece); continue}; + const moveset = thisPieceMoveset.slideMoves[math.getKeyFromCoords(direction)]; + if (!moveset) {console.log(thisPiece, thisPieceMoveset.slideMoves); continue}; const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! @@ -341,127 +341,29 @@ const checkdetection = (function(){ function appendBlockingMoves (square1, square2, moves, coords) { // coords is of the selected piece // What is the line between our king and the attacking piece? - let direction; - if (square1[1] === square2[1]) direction = 'horizontal'; - else if (square1[0] === square2[0]) direction = 'vertical'; - else if (square1[0] > square2[0] && square1[1] > square2[1] - || square2[0] > square1[0] && square2[1] > square1[1]) direction = 'diagonalUp' - else direction = 'diagonalDown' - - const upDiag = math.getUpDiagonalFromCoords(coords) - const downDiag = math.getDownDiagonalFromCoords(coords) - - function appendBlockPointIfLegal (coord, value1, value2, blockPoint) { - if (coord > value1 && coord < value2 - || coord > value2 && coord < value1) { - // Can our piece legally move there? - if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! - } - } - - if (direction === 'horizontal') { - - // Does our selected piece's vertical moveset intersect this line segment? - if (moves.vertical) { - const blockPoint = [coords[0], square1[1]]; - appendBlockPointIfLegal(coords[0], square1[0], square2[0], blockPoint) - } - - // Does our selected piece's diagonalUp moveset intersect this line segment? - if (moves.diagonalUp) { - // When y is the y level of our squares, what is the x intersection point? - const xIntsect = -upDiag + square1[1]; - const blockPoint = [xIntsect, square1[1]] - appendBlockPointIfLegal(xIntsect, square1[0], square2[0], blockPoint) - } - - // Does our selected piece's diagonalDown moveset intersect this line segment? - if (moves.diagonalDown) { - const xIntsect = downDiag - square1[1]; - const blockPoint = [xIntsect, square1[1]] - appendBlockPointIfLegal(xIntsect, square1[0], square2[0], blockPoint) - } - } - - else if (direction === 'vertical') { - - // Does our selected piece's horizontal moveset intersect this line segment? - if (moves.horizontal) { - const blockPoint = [square1[0], coords[1]] - appendBlockPointIfLegal(coords[1], square1[1], square2[1], blockPoint) - } - - if (moves.diagonalUp) { - const yIntsect = upDiag + square1[0]; - const blockPoint = [square1[0], yIntsect] - appendBlockPointIfLegal(yIntsect, square1[1], square2[1], blockPoint) - } + let direction = [square1[0] - square2[0], square1[1] - square2[1]]; - if (moves.diagonalDown) { - const yIntsect = downDiag - square1[0]; - const blockPoint = [square1[0], yIntsect] - appendBlockPointIfLegal(yIntsect, square1[1], square2[1], blockPoint) - } + const box = { + left: Math.min(square1[0],square2[0]), + right: Math.max(square1[0],square2[0]), + top: Math.min(square1[1],square2[1]), + bottom: Math.max(square1[1],square2[1]) } - else if (direction === 'diagonalUp') { - - if (moves.vertical) { - const xDiff = coords[0] - square1[0]; - const intsectY = square1[1] + xDiff; - const blockPoint = [coords[0], intsectY] - appendBlockPointIfLegal(coords[0], square1[0], square2[0], blockPoint) - } - - if (moves.horizontal) { - const yDiff = coords[1] - square1[1]; - const intsectX = square1[0] + yDiff; - const blockPoint = [intsectX, coords[1]] - appendBlockPointIfLegal(coords[1], square1[1], square2[1], blockPoint) - } - - out: if (moves.diagonalDown) { - const squaresUpDiag = math.getUpDiagonalFromCoords(square1) - // -1x + downDiag = 1x + squaresUpDiag Set them equal to find their intersection point - // 2x = downDiag - squaresUpDiag - // x = (downDiag - squaresUpDiag) / 2 - const xIntsect = (downDiag - squaresUpDiag) / 2; - if (!Number.isInteger(xIntsect)) break out; // Wrong color square diagonal - // y = -1x + downDiag - const yIntsect = downDiag - xIntsect; - const blockPoint = [xIntsect, yIntsect] - appendBlockPointIfLegal(xIntsect, square1[0], square2[0], blockPoint) - } + function appendBlockPointIfLegal (blockPoint) { + if (!math.isAproxEqual(blockPoint[0],Math.round(blockPoint[0])) || + !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) return; // Block is off grid so probably not valid + if (math.boxContainsSquare(box, blockPoint)) return; + // Can our piece legally move there? + if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! } - else { // direction === 'diagonalDown' - - if (moves.vertical) { - const xDiff = coords[0] - square1[0]; - const intsectY = square1[1] - xDiff; - const blockPoint = [coords[0], intsectY] - appendBlockPointIfLegal(coords[0], square1[0], square2[0], blockPoint) - } - - if (moves.horizontal) { - const yDiff = coords[1] - square1[1]; - const intsectX = square1[0] - yDiff; - const blockPoint = [intsectX, coords[1]] - appendBlockPointIfLegal(coords[1], square1[1], square2[1], blockPoint) - } - - out: if (moves.diagonalUp) { - const squaresDownDiag = math.getDownDiagonalFromCoords(square1) - // 1x + upDiag = -1x + squaresDownDiag Set them equal to find their intersection point - // 2x = squaresDownDiag - upDiag - // x = (squaresDownDiag - upDiag) / 2 - const xIntsect = (squaresDownDiag - upDiag) / 2; - if (!Number.isInteger(xIntsect)) break out; // Wrong color square diagonal - // y = 1x + upDiag - const yIntsect = xIntsect + upDiag; - const blockPoint = [xIntsect, yIntsect] - appendBlockPointIfLegal(xIntsect, square1[0], square2[0], blockPoint) - } + for (const linestr in moves.slides) { + const line = math.getCoordsFromKey(linestr) + const c1 = math.getLineFromCoords(line, coords) + const c2 = math.getLineFromCoords(direction,square2) + const blockPoint = math.getLineIntersection(line[0], line[1], c1, direction[0], direction[1], c2) + appendBlockPointIfLegal(blockPoint) } } diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 490543596..bf53b4007 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -27,6 +27,22 @@ const math = (function() { return (value & (value - 1)) === 0; } + function isAproxEqual(a, b, epsilon) { + if (epsilon == null) { + epsilon = 0.001; + } + return Math.abs(a-b) Date: Fri, 12 Jul 2024 18:25:35 +0100 Subject: [PATCH 09/58] Allow for any line to slide correctly --- .../scripts/game/chess/checkdetection.js | 21 ++++++++++--------- src/client/scripts/game/chess/legalmoves.js | 6 +++--- .../scripts/game/chess/organizedlines.js | 4 ++-- .../scripts/game/chess/specialdetect.js | 3 ++- src/client/scripts/game/misc/math.js | 8 +++++++ .../scripts/game/rendering/highlights.js | 2 +- 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index c1a6db08d..70baf462b 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -123,9 +123,8 @@ const checkdetection = (function(){ */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { for (const line of gamefile.startSnapshot.slideMovesPossible) { - console.log(line) const strline = math.getKeyFromCoords(line) - const key = math.getLineFromCoords(line, coords) + const key = math.getKeyFromLine(line, coords) if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) return true; } @@ -147,9 +146,9 @@ const checkdetection = (function(){ const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - if (!thisPieceMoveset.slideMoves) {console.log(thisPiece); continue}; + if (!thisPieceMoveset.slideMoves) {continue}; const moveset = thisPieceMoveset.slideMoves[math.getKeyFromCoords(direction)]; - if (!moveset) {console.log(thisPiece, thisPieceMoveset.slideMoves); continue}; + if (!moveset) {continue}; const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! @@ -297,7 +296,7 @@ const checkdetection = (function(){ const selectedPieceCoords = pieceSelected.coords; let sameLines = []; for (const line of gamefile.startSnapshot.slideMovesPossible) { // Only check current possible slides - if (math.getLineFromCoords(line, kingCoords) !== math.getLineFromCoords(line, selectedPieceCoords)) continue; + if (math.getKeyFromLine(line, kingCoords) !== math.getKeyFromLine(line, selectedPieceCoords)) continue; sameLines.push(line); }; @@ -312,7 +311,7 @@ const checkdetection = (function(){ let checklines = []; for (const line of sameLines) { const strline = math.getKeyFromCoords(line); - const key = math.getLineFromCoords(line,kingCoords); + const key = math.getKeyFromLine(line,kingCoords); const opensDiscovered = doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], 'horizontal', kingCoords, color, []) if (!opensDiscovered) continue; checklines.push(line); @@ -350,12 +349,14 @@ const checkdetection = (function(){ bottom: Math.max(square1[1],square2[1]) } - function appendBlockPointIfLegal (blockPoint) { + function appendBlockPointIfLegal (blockPoint,line) { if (!math.isAproxEqual(blockPoint[0],Math.round(blockPoint[0])) || !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) return; // Block is off grid so probably not valid - if (math.boxContainsSquare(box, blockPoint)) return; - // Can our piece legally move there? - if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! + blockPoint=[Math.round(blockPoint[0]), Math.round(blockPoint[1])] + if (!math.boxContainsSquare(box, blockPoint)) return; + if (math.getKeyFromLine(line,blockPoint)!==math.getKeyFromLine(line, coords)) return; // stop line multiples being annoying + // Can our piece legally move there? + if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! } for (const linestr in moves.slides) { diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 0f07c1b2e..135d699e3 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -101,7 +101,7 @@ const legalmoves = (function(){ for (let i=0; i=limits[0] && endCoords[0]<=limits[1] && line[0]!=0) return true; else if (endCoords[1]>=limits[0] && endCoords[1]<=limits[1] && line[0]==0) return true; diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index a6ae5c7e1..8bad6baf1 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -67,7 +67,7 @@ const organizedlines = { let lines = gamefile.startSnapshot.slideMovesPossible for (let i = 0; i Date: Fri, 12 Jul 2024 20:30:01 +0100 Subject: [PATCH 10/58] jank auto slide allocation --- src/client/scripts/game/chess/variant.js | 30 +++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index 7a3dc971b..612f9c0ef 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -44,6 +44,35 @@ const variant = (function() { else initStartSnapshotAndGamerules(gamefile, metadata) // Default (built-in variant, not pasted) initPieceMovesets(gamefile) + + initSlideMoves(gamefile) + } + + /** + * + * @param {gamefile} gamefile + */ + function initSlideMoves(gamefile) { + gamefile.startSnapshot.slideMovesPossible = getPossibleSlides(gamefile.pieceMovesets, gamefile.startSnapshot.position) + } + + function getPossibleSlides(movesets, position) { + const teamtypes = new Set(Object.values(position)); // Make a set of all pieces in game + const rawtypes = new Set(); + for (const tpiece of teamtypes) { + rawtypes.add(math.trimWorBFromType(tpiece)); // Make a set wit the team colour trimmed + } + const slides = new Set(); + for (const type of rawtypes) { + let moveset = movesets[type]; + if (!moveset) continue; + moveset = moveset(); + if (!moveset.slideMoves) continue; + Object.keys(moveset.slideMoves).forEach(slide => {slides.add(slide)}); + } + let temp = []; + slides.forEach(slideline => {temp.push(math.getCoordsFromKey(slideline))}) + return temp; } /** @@ -191,7 +220,6 @@ const variant = (function() { default: throw new Error('Unknown variant.') } - gamefile.startSnapshot.slideMovesPossible = [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]] // Every variant has the exact same initial moveRuleState value. if (gamefile.gameRules.moveRule) gamefile.startSnapshot.moveRuleState = 0 gamefile.startSnapshot.fullMove = 1; // Every variant has the exact same fullMove value. From 01f34954adce8bd6b3c492df1a72e2f30172827c Mon Sep 17 00:00:00 2001 From: Idonotus Date: Sat, 13 Jul 2024 17:35:41 +0100 Subject: [PATCH 11/58] fix slide blocking and castling oversight --- src/client/scripts/game/chess/checkdetection.js | 7 +++---- src/client/scripts/game/chess/variant.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 70baf462b..e1f8ab80b 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -351,10 +351,9 @@ const checkdetection = (function(){ function appendBlockPointIfLegal (blockPoint,line) { if (!math.isAproxEqual(blockPoint[0],Math.round(blockPoint[0])) || - !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) return; // Block is off grid so probably not valid + !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) {console.log("A"); return}; // Block is off grid so probably not valid blockPoint=[Math.round(blockPoint[0]), Math.round(blockPoint[1])] - if (!math.boxContainsSquare(box, blockPoint)) return; - if (math.getKeyFromLine(line,blockPoint)!==math.getKeyFromLine(line, coords)) return; // stop line multiples being annoying + if (math.getKeyFromLine(line,blockPoint)!==math.getKeyFromLine(line, coords)) {console.log("C"); return}; // stop line multiples being annoying // Can our piece legally move there? if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! } @@ -364,7 +363,7 @@ const checkdetection = (function(){ const c1 = math.getLineFromCoords(line, coords) const c2 = math.getLineFromCoords(direction,square2) const blockPoint = math.getLineIntersection(line[0], line[1], c1, direction[0], direction[1], c2) - appendBlockPointIfLegal(blockPoint) + appendBlockPointIfLegal(blockPoint, line) } } diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index 612f9c0ef..5b357c2eb 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -62,7 +62,7 @@ const variant = (function() { for (const tpiece of teamtypes) { rawtypes.add(math.trimWorBFromType(tpiece)); // Make a set wit the team colour trimmed } - const slides = new Set(); + const slides = new Set([[1,0]]); // Always on for castling, If castling can be disabled, change this? for (const type of rawtypes) { let moveset = movesets[type]; if (!moveset) continue; From f54fcb19040caaf089efe5b9b7f9cb3bdb28c94a Mon Sep 17 00:00:00 2001 From: Idonotus Date: Sat, 13 Jul 2024 17:55:03 +0100 Subject: [PATCH 12/58] doc linekeyfunction --- src/client/scripts/game/misc/math.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index dbb9c6713..bcc46fb76 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -175,6 +175,13 @@ const math = (function() { return step[0]*coords[1]-step[1]*coords[0] } + /** + * Gets a unique key from the line equation. + * Compatable with factorable steps like `[2,2]`. + * @param {Number[]} step Step in the form of `[deltax,deltay]` + * @param {Number[]} coords Coordinate in the form of `[x,y]` + * @returns {String} the key in the format of `id|intercept` + */ function getKeyFromLine(step, coords) { const lineIsVertical = step[0]===0; const deltaAxis = lineIsVertical ? step[1] : step[0]; From a9f82e79a3b87fc1ceb845022672e06edac3f523 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Sun, 14 Jul 2024 13:56:26 +0100 Subject: [PATCH 13/58] highlight lines render atleast --- src/client/scripts/game/chess/variant.js | 2 +- src/client/scripts/game/misc/math.js | 58 ++++--- .../scripts/game/rendering/highlightline.js | 144 +++++++----------- .../scripts/game/rendering/highlights.js | 9 +- 4 files changed, 99 insertions(+), 114 deletions(-) diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index 5b357c2eb..0a21383cc 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -62,7 +62,7 @@ const variant = (function() { for (const tpiece of teamtypes) { rawtypes.add(math.trimWorBFromType(tpiece)); // Make a set wit the team colour trimmed } - const slides = new Set([[1,0]]); // Always on for castling, If castling can be disabled, change this? + const slides = new Set(['1,0']); // Always on for castling, If castling can be disabled, change this? for (const type of rawtypes) { let moveset = movesets[type]; if (!moveset) continue; diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index bcc46fb76..63eb6b54c 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -178,9 +178,9 @@ const math = (function() { /** * Gets a unique key from the line equation. * Compatable with factorable steps like `[2,2]`. - * @param {Number[]} step Step in the form of `[deltax,deltay]` - * @param {Number[]} coords Coordinate in the form of `[x,y]` - * @returns {String} the key in the format of `id|intercept` + * @param {Number[]} step Line step `[deltax,deltay]` + * @param {Number[]} coords `[x,y]` + * @returns {String} the key `id|intercept` */ function getKeyFromLine(step, coords) { const lineIsVertical = step[0]===0; @@ -330,6 +330,13 @@ const math = (function() { return [worldX, worldY] } + // fast clampfunc + function clamp(min,max,value) { + if (min>value) return min; + if (max lineEnd[1]) closestPointY = lineEnd[1] - - closestPoint = [lineStart[0], closestPointY] + closestPoint = [lineStart[0], clamp(lineStart[1], lineEnd[1], point[1])] } else { const m = dy / dx; const b = lineStart[1] - m * lineStart[0]; // Calculate x and y coordinates of closest point on line - const x = (m * (point[1] - b) + point[0]) / (m * m + 1); + let x = (m * (point[1] - b) + point[0]) / (m * m + 1); + x = clamp(lineStart[0],lineEnd[0],x) const y = m * x + b; - closestPoint = (x < lineStart[0]) ? lineStart : (x > lineEnd[0]) ? lineEnd : [x, y]; + closestPoint = [x,y]; } distance = euclideanDistance(closestPoint, point) return { - coords: closestPoint, - distance: distance + coords: closestPoint, + distance: distance } } @@ -373,32 +377,51 @@ const math = (function() { return (value / (camera.getScreenBoundingBox(false).top - camera.getScreenBoundingBox(false).bottom)) * camera.getCanvasHeightVirtualPixels() } + function getAABBCornerOfLine(line, leftSide) { + let corner = ""; + v: { + if (line[0]==0) break v; // Horizontal so parallel with top/bottom lines + corner += ((line[0]>0==line[1]>0)==leftSide) ? "bottom" : "top" + } + h: { + if (line[1]==0) break h; // Vertical so parallel with left/right lines + corner += leftSide ? "left" : "right" + } + return corner; + } + function getLineIntersectionEntryTile (ax, by, c, boundingBox, corner) { const { left, right, top, bottom } = boundingBox; - + let xIntersectBottom = undefined; + let xIntersectTop = undefined; + let yIntersectLeft = undefined; + let yIntersectRight = undefined; // Check for intersection with left side of rectangle if (corner.endsWith('left')) { - const yIntersectLeft = ((left * ax) + c) / by; + yIntersectLeft = ((left * ax) + c) / by; if (yIntersectLeft >= bottom && yIntersectLeft <= top) return [left, yIntersectLeft] } // Check for intersection with bottom side of rectangle if (corner.startsWith('bottom')) { - const xIntersectBottom = (c - (bottom * by)) / ax; + xIntersectBottom = (c - (bottom * by)) / ax; if (xIntersectBottom >= left && xIntersectBottom <= right) return [xIntersectBottom, bottom] } // Check for intersection with right side of rectangle if (corner.endsWith('right')) { - const yIntersectRight = ((right * ax) + c) / by; + yIntersectRight = ((right * ax) + c) / by; if (yIntersectRight >= bottom && yIntersectRight <= top) return [right, yIntersectRight]; } // Check for intersection with top side of rectangle if (corner.startsWith('top')) { - const xIntersectTop = (c - (top * by)) / ax; + xIntersectTop = (c - (top * by)) / ax; if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; } + console.log(corner) + console.log(boundingBox) + console.log(xIntersectBottom, xIntersectTop, yIntersectLeft, yIntersectRight) } // Returns point, if there is one, of a line with specified slope "b" intersection screen edge on desired corner @@ -892,6 +915,7 @@ const math = (function() { getBoundingBoxOfBoard, convertPixelsToWorldSpace_Virtual, convertWorldSpaceToPixels_Virtual, + getAABBCornerOfLine, getLineIntersectionEntryTile, getIntersectionEntryTile, convertWorldSpaceToGrid, diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index a33227d85..7d533a1d9 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -35,84 +35,46 @@ const highlightline = (function(){ color[3] = 1; const snapDist = miniimage.gwidthWorld() / 2; - const snapPointsList = []; - - if (legalmoves.horizontal) { - - const left = math.convertCoordToWorldSpace_ClampEdge([legalmoves.horizontal[0], pieceCoords[1]]) - const right = math.convertCoordToWorldSpace_ClampEdge([legalmoves.horizontal[1], pieceCoords[1]]) - - appendLineToData(dataLines, left, right, color) - - const closestPoint = math.closestPointOnLine(left, right, input.getMouseWorldLocation()) // { coords, distance } - closestPoint.moveset = legalmoves.horizontal; - closestPoint.direction = 'horizontal'; - if (closestPoint.distance <= snapDist) snapPointsList.push(closestPoint) - } - - if (legalmoves.vertical) { - - const bottom = math.convertCoordToWorldSpace_ClampEdge([pieceCoords[0], legalmoves.vertical[0]]) - const top = math.convertCoordToWorldSpace_ClampEdge([pieceCoords[0], legalmoves.vertical[1]]) - - appendLineToData(dataLines, bottom, top, color) - - const closestPoint = math.closestPointOnLine(bottom, top, input.getMouseWorldLocation()) - closestPoint.moveset = legalmoves.vertical; - closestPoint.direction = 'vertical'; - if (closestPoint.distance <= snapDist) snapPointsList.push(closestPoint) - } - - const a = perspective.distToRenderBoard + /** @type {BoundingBox} */ let boundingBox = perspective.getEnabled() ? { left: -a, right: a, bottom: -a, top: a } : camera.getScreenBoundingBox(false) - up: if (legalmoves.diagonalUp) { - - const diag = math.getUpDiagonalFromCoords(worldSpaceCoords); - - let point1 = math.getIntersectionEntryTile(1, diag, boundingBox, "bottomleft") - if (!point1) break up; - const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.diagonalUp, true, false) - const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord) - point1 = capPointAtSlideLimit(point1, leftLimitPointWorld, false) - - let point2 = math.getIntersectionEntryTile(1, diag, boundingBox, "topright") - const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.diagonalUp, true, true) - const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord) - point2 = capPointAtSlideLimit(point2, rightLimitPointWorld, true) - - appendLineToData(dataLines, point1, point2, color) - - const closestPoint = math.closestPointOnLine(point1, point2, input.getMouseWorldLocation()) - closestPoint.moveset = legalmoves.diagonalUp; - closestPoint.direction = 'diagonalup'; - if (closestPoint.distance <= snapDist) snapPointsList.push(closestPoint) - } - - down: if (legalmoves.diagonalDown) { - - const diag = math.getDownDiagonalFromCoords(worldSpaceCoords); - - let point1 = math.getIntersectionEntryTile(-1, diag, boundingBox, "topleft") - if (!point1) break down; - const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.diagonalDown, false, false) - const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord) - point1 = capPointAtSlideLimit(point1, leftLimitPointWorld, false) - - let point2 = math.getIntersectionEntryTile(-1, diag, boundingBox, "bottomright") - const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.diagonalDown, false, true) - const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord) - point2 = capPointAtSlideLimit(point2, rightLimitPointWorld, true) - - appendLineToData(dataLines, point1, point2, color) - - const closestPoint = math.closestPointOnLine(point1, point2, input.getMouseWorldLocation()) - closestPoint.moveset = legalmoves.diagonalDown; - closestPoint.direction = 'diagonaldown'; - if (closestPoint.distance <= snapDist) snapPointsList.push(closestPoint) - } - + const mouseLocation = input.getMouseWorldLocation() + + let closestDistance = -1; + let closestPoint; + for (var strline in legalmoves.slides) { + const line = math.getCoordsFromKey(strline); + const diag = math.getLineFromCoords(line, worldSpaceCoords); + const lineIsVertical = line[0]===0 + + const corner1 = math.getAABBCornerOfLine(line, true); + + let point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + if (!point1) continue; + const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.slides[strline], line, false); + const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord); + point1 = capPointAtSlideLimit(point1, leftLimitPointWorld, false, lineIsVertical); + + const corner2 = math.getAABBCornerOfLine(line, false); + + let point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + if (!point2) continue; // I hate this + const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.slides[strline], line, true); + const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord); + point2 = capPointAtSlideLimit(point2, rightLimitPointWorld, true, lineIsVertical); + + appendLineToData(dataLines, point1, point2, color); + + const snapPoint = math.closestPointOnLine(point1, point2, mouseLocation) + if (closestDistance<0) if (snapPoint.distance>snapDist) continue; + else if (snapPoint.distance>closestDistance) continue; + closestDistance = snapPoint.distance + snapPoint.moveset = legalmoves.slides[strline] + snapPoint.line = line + closestPoint = snapPoint + }; + modelLines = buffermodel.createModel_Colored(new Float32Array(dataLines), 2, "LINES") // Ghost image... @@ -123,13 +85,6 @@ const highlightline = (function(){ // key points that would trump clicking pieces if (miniimage.isHovering()) return; - // Iterate through all snapping points to find the closest one to the mouse - let closestPoint = snapPointsList[0]; - for (let i = 1; i < snapPointsList.length; i++) { - const thisPoint = snapPointsList[i]; - if (thisPoint.distance < closestPoint.distance) closestPoint = thisPoint; - } - if (!closestPoint) return; // There were no snapping points, the mouse is not next to a line. // Generate the ghost image model @@ -231,18 +186,25 @@ const highlightline = (function(){ ) } - function capPointAtSlideLimit(point, slideLimit, positive) { // slideLimit = [x,y] - if (!positive && point[0] < slideLimit[0] - || positive && point[0] > slideLimit[0]) return slideLimit; + function capPointAtSlideLimit(point, slideLimit, positive, lineIsVertical) { // slideLimit = [x,y] + const cappingAxis = lineIsVertical ? 1 : 0 + if (!positive && point[cappingAxis] < slideLimit[cappingAxis] + || positive && point[cappingAxis] > slideLimit[cappingAxis]) return slideLimit; return point; } - function getPointOfDiagSlideLimit (pieceCoords, moveset, isDiagUp, positive) { // positive is true if it's the right/top - - const targetX = positive ? moveset[1] : moveset[0]; - const xDiff = targetX - pieceCoords[0] - const yDiff = isDiagUp ? xDiff : -xDiff; - + function getPointOfDiagSlideLimit (pieceCoords, moveset, line, positive) { // positive is true if it's the right/top + let yDiff; + let xDiff; + if (line[0]!==0) { + const targetX = positive ? moveset[1] : moveset[0]; + xDiff = targetX - pieceCoords[0]; + yDiff = (line[1]*xDiff)/line[0]; + } else { + const targetY = positive ? moveset[1] : moveset[0]; + yDiff = targetY - pieceCoords[1]; + xDiff = 0; + } return [pieceCoords[0]+xDiff, pieceCoords[1]+yDiff] } diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 77d676265..a2c1c4735 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -300,15 +300,14 @@ const highlights = (function(){ if (line[1] == 0 || line[0] == 0) {continue;} const lineEqua = math.getLineFromCoords(line, coords) const lineGrad = line[1]/line[0] - const corner2 = lineGrad>0 ? 'bottomleft' : 'topleft' - const corner1 = lineGrad>0 ? 'topleft' : 'bottomright' + const corner1 = math.getAABBCornerOfLine(line, true) + const corner2 = math.getAABBCornerOfLine(line, false) const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1) const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2) - if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect1Tile || !intsect2Tile) {debugger; continue;} // This should not happen + if (!intsect1Tile && !intsect2Tile) {console.log(intsect1Tile, intsect2Tile); continue;} // If there's no intersection point, it's off the screen, don't bother rendering. + if (!intsect1Tile || !intsect2Tile) {console.log(intsect1Tile, intsect2Tile); continue;} // FIXME: This should not happen if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a) - } } From d3b89c171343f5b458c39dc47251f5b0606bbc9c Mon Sep 17 00:00:00 2001 From: Idonotus Date: Sun, 14 Jul 2024 18:20:45 +0100 Subject: [PATCH 14/58] boundng box interesection fix --- src/client/scripts/game/misc/math.js | 10 +++++----- src/client/scripts/game/rendering/highlights.js | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 3b086793e..d2a26be65 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -390,7 +390,7 @@ const math = (function() { return corner; } - function getLineIntersectionEntryTile (ax, by, c, boundingBox, corner) { + function getLineIntersectionEntryTile (dx, dy, c, boundingBox, corner) { const { left, right, top, bottom } = boundingBox; let xIntersectBottom = undefined; let xIntersectTop = undefined; @@ -398,25 +398,25 @@ const math = (function() { let yIntersectRight = undefined; // Check for intersection with left side of rectangle if (corner.endsWith('left')) { - yIntersectLeft = ((left * ax) + c) / by; + yIntersectLeft = ((left * dy) + c) / dx; if (yIntersectLeft >= bottom && yIntersectLeft <= top) return [left, yIntersectLeft] } // Check for intersection with bottom side of rectangle if (corner.startsWith('bottom')) { - xIntersectBottom = (c - (bottom * by)) / ax; + xIntersectBottom = ((bottom * dx) - c) / dy; if (xIntersectBottom >= left && xIntersectBottom <= right) return [xIntersectBottom, bottom] } // Check for intersection with right side of rectangle if (corner.endsWith('right')) { - yIntersectRight = ((right * ax) + c) / by; + yIntersectRight = ((right * dy) + c) / dx; if (yIntersectRight >= bottom && yIntersectRight <= top) return [right, yIntersectRight]; } // Check for intersection with top side of rectangle if (corner.startsWith('top')) { - xIntersectTop = (c - (top * by)) / ax; + xIntersectTop = ((top * dx) - c) / dy; if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; } console.log(corner) diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index a2c1c4735..7b15f0854 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -300,10 +300,12 @@ const highlights = (function(){ if (line[1] == 0 || line[0] == 0) {continue;} const lineEqua = math.getLineFromCoords(line, coords) const lineGrad = line[1]/line[0] + const corner1 = math.getAABBCornerOfLine(line, true) const corner2 = math.getAABBCornerOfLine(line, false) const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1) const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2) + if (!intsect1Tile && !intsect2Tile) {console.log(intsect1Tile, intsect2Tile); continue;} // If there's no intersection point, it's off the screen, don't bother rendering. if (!intsect1Tile || !intsect2Tile) {console.log(intsect1Tile, intsect2Tile); continue;} // FIXME: This should not happen if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); From fc2d1542ced69f7606dc175af937ce7b6ee9678f Mon Sep 17 00:00:00 2001 From: Idonotus Date: Sun, 14 Jul 2024 20:48:39 +0100 Subject: [PATCH 15/58] highlight line selection and cleanup --- src/client/scripts/game/misc/math.js | 3 -- .../scripts/game/rendering/highlightline.js | 50 ++++++------------- .../scripts/game/rendering/highlights.js | 22 ++++---- 3 files changed, 25 insertions(+), 50 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index d2a26be65..ee3ff6916 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -419,9 +419,6 @@ const math = (function() { xIntersectTop = ((top * dx) - c) / dy; if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; } - console.log(corner) - console.log(boundingBox) - console.log(xIntersectBottom, xIntersectTop, yIntersectLeft, yIntersectRight) } // Returns point, if there is one, of a line with specified slope "b" intersection screen edge on desired corner diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index 7d533a1d9..d8c2f1a07 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -41,7 +41,7 @@ const highlightline = (function(){ const mouseLocation = input.getMouseWorldLocation() - let closestDistance = -1; + let closestDistance; let closestPoint; for (var strline in legalmoves.slides) { const line = math.getCoordsFromKey(strline); @@ -67,8 +67,8 @@ const highlightline = (function(){ appendLineToData(dataLines, point1, point2, color); const snapPoint = math.closestPointOnLine(point1, point2, mouseLocation) - if (closestDistance<0) if (snapPoint.distance>snapDist) continue; - else if (snapPoint.distance>closestDistance) continue; + if (!closestDistance) {if (snapPoint.distance>snapDist) continue;} + else if (snapPoint.distance>closestDistance) {continue;} closestDistance = snapPoint.distance snapPoint.moveset = legalmoves.slides[strline] snapPoint.line = line @@ -86,7 +86,6 @@ const highlightline = (function(){ if (miniimage.isHovering()) return; if (!closestPoint) return; // There were no snapping points, the mouse is not next to a line. - // Generate the ghost image model const dataGhost = [] @@ -122,43 +121,22 @@ const highlightline = (function(){ boundingBox = perspective.getEnabled() ? math.generatePerspectiveBoundingBox(perspectiveLimitToTeleport) : board.gboundingBox(); - if (closestPoint.direction === 'horizontal') { - if (moveset[0] === -Infinity) moveset[0] = boundingBox.left; - if (moveset[1] === Infinity) moveset[1] = boundingBox.right; - point1 = [moveset[0], pieceCoords[1]] - point2 = [moveset[1], pieceCoords[1]] - } - - else if (closestPoint.direction === 'vertical') { - if (moveset[0] === -Infinity) moveset[0] = boundingBox.bottom; - if (moveset[1] === Infinity) moveset[1] = boundingBox.top; - point1 = [pieceCoords[0], moveset[0]] - point2 = [pieceCoords[0], moveset[1]] - } - - /** - else if (closestPoint.direction === 'diagonalup') { + const line = closestPoint.line + const diag = math.getLineFromCoords(line, pieceCoords) + const lineIsVertical = line[0]===0 - // Calculate the intersection tile of this diagonal with the left/bottom and right/top sides of the screen. - const diag = math.getUpDiagonalFromCoords(pieceCoords) // mx + b - const intsect1Tile = math.getIntersectionEntryTile(1, diag, boundingBox, 'bottomleft') - const intsect2Tile = math.getIntersectionEntryTile(1, diag, boundingBox, 'topright') + const corner1 = math.getAABBCornerOfLine(line, true); - point1 = moveset[0] === -Infinity ? intsect1Tile : [moveset[0], pieceCoords[1] - (pieceCoords[0] - moveset[0])] - point2 = moveset[1] === Infinity ? intsect2Tile : [moveset[1], pieceCoords[1] + moveset[1] - pieceCoords[0]] - } + point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, false); + point1 = capPointAtSlideLimit(point1, leftLimitPointCoord, false, lineIsVertical); - else { // closestPoint.direction === 'diagonaldown' + const corner2 = math.getAABBCornerOfLine(line, false); - // Calculate the intersection tile of this diagonal with the left/bottom and right/top sides of the screen. - const diag = math.getDownDiagonalFromCoords(pieceCoords) // mx + b - const intsect1Tile = math.getIntersectionEntryTile(-1, diag, boundingBox, 'topleft') - const intsect2Tile = math.getIntersectionEntryTile(-1, diag, boundingBox, 'bottomright') + point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, true); + point2 = capPointAtSlideLimit(point2, rightLimitPointCoord, true, lineIsVertical); - point1 = moveset[0] === -Infinity ? intsect1Tile : [moveset[0], pieceCoords[1] + pieceCoords[0] - moveset[0]] - point2 = moveset[1] === Infinity ? intsect2Tile : [moveset[1], pieceCoords[1] - (moveset[1] - pieceCoords[0])] - } - */ let tileMouseFingerOver; if (input.getTouchClicked()) { // Set to what the finger tapped above // let touchClickedTile = input.getTouchClickedTile() // { id, x, y } diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 7b15f0854..25a84d83c 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -296,18 +296,18 @@ const highlights = (function(){ function concatData_HighlightedMoves_Diagonals (coords, renderBoundingBox, r, g, b, a) { const legalMoves = selection.getLegalMovesOfSelectedPiece() for (var strline in legalMoves.slides) { - const line = math.getCoordsFromKey(strline) - if (line[1] == 0 || line[0] == 0) {continue;} - const lineEqua = math.getLineFromCoords(line, coords) - const lineGrad = line[1]/line[0] - - const corner1 = math.getAABBCornerOfLine(line, true) - const corner2 = math.getAABBCornerOfLine(line, false) - const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1) - const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2) + const line = math.getCoordsFromKey(strline); + if (line[1] == 0 || line[0] == 0) {continue;}; + const lineEqua = math.getLineFromCoords(line, coords); + const lineGrad = line[1]/line[0]; + + const corner1 = math.getAABBCornerOfLine(line, true); + const corner2 = math.getAABBCornerOfLine(line, false); + const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1); + const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2); - if (!intsect1Tile && !intsect2Tile) {console.log(intsect1Tile, intsect2Tile); continue;} // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect1Tile || !intsect2Tile) {console.log(intsect1Tile, intsect2Tile); continue;} // FIXME: This should not happen + if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. + if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} // FIXME: This should not happen if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a) } From 44a907a35f6eda351cef39ca968b9fc87bffab5b Mon Sep 17 00:00:00 2001 From: Idonotus Date: Mon, 15 Jul 2024 15:18:41 +0100 Subject: [PATCH 16/58] moveset slidelimit changes --- .../scripts/game/chess/checkdetection.js | 33 ++++++---- src/client/scripts/game/chess/legalmoves.js | 61 +++++++------------ src/client/scripts/game/misc/math.js | 7 ++- 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index e1f8ab80b..adf2ae419 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -149,12 +149,12 @@ const checkdetection = (function(){ if (!thisPieceMoveset.slideMoves) {continue}; const moveset = thisPieceMoveset.slideMoves[math.getKeyFromCoords(direction)]; if (!moveset) {continue}; - const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, lineIsVertical, moveset, thisPiece.coords, thisPieceColor) + const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} // const isWithinMoveset = math.boxContainsSquare(rectangle, coords) - const isWithinMoveset = legalmoves.doesSlideMovesetContainSquare(thisPieceLegalSlide, lineIsVertical, coords) + const isWithinMoveset = legalmoves.doesSlideMovesetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords) if (isWithinMoveset) { if (attackers) appendAttackerToList(attackers, { coords: thisPiece.coords, slidingCheck: true }) @@ -317,19 +317,26 @@ const checkdetection = (function(){ checklines.push(line); } const tempslides = {} - if (checklines.length > 1) { - if (math.areLinesCollinear(checklines)) { - for (const line of checklines) { - const strline = math.getKeyFromCoords(line) - tempslides[strline] = moves.slideMoves[strline] + r : { + if (checklines.length > 1) { + if (math.areLinesCollinear(checklines)) { + // FIXME: this is not correct as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check + // For now lets just blank slides + /** + for (const line of checklines) { + const strline = math.getKeyFromCoords(line) + if (!moves.slideMoves[strline]) break r; + tempslides[strline] = moves.slideMoves[strline] + */ + } else { + // Cannot slide to block all attack lines so blank the slides + // Could probably blank regular attacks too } - } else { - // Cannot slide to block all attack lines so blank the slides - // Could probably blank regular attacks too + } else if (checklines.length === 1) { + const strline = math.getKeyFromCoords(checklines[0]) + if (!moves.slideMoves[strline]) break r; + tempslides[strline] = moves.slideMoves[strline] } - } else if (checklines.length === 1) { - const strline = math.getKeyFromCoords(checklines[0]) - tempslides[strline] = moves.slideMoves[strline] } // Add the piece back with the EXACT SAME index it had before!! diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 135d699e3..0875a389d 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -102,19 +102,10 @@ const legalmoves = (function(){ const line = lines[i]; if (!thisPieceMoveset.slideMoves[line]) continue; const key = math.getKeyFromLine(line,coords); - legalSlideMoves[line] = slide_CalcLegalLimit(gamefile.piecesOrganizedByLines[line][key],line[0]===0, thisPieceMoveset.slideMoves[line], coords, color); + legalSlideMoves[line] = slide_CalcLegalLimit(gamefile.piecesOrganizedByLines[line][key],line, thisPieceMoveset.slideMoves[line], coords, color); }; }; - /** - let key = coords[1]; // Key is y level for horizontal slide - legalHorizontalMoves = slide_CalcLegalLimit(gamefile.piecesOrganizedByRow[key], false, thisPieceMoveset.horizontal, coords, color) - key = coords[0] // Key is x for vertical slide - legalVerticalMoves = slide_CalcLegalLimit(gamefile.piecesOrganizedByColumn[key], true, thisPieceMoveset.vertical, coords, color) - key = math.getUpDiagonalFromCoords(coords) // Key is -x + y for up-diagonal slide - legalUpDiagonalMoves = slide_CalcLegalLimit(gamefile.piecesOrganizedByUpDiagonal[key], false, thisPieceMoveset.diagonalUp, coords, color) - key = math.getDownDiagonalFromCoords(coords) // Key is x + y for down-diagonal slide - legalDownDiagonalMoves = slide_CalcLegalLimit(gamefile.piecesOrganizedByDownDiagonal[key], false, thisPieceMoveset.diagonalDown, coords, color) - */ + } // Add any special moves! @@ -169,45 +160,40 @@ const legalmoves = (function(){ // Takes in specified organized list, direction of the slide, the current moveset... // Shortens the moveset by pieces that block it's path. - function slide_CalcLegalLimit (organizedLine, lineIsVertical, slideMoveset, coords, color) { + function slide_CalcLegalLimit (organizedLine, line, slideMoveset, coords, color) { if (!slideMoveset) return // Return undefined if there is no slide moveset // The default slide is [-Infinity, Infinity], change that if there are any pieces blocking our path! // For most we'll be comparing the x values, only exception is the columns. - const zeroOrOne = lineIsVertical ? 1 : 0 - const limit = [slideMoveset[0] + coords[zeroOrOne], slideMoveset[1] + coords[zeroOrOne]] - + const zeroOrOne = line[0] == 0 ? 1 : 0 + const limit = [slideMoveset[0], slideMoveset[1]] // Iterate through all pieces on same line for (let i = 0; i < organizedLine.length; i++) { // What are the coords of this piece? const thisPiece = organizedLine[i] // { type, coords } - const thisPieceXorY = thisPiece.coords[zeroOrOne] + const thisPieceSteps = Math.floor((thisPiece.coords[zeroOrOne]-coords[zeroOrOne])/line[zeroOrOne]) const thisPieceColor = math.getPieceColorFromType(thisPiece.type) const isFriendlyPiece = color === thisPieceColor const isVoid = thisPiece.type === 'voidsN'; - // Is the piece to the left of us or right of us? - if (thisPieceXorY < coords[zeroOrOne]) { // To our left + if (thisPieceSteps < 0) { // To our left // What would our new left slide limit be? If it's an opponent, it's legal to capture it. - const newLeftSlideLimit = isFriendlyPiece || isVoid ? thisPieceXorY + 1 : thisPieceXorY - + const newLeftSlideLimit = isFriendlyPiece || isVoid ? thisPieceSteps + 1 : thisPieceSteps // If the piece x is closer to us than our current left slide limit, update it if (newLeftSlideLimit > limit[0]) limit[0] = newLeftSlideLimit - } else if (thisPieceXorY > coords[zeroOrOne]) { // To our right + } else if (thisPieceSteps > 0) { // To our right // What would our new right slide limit be? If it's an opponent, it's legal to capture it. - const newRightSlideLimit = isFriendlyPiece || isVoid ? thisPieceXorY - 1 : thisPieceXorY - + const newRightSlideLimit = isFriendlyPiece || isVoid ? thisPieceSteps - 1 : thisPieceSteps // If the piece x is closer to us than our current left slide limit, update it if (newRightSlideLimit < limit[1]) limit[1] = newRightSlideLimit } // else this is us, don't do anything. } - return limit; } @@ -243,12 +229,13 @@ const legalmoves = (function(){ for (var strline in legalMoves.slides) { let line=math.getCoordsFromKey(strline); let limits = legalMoves.slides[strline]; + let selectedPieceLine = math.getKeyFromLine(line,startCoords); let clickedCoordsLine = math.getKeyFromLine(line,endCoords); - if (limits && selectedPieceLine==clickedCoordsLine) { - if (endCoords[0]>=limits[0] && endCoords[0]<=limits[1] && line[0]!=0) return true; - else if (endCoords[1]>=limits[0] && endCoords[1]<=limits[1] && line[0]==0) return true; - } + if (!limits||selectedPieceLine!=clickedCoordsLine) continue; + + if (!doesSlideMovesetContainSquare(limits, line, startCoords, endCoords)) continue; + return true; } return false; } @@ -351,13 +338,17 @@ const legalmoves = (function(){ } } + // TODO: moveset changes // This requires coords be on the same line as the sliding moveset. - function doesSlideMovesetContainSquare (slideMoveset, lineIsVertical, coords) { + function doesSlideMovesetContainSquare (slideMoveset, line, pieceCoords, coords) { - const xOrY = lineIsVertical ? coords[1] : coords[0]; + const axis = line[0]===0 ? 1 : 0 + const coordMag = coords[axis]; + const min = slideMoveset[0] * line[axis] + pieceCoords[axis] + const max = slideMoveset[1] * line[axis] + pieceCoords[axis] - if (xOrY < slideMoveset[0]) return false; - if (xOrY > slideMoveset[1]) return false; + if (coordMag < min) return false; + if (coordMag > max) return false; return true; } @@ -371,12 +362,6 @@ const legalmoves = (function(){ if (moves.individual.length > 0) return true; for (var line in moves.slides) if (doesSlideHaveWidth(moves.slides[line])) return true; - /** - if (doesSlideHaveWidth(moves.horizontal)) return true; - if (doesSlideHaveWidth(moves.vertical)) return true; - if (doesSlideHaveWidth(moves.diagonalUp)) return true; - if (doesSlideHaveWidth(moves.diagonalDown)) return true; - */ function doesSlideHaveWidth(slide) { // [-Infinity, Infinity] if (!slide) return false; diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index ee3ff6916..198cb8d54 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -189,10 +189,11 @@ const math = (function() { return `${getLineFromCoords(step,coords)}|${coordAxis - (Math.floor(coordAxis / deltaAxis) * deltaAxis)}` } + // TODO: implement this /** - * TODO: implement this - * @param {Number[][]} lines - * @returns {Boolean} + * Checks if all lines are colinear aka `[[1,0],[2,0]]` would be as they are both the same direction + * @param {Number[][]} lines Array of vectors `[[1,0],[2,0]]` + * @returns {Boolean} */ function areLinesCollinear(lines) { From f023e54c2999530ce64b6c4f2c4169e00944f915 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Mon, 15 Jul 2024 17:47:00 +0100 Subject: [PATCH 17/58] made the rider get its own horse + jsdoc changes --- .../scripts/game/chess/formatconverter.js | 2 +- src/client/scripts/game/chess/movesets.js | 23 +++++++++++-------- src/client/scripts/game/misc/math.js | 4 ++-- src/client/scripts/game/rendering/pieces.js | 4 ++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/client/scripts/game/chess/formatconverter.js b/src/client/scripts/game/chess/formatconverter.js index b1053b65c..40f9d346a 100644 --- a/src/client/scripts/game/chess/formatconverter.js +++ b/src/client/scripts/game/chess/formatconverter.js @@ -31,7 +31,7 @@ const formatconverter = (function() { "centaursW": "CE", "centaursB": "ce", "royalQueensW": "RQ", "royalQueensB": "rq", "royalCentaursW": "RC", "royalCentaursB": "rc", - "knightRidersW": "NR", "knightRidersB": "nr", + "knightridersW": "NR", "knightridersB": "nr", "obstaclesN": "ob", "voidsN": "vo" }; diff --git a/src/client/scripts/game/chess/movesets.js b/src/client/scripts/game/chess/movesets.js index dfebeeba9..6a450519d 100644 --- a/src/client/scripts/game/chess/movesets.js +++ b/src/client/scripts/game/chess/movesets.js @@ -26,15 +26,9 @@ const movesets = (function() { knights: function () { return { individual: [ - //[-2,1],[-1,2],[1,2],[2,1], - //[-2,-1],[-1,-2],[1,-2],[2,-1] - ], - slideMoves: { - '2,1': [-slideLimit, slideLimit], - '2,-1': [-slideLimit, slideLimit], - '1,2': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding - '1,-2': [-slideLimit, slideLimit] - } + [-2,1],[-1,2],[1,2],[2,1], + [-2,-1],[-1,-2],[1,-2],[2,-1] + ] } }, hawks: function () { @@ -164,6 +158,17 @@ const movesets = (function() { ] } }, + knightriders: function () { + return { + individual: [], + slideMoves: { + '1,2' : [-slideLimit, slideLimit], + '1,-2' : [-slideLimit,slideLimit], + '2,1' : [-slideLimit,slideLimit], + '2,-1' : [-slideLimit,slideLimit], + } + } + }, centaurs: function () { return { individual: [ diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 198cb8d54..42d6104d9 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -167,8 +167,8 @@ const math = (function() { * c=b*y-intercept so is unique for each line * Not unique when step can be factored * eg [2,2] - * @param {number[]} step The gradient - * @param {number[]} coords + * @param {number[]} step `[deltax, deltay]` + * @param {number[]} coords `[x,y]` * @returns {number} integer c */ function getLineFromCoords(step, coords) { diff --git a/src/client/scripts/game/rendering/pieces.js b/src/client/scripts/game/rendering/pieces.js index e82e7c5d4..e02604d48 100644 --- a/src/client/scripts/game/rendering/pieces.js +++ b/src/client/scripts/game/rendering/pieces.js @@ -10,8 +10,8 @@ const pieces = (function () { // All piece names we have a texture for on our spritesheet (except voids). // They are arranged in this order for faster checkmate/draw detection. - const white = ['kingsW', 'giraffesW', 'camelsW', 'zebrasW', 'amazonsW', 'queensW', 'royalQueensW', 'hawksW', 'chancellorsW', 'archbishopsW', 'centaursW', 'royalCentaursW', 'knightsW', 'guardsW', 'rooksW', 'bishopsW', 'pawnsW']; - const black = ['kingsB', 'giraffesB', 'camelsB', 'zebrasB', 'amazonsB', 'queensB', 'royalQueensB', 'hawksB', 'chancellorsB', 'archbishopsB', 'centaursB', 'royalCentaursB', 'knightsB', 'guardsB', 'rooksB', 'bishopsB', 'pawnsB']; + const white = ['kingsW', 'giraffesW', 'camelsW', 'zebrasW', 'knightridersW','amazonsW', 'queensW', 'royalQueensW', 'hawksW', 'chancellorsW', 'archbishopsW', 'centaursW', 'royalCentaursW', 'knightsW', 'guardsW', 'rooksW', 'bishopsW', 'pawnsW']; + const black = ['kingsB', 'giraffesB', 'camelsB', 'zebrasB', 'knightridersB', 'amazonsB', 'queensB', 'royalQueensB', 'hawksB', 'chancellorsB', 'archbishopsB', 'centaursB', 'royalCentaursB', 'knightsB', 'guardsB', 'rooksB', 'bishopsB', 'pawnsB']; const neutral = ['obstaclesN', 'voidsN']; /** A list of the royal pieces, without the color appended. */ From b3999206c24c98caae46e317532c64d85488e07c Mon Sep 17 00:00:00 2001 From: Idonotus Date: Mon, 15 Jul 2024 18:08:47 +0100 Subject: [PATCH 18/58] fixed highlight lines and orthoganol highlights --- src/client/scripts/game/misc/math.js | 4 ++-- .../scripts/game/rendering/highlightline.js | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 42d6104d9..e21c74af5 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -381,11 +381,11 @@ const math = (function() { function getAABBCornerOfLine(line, leftSide) { let corner = ""; v: { - if (line[0]==0) break v; // Horizontal so parallel with top/bottom lines + if (line[1]==0) break v; // Horizontal so parallel with top/bottom lines corner += ((line[0]>0==line[1]>0)==leftSide) ? "bottom" : "top" } h: { - if (line[1]==0) break h; // Vertical so parallel with left/right lines + if (line[0]==0) break h; // Vertical so parallel with left/right lines corner += leftSide ? "left" : "right" } return corner; diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index d8c2f1a07..3d7a974cf 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -49,9 +49,9 @@ const highlightline = (function(){ const lineIsVertical = line[0]===0 const corner1 = math.getAABBCornerOfLine(line, true); - + let point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); - if (!point1) continue; + if (!point1) {continue}; const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.slides[strline], line, false); const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord); point1 = capPointAtSlideLimit(point1, leftLimitPointWorld, false, lineIsVertical); @@ -172,17 +172,9 @@ const highlightline = (function(){ } function getPointOfDiagSlideLimit (pieceCoords, moveset, line, positive) { // positive is true if it's the right/top - let yDiff; - let xDiff; - if (line[0]!==0) { - const targetX = positive ? moveset[1] : moveset[0]; - xDiff = targetX - pieceCoords[0]; - yDiff = (line[1]*xDiff)/line[0]; - } else { - const targetY = positive ? moveset[1] : moveset[0]; - yDiff = targetY - pieceCoords[1]; - xDiff = 0; - } + const steps = positive ? moveset[1] : moveset[0] + let yDiff = line[1]*steps + let xDiff = line[0]*steps return [pieceCoords[0]+xDiff, pieceCoords[1]+yDiff] } From 034d1e56c02ad2ebd4a2bf49e55d6f2565f17087 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Mon, 15 Jul 2024 19:19:24 +0100 Subject: [PATCH 19/58] vertical highlight line selection --- src/client/scripts/game/misc/math.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index e21c74af5..b90e5bf53 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -382,7 +382,8 @@ const math = (function() { let corner = ""; v: { if (line[1]==0) break v; // Horizontal so parallel with top/bottom lines - corner += ((line[0]>0==line[1]>0)==leftSide) ? "bottom" : "top" + corner += ((line[0]>0==line[1]>0)==leftSide==(line[0]!=0)) ? "bottom" : "top" + // Gonna be honest I have no idea how this works but it does sooooooo its staying } h: { if (line[0]==0) break h; // Vertical so parallel with left/right lines From 3a22d6c48eba785d67e644deae79a373db8649cc Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:14:32 -0600 Subject: [PATCH 20/58] Added knightriders to Knighted Chess variant --- src/client/scripts/game/chess/variant.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index 0a21383cc..ce9b895e6 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -325,7 +325,7 @@ const variant = (function() { positionString = 'k5,8+|n3,8|n4,8|n6,8|n7,8|p-5,7+|p-4,7+|p-3,7+|p-2,7+|p-1,7+|p0,7+|p1,7+|p2,7+|p3,7+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p9,7+|p10,7+|p11,7+|p12,7+|p13,7+|p14,7+|p15,7+|K5,1+|N3,1|N4,1|N6,1|N7,1|P-5,2+|P-4,2+|P-3,2+|P-2,2+|P-1,2+|P0,2+|P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|P9,2+|P10,2+|P11,2+|P12,2+|P13,2+|P14,2+|P15,2+'; return getStartSnapshotPosition({ positionString }) case "Knighted Chess": - positionString = 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|N2,1|N7,1|n2,8|n7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+'; + positionString = 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|NR2,1|NR7,1|nr2,8|nr7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+'; return getStartSnapshotPosition({ positionString }) case "Omega": // Joel & Cory's version positionString = 'r-2,4|r2,4|r-2,2|r2,2|r-2,0|r0,0|r2,0|k0,-1|R1,-2|P-2,-3|Q-1,-3|P2,-3|K0,-4' From 8ea08547125ad77737e49efce63c136f0ee51e83 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Tue, 16 Jul 2024 20:51:20 +0100 Subject: [PATCH 21/58] renames and arrows --- .../scripts/game/chess/checkdetection.js | 33 ++- src/client/scripts/game/chess/gamefile.js | 2 +- src/client/scripts/game/chess/legalmoves.js | 38 ++-- src/client/scripts/game/chess/movesets.js | 14 +- .../scripts/game/chess/organizedlines.js | 6 +- src/client/scripts/game/chess/variant.js | 10 +- src/client/scripts/game/misc/math.js | 9 + src/client/scripts/game/rendering/arrows.js | 209 +++++++----------- .../scripts/game/rendering/highlightline.js | 8 +- .../scripts/game/rendering/highlights.js | 26 +-- 10 files changed, 159 insertions(+), 196 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index adf2ae419..f171eb9e3 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -122,7 +122,7 @@ const checkdetection = (function(){ * @returns */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { - for (const line of gamefile.startSnapshot.slideMovesPossible) { + for (const line of gamefile.startSnapshot.slidingPossible) { const strline = math.getKeyFromCoords(line) const key = math.getKeyFromLine(line, coords) if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) return true; @@ -146,15 +146,15 @@ const checkdetection = (function(){ const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - if (!thisPieceMoveset.slideMoves) {continue}; - const moveset = thisPieceMoveset.slideMoves[math.getKeyFromCoords(direction)]; + if (!thisPieceMoveset.sliding) {continue}; + const moveset = thisPieceMoveset.sliding[math.getKeyFromCoords(direction)]; if (!moveset) {continue}; const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} // const isWithinMoveset = math.boxContainsSquare(rectangle, coords) - const isWithinMoveset = legalmoves.doesSlideMovesetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords) + const isWithinMoveset = legalmoves.doesslidingetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords) if (isWithinMoveset) { if (attackers) appendAttackerToList(attackers, { coords: thisPiece.coords, slidingCheck: true }) @@ -223,7 +223,7 @@ const checkdetection = (function(){ // Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { - if (math.isEmpty(moves.slides)) return; + if (math.isEmpty(moves.sliding)) return; const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces @@ -287,7 +287,7 @@ const checkdetection = (function(){ return true; function eraseAllSlidingMoves() { - legalMoves.slides = {} + legalMoves.sliding = {} } } @@ -295,7 +295,7 @@ const checkdetection = (function(){ const selectedPieceCoords = pieceSelected.coords; let sameLines = []; - for (const line of gamefile.startSnapshot.slideMovesPossible) { // Only check current possible slides + for (const line of gamefile.startSnapshot.slidingPossible) { // Only check current possible slides if (math.getKeyFromLine(line, kingCoords) !== math.getKeyFromLine(line, selectedPieceCoords)) continue; sameLines.push(line); }; @@ -320,22 +320,17 @@ const checkdetection = (function(){ r : { if (checklines.length > 1) { if (math.areLinesCollinear(checklines)) { - // FIXME: this is not correct as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check - // For now lets just blank slides - /** - for (const line of checklines) { - const strline = math.getKeyFromCoords(line) - if (!moves.slideMoves[strline]) break r; - tempslides[strline] = moves.slideMoves[strline] - */ + // FIXME: this is a problem as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check + // Discuss before implementing a proper solution + // For now lets just blank sliding } else { - // Cannot slide to block all attack lines so blank the slides + // Cannot slide to block all attack lines so blank the sliding // Could probably blank regular attacks too } } else if (checklines.length === 1) { const strline = math.getKeyFromCoords(checklines[0]) - if (!moves.slideMoves[strline]) break r; - tempslides[strline] = moves.slideMoves[strline] + if (!moves.sliding[strline]) break r; + tempslides[strline] = moves.sliding[strline] } } @@ -365,7 +360,7 @@ const checkdetection = (function(){ if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! } - for (const linestr in moves.slides) { + for (const linestr in moves.sliding) { const line = math.getCoordsFromKey(linestr) const c1 = math.getLineFromCoords(line, coords) const c2 = math.getLineFromCoords(direction,square2) diff --git a/src/client/scripts/game/chess/gamefile.js b/src/client/scripts/game/chess/gamefile.js index fca729748..a0bb56ca8 100644 --- a/src/client/scripts/game/chess/gamefile.js +++ b/src/client/scripts/game/chess/gamefile.js @@ -50,7 +50,7 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) * @type {BoundingBox} */ box: undefined, /** Possible slide mopves*/ - slideMovesPossible: [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]], + slidingPossible: [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]], } diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 0875a389d..a3f8f9fa0 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -10,7 +10,7 @@ /** An object containing all the legal moves of a piece. * @typedef {Object} LegalMoves * @property {Object} individual - A list of the legal jumping move coordinates: `[[1,2], [2,1]]` - * @property {Object} slides - A dict containing length-2 arrays with the legal left and right slide limits: `{[1,0]:[-5, Infinity]}` + * @property {Object} sliding - A dict containing length-2 arrays with the legal left and right slide limits: `{[1,0]:[-5, Infinity]}` */ const legalmoves = (function(){ @@ -86,7 +86,7 @@ const legalmoves = (function(){ const thisPieceMoveset = getPieceMoveset(gamefile, type) // Default piece moveset let legalIndividualMoves = []; - let legalSlideMoves = {}; + let legalSliding = {}; if (!onlyCalcSpecials) { @@ -96,13 +96,13 @@ const legalmoves = (function(){ legalIndividualMoves = moves_RemoveOccupiedByFriendlyPieceOrVoid(gamefile, thisPieceMoveset.individual, color) // Legal sliding moves - if (thisPieceMoveset.slideMoves) { - let lines = gamefile.startSnapshot.slideMovesPossible; + if (thisPieceMoveset.sliding) { + let lines = gamefile.startSnapshot.slidingPossible; for (let i=0; i max) return false; @@ -360,8 +360,8 @@ const legalmoves = (function(){ function hasAtleast1Move (moves) { // { individual, horizontal, vertical, ... } if (moves.individual.length > 0) return true; - for (var line in moves.slides) - if (doesSlideHaveWidth(moves.slides[line])) return true; + for (var line in moves.sliding) + if (doesSlideHaveWidth(moves.sliding[line])) return true; function doesSlideHaveWidth(slide) { // [-Infinity, Infinity] if (!slide) return false; @@ -376,7 +376,7 @@ const legalmoves = (function(){ getPieceMoveset, calculate, checkIfMoveLegal, - doesSlideMovesetContainSquare, + doesslidingetContainSquare, hasAtleast1Move, slide_CalcLegalLimit, isOpponentsMoveLegal diff --git a/src/client/scripts/game/chess/movesets.js b/src/client/scripts/game/chess/movesets.js index 6a450519d..ea9798b72 100644 --- a/src/client/scripts/game/chess/movesets.js +++ b/src/client/scripts/game/chess/movesets.js @@ -61,7 +61,7 @@ const movesets = (function() { rooks: function () { return { individual: [], - slideMoves: { + sliding: { '1,0': [-slideLimit, slideLimit], '0,1': [-slideLimit, slideLimit] } @@ -70,7 +70,7 @@ const movesets = (function() { bishops: function () { return { individual: [], - slideMoves: { + sliding: { '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally '1,-1': [-slideLimit, slideLimit] } @@ -79,7 +79,7 @@ const movesets = (function() { queens: function () { return { individual: [], - slideMoves: { + sliding: { '1,0': [-slideLimit, slideLimit], '0,1': [-slideLimit, slideLimit], '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally @@ -90,7 +90,7 @@ const movesets = (function() { royalQueens: function () { return { individual: [], - slideMoves: { + sliding: { '1,0': [-slideLimit, slideLimit], '0,1': [-slideLimit, slideLimit], '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally @@ -104,7 +104,7 @@ const movesets = (function() { [-2,1],[-1,2],[1,2],[2,1], [-2,-1],[-1,-2],[1,-2],[2,-1] ], - slideMoves: { + sliding: { '1,0': [-slideLimit, slideLimit], '0,1': [-slideLimit, slideLimit] } @@ -126,7 +126,7 @@ const movesets = (function() { [-2,1],[-1,2],[1,2],[2,1], [-2,-1],[-1,-2],[1,-2],[2,-1] ], - slideMoves: { + sliding: { '1,0': [-slideLimit, slideLimit], '0,1': [-slideLimit, slideLimit], '1,1': [-slideLimit, slideLimit], // These represent the x limit of the piece sliding diagonally @@ -161,7 +161,7 @@ const movesets = (function() { knightriders: function () { return { individual: [], - slideMoves: { + sliding: { '1,2' : [-slideLimit, slideLimit], '1,-2' : [-slideLimit,slideLimit], '2,1' : [-slideLimit,slideLimit], diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index 8bad6baf1..bc4e26918 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -44,7 +44,7 @@ const organizedlines = { gamefile.piecesOrganizedByKey = {} gamefile.piecesOrganizedByLines = {} - let lines = gamefile.startSnapshot.slideMovesPossible + let lines = gamefile.startSnapshot.slidingPossible for (let i = 0; i {slides.add(slide)}); + if (!moveset.sliding) continue; + Object.keys(moveset.sliding).forEach(slide => {slides.add(slide)}); } let temp = []; slides.forEach(slideline => {temp.push(math.getCoordsFromKey(slideline))}) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index b90e5bf53..10069c9c9 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -392,6 +392,14 @@ const math = (function() { return corner; } + // Top left as failsafe + function getCornerOfBoundingBox(boundingBox, corner) { + const { left, right, top, bottom } = boundingBox; + let yval = corner.startsWith('bottom') ? bottom : top; + let xval = corner.endsWith('right') ? right : left + return [xval, yval] + } + function getLineIntersectionEntryTile (dx, dy, c, boundingBox, corner) { const { left, right, top, bottom } = boundingBox; let xIntersectBottom = undefined; @@ -922,6 +930,7 @@ const math = (function() { convertPixelsToWorldSpace_Virtual, convertWorldSpaceToPixels_Virtual, getAABBCornerOfLine, + getCornerOfBoundingBox, getLineIntersectionEntryTile, getIntersectionEntryTile, convertWorldSpaceToGrid, diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index 752ab5c8f..71a89471f 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -75,14 +75,7 @@ const arrows = (function() { // Same as above, but doesn't round const boundingBoxFloat = perspective.getEnabled() ? math.generatePerspectiveBoundingBox(perspectiveDist) : board.gboundingBoxFloat() - const horzRight = {} - const horzLeft = {} - const vertTop = {} - const vertBottom = {} - const upDiagRight = {} - const upDiagLeft = {} - const downDiagRight = {} - const downDiagLeft = {} + const slideArrows = {}; let headerPad = perspective.getEnabled() ? 0 : math.convertPixelsToWorldSpace_Virtual(camera.getPIXEL_HEIGHT_OF_TOP_NAV()) let footerPad = perspective.getEnabled() ? 0 : math.convertPixelsToWorldSpace_Virtual(camera.getPIXEL_HEIGHT_OF_BOTTOM_NAV()) @@ -100,92 +93,74 @@ const arrows = (function() { paddedBoundingBox.bottom += math.convertWorldSpaceToGrid(footerPad) } - gamefileutility.forEachPieceInPiecesByType(calcPiecesOffScreen, game.getGamefile().ourPieces) + const gamefile = game.getGamefile() + const slides = gamefile.startSnapshot.slidingPossible - function calcPiecesOffScreen(type, coords) { + for (const line of slides) { + const perpendicular = [-line[1], line[0]]; + const linestr = math.getKeyFromCoords(line); + + let boardCornerLeft = math.getAABBCornerOfLine(perpendicular,true); + let boardCornerRight = math.getAABBCornerOfLine(perpendicular,false); - if (!coords) return; + boardCornerLeft = math.getCornerOfBoundingBox(paddedBoundingBox,boardCornerLeft); + boardCornerRight = math.getCornerOfBoundingBox(paddedBoundingBox,boardCornerRight); - // Is the piece off-screen? + const boardSlidesRight = math.getLineFromCoords(line, boardCornerLeft); + const boardSlidesLeft = math.getLineFromCoords(line, boardCornerRight); - if (math.boxContainsSquare(boundingBox, coords)) return; + const boardSlidesStart = Math.min(boardSlidesLeft, boardSlidesRight); + const boardSlidesEnd = Math.max(boardSlidesLeft, boardSlidesRight); - const x = coords[0] - const y = coords[1] + for (let i=Math.ceil(boardSlidesStart); i<=Math.floor(boardSlidesEnd); i++) { + for (let x=0; x boundingBox.bottom && y < boundingBox.top) { + if (math.isEmpty(pieces)) continue; - // Left or right side? - const rightSide = x > boundingBox.right; - - if (rightSide) { - // What is the current piece on the right side? Is this piece more left than it? Don't render both - if (!horzRight[y]) horzRight[y] = { type, coords } - else if (x < horzRight[y].coords[0]) horzRight[y] = { type, coords } - } else { // Left side - if (!horzLeft[y]) horzLeft[y] = { type, coords } - else if (x > horzLeft[y].coords[0]) horzLeft[y] = { type, coords } + if (!slideArrows[linestr]) slideArrows[linestr] = {}; + + slideArrows[linestr][key] = pieces } } + } - // Vertical. Same column as one onscreen? - if (x > boundingBox.left && x < boundingBox.right) { - const topSide = y > boundingBox.top; - if (topSide) { - if (!vertTop[x]) vertTop[x] = { type, coords } - else if (y < vertTop[x].coords[1]) vertTop[x] = { type, coords } - } else { - if (!vertBottom[x]) vertBottom[x] = { type, coords } - else if (y > vertBottom[x].coords[1]) vertBottom[x] = { type, coords } - } - } + function calcPiecesOffScreen(line, organizedline) { - // Up Diagonal. Same diag as one onscreen? - { - const diagUp = math.getLineFromCoords([1,1], coords) - const boardCornerTopLeft = [paddedBoundingBox.left, paddedBoundingBox.top] - const boardCornerBottomRight = [paddedBoundingBox.right, paddedBoundingBox.bottom] - const boardDiagUpStart = math.getLineFromCoords([1, 1], boardCornerTopLeft) - const boardDiagUpEnd = math.getLineFromCoords([1, 1], boardCornerBottomRight) - if (diagUp < boardDiagUpStart && diagUp > boardDiagUpEnd) { - const topRightSide = y > paddedBoundingBox.top || x > paddedBoundingBox.right; - if (topRightSide) { - if (!upDiagRight[diagUp]) upDiagRight[diagUp] = { type, coords } - else if (x < upDiagRight[diagUp].coords[0]) upDiagRight[diagUp] = { type, coords } - } else { // Left side - if (!upDiagLeft[diagUp]) upDiagLeft[diagUp] = { type, coords } - else if (x > upDiagLeft[diagUp].coords[0]) upDiagLeft[diagUp] = { type, coords } - } - } - } + let left; + let right; + for (var piece of organizedline) { + if (!piece.coords) continue; + + // Is the piece off-screen? + if (math.boxContainsSquare(boundingBox, piece.coords)) return; + + const x = piece.coords[0]; + const y = piece.coords[1]; + const axis = line == 0 ? 1 : 0; - // Down Diagonal. Same diag as one onscreen? - { - const diagDown = math.getLineFromCoords([1, -1],coords) - const boardCornerBottomLeft = [paddedBoundingBox.left, paddedBoundingBox.bottom] - const boardCornerTopRight = [paddedBoundingBox.right, paddedBoundingBox.top] - const boardDiagDownStart = math.getLineFromCoords([1, -1], boardCornerBottomLeft) - const boardDiagDownEnd = math.getLineFromCoords([1, -1], boardCornerTopRight) - // console.log(boardDiagDownStart, diagDown, boardDiagDownEnd) - if (diagDown > boardDiagDownStart && diagDown < boardDiagDownEnd) { - const topLeftSide = y > paddedBoundingBox.top || x < paddedBoundingBox.left; - if (topLeftSide) { - if (!downDiagLeft[diagDown]) downDiagLeft[diagDown] = { type, coords } - else if (x > downDiagLeft[diagDown].coords[0]) downDiagLeft[diagDown] = { type, coords } - } else { // Left side - if (!downDiagRight[diagDown]) downDiagRight[diagDown] = { type, coords } - else if (x < downDiagRight[diagDown].coords[0]) downDiagRight[diagDown] = { type, coords } - } + const rightCorner = math.getCornerOfBoundingBox(paddedBoundingBox, math.getAABBCornerOfLine(line,false)); + const rightSide = x > paddedBoundingBox.right || y > rightCorner[1] == (rightCorner[1]==paddedBoundingBox.top); + + if (rightSide) { + if (!right) right = piece; + else if (piece.coords[axis]left.coords[axis]) left = piece; } } + + const dirs = {} + if (right) dirs["r"]=right + if (left) dirs["l"]=left + return dirs } // If we are in only-show-attackers mode - removeTypesWithIncorrectMoveset(horzRight, horzLeft, vertTop, vertBottom, upDiagRight, upDiagLeft, downDiagRight, downDiagLeft) - - - + removeTypesWithIncorrectMoveset(slideArrows) // Calc the model data... @@ -195,32 +170,23 @@ const arrows = (function() { let padding = (worldWidth/2) + sidePadding * boardScale; if (perspective.getEnabled()) padding = 0; - iterateThroughStraightLine(horzRight, false, 'right') - iterateThroughStraightLine(horzLeft, false, 'left') - iterateThroughStraightLine(vertTop, true, 'top') - iterateThroughStraightLine(vertBottom, true, 'bottom') - - function iterateThroughStraightLine(line, isVertical, direction) { - for (const key in line) { - const piece = line[key] // { type, coords } - if (piece.type === 'voidsN') continue; - const renderCoords = isVertical ? [piece.coords[0], boundingBoxFloat[direction]] : [boundingBoxFloat[direction], piece.coords[1]] - concatData(renderCoords, piece.type, direction, worldWidth, padding, headerPad, footerPad, piece.coords) - } + for (const strline in slideArrows) { + const line = math.getCoordsFromKey(strline) + iterateThroughDiagLine(slideArrows[strline], line) } - iterateThroughDiagLine(upDiagRight, math.getUpDiagonalFromCoords, 1, 'topright') - iterateThroughDiagLine(upDiagLeft, math.getUpDiagonalFromCoords, 1, 'bottomleft') - iterateThroughDiagLine(downDiagRight, math.getDownDiagonalFromCoords, -1, 'bottomright') - iterateThroughDiagLine(downDiagLeft, math.getDownDiagonalFromCoords, -1, 'topleft') - - function iterateThroughDiagLine(line, mathDiagFunc, oneOrNegOne, direction) { - for (const key in line) { - const piece = line[key] // { type, coords } - if (piece.type === 'voidsN') continue; - const diag = mathDiagFunc(piece.coords) - const renderCoords = math.getIntersectionEntryTile(oneOrNegOne, diag, paddedBoundingBox, direction) - concatData(renderCoords, piece.type, direction, worldWidth, padding, headerPad, footerPad, piece.coords) + function iterateThroughDiagLine(lines, direction) { + for (const diag in lines) { + for (const side in lines[diag]) { + const piece = lines[diag][side] + let intersect=Number(diag.split("|")[0]) + if (piece.type === 'voidsN') continue; + const isLeft = side==="l" + const corner = math.getAABBCornerOfLine(direction, isLeft) + const renderCoords = math.getLineIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) + const arrowDirection = isLeft ? [-direction[0],-direction[1]] : direction + concatData(renderCoords, piece.type, corner, worldWidth, padding, headerPad, footerPad, piece.coords, arrowDirection, !isLeft) + } } } @@ -230,34 +196,35 @@ const arrows = (function() { modelArrows = buffermodel.createModel_Colored(new Float32Array(dataArrows), 2, "TRIANGLES") } - function removeTypesWithIncorrectMoveset(horzRight, horzLeft, vertTop, vertBottom, upDiagRight, upDiagLeft, downDiagRight, downDiagLeft) { + function removeTypesWithIncorrectMoveset(arrows) { if (mode !== 1) return; const gamefile = game.getGamefile(); - removeTypesWithIncorrectMoveset_2(horzRight, 'horizontal'); - removeTypesWithIncorrectMoveset_2(horzLeft, 'horizontal'); - removeTypesWithIncorrectMoveset_2(vertTop, 'vertical'); - removeTypesWithIncorrectMoveset_2(vertBottom, 'vertical'); - removeTypesWithIncorrectMoveset_2(upDiagRight, 'diagonalUp'); - removeTypesWithIncorrectMoveset_2(upDiagLeft, 'diagonalUp'); - removeTypesWithIncorrectMoveset_2(downDiagRight, 'diagonalDown'); - removeTypesWithIncorrectMoveset_2(downDiagLeft, 'diagonalDown'); + for (const strline in arrows) { + removeTypesWithIncorrectMoveset_2(arrows[strline],strline) + if (math.isEmpty(arrows[strline])) delete arrows[strline]; + } function removeTypesWithIncorrectMoveset_2(object, direction) { // horzRight, vertical/diagonalUp for (const key in object) { - const type = object[key].type; // { type, coords } - if (!doesTypeHaveMoveset(gamefile, type, direction)) delete object[key] + // { type, coords } + for (const side in object[key]) { + const type = object[key][side].type; + if (!doesTypeHaveMoveset(gamefile, type, direction)) delete object[key][side]; + } + if (math.isEmpty(object[key])) delete object[key]; } } function doesTypeHaveMoveset(gamefile, type, direction) { const moveset = legalmoves.getPieceMoveset(gamefile, type) - return moveset[direction] != null; + if (!moveset.sliding) {if (['rooks','knightriders','queens'].includes(type)) console.error(`${type} should have sliding moves wtf`); return false} + return moveset.sliding[direction] != null; } } - function concatData(renderCoords, type, paddingDir, worldWidth, padding, headerPad, footerPad, pieceCoords) { + function concatData(renderCoords, type, paddingDir, worldWidth, padding, headerPad, footerPad, pieceCoords, direction) { const worldHalfWidth = worldWidth/2 // Convert to world-space @@ -326,14 +293,7 @@ const arrows = (function() { [dist+size, 0] ] - const angle = paddingDir === 'top' ? 90 - : paddingDir === 'left' ? 180 - : paddingDir === 'bottom' ? 270 - : paddingDir === 'topright' ? 45 - : paddingDir === 'topleft' ? 135 - : paddingDir === 'bottomleft' ? 225 - : paddingDir === 'bottomright' ? 315 - : 0; + const angle = Math.atan2(direction[1], direction[0]) const ad = applyTransform(points, angle, worldCoords) for (let i = 0; i < ad.length; i++) { @@ -345,12 +305,11 @@ const arrows = (function() { function applyTransform(points, rotation, translation) { // convert rotation angle to radians - const angleRad = rotation * Math.PI / 180; // apply rotation matrix and translation vector to each point const transformedPoints = points.map(point => { - const cos = Math.cos(angleRad); - const sin = Math.sin(angleRad); + const cos = Math.cos(rotation); + const sin = Math.sin(rotation); const xRot = point[0] * cos - point[1] * sin; const yRot = point[0] * sin + point[1] * cos; const xTrans = xRot + translation[0]; diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index 3d7a974cf..7beeee07a 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -43,7 +43,7 @@ const highlightline = (function(){ let closestDistance; let closestPoint; - for (var strline in legalmoves.slides) { + for (var strline in legalmoves.sliding) { const line = math.getCoordsFromKey(strline); const diag = math.getLineFromCoords(line, worldSpaceCoords); const lineIsVertical = line[0]===0 @@ -52,7 +52,7 @@ const highlightline = (function(){ let point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); if (!point1) {continue}; - const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.slides[strline], line, false); + const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, false); const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord); point1 = capPointAtSlideLimit(point1, leftLimitPointWorld, false, lineIsVertical); @@ -60,7 +60,7 @@ const highlightline = (function(){ let point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); if (!point2) continue; // I hate this - const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.slides[strline], line, true); + const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, true); const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord); point2 = capPointAtSlideLimit(point2, rightLimitPointWorld, true, lineIsVertical); @@ -70,7 +70,7 @@ const highlightline = (function(){ if (!closestDistance) {if (snapPoint.distance>snapDist) continue;} else if (snapPoint.distance>closestDistance) {continue;} closestDistance = snapPoint.distance - snapPoint.moveset = legalmoves.slides[strline] + snapPoint.moveset = legalmoves.sliding[strline] snapPoint.line = line closestPoint = snapPoint }; diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 25a84d83c..993ed4b97 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -233,13 +233,13 @@ const highlights = (function(){ function concatData_HighlightedMoves_Sliding_Horz(coords, left, right) { const legalMoves = selection.getLegalMovesOfSelectedPiece() - if (!legalMoves.slides['1,0']) return; // Break if no legal horizontal slide + if (!legalMoves.sliding['1,0']) return; // Break if no legal horizontal slide const [r,g,b,a] = options.getDefaultLegalMoveHighlight(); // Left - let startXWithoutOffset = legalMoves.slides['1,0'][0] - board.gsquareCenter() + let startXWithoutOffset = legalMoves.sliding['1,0'][0] - board.gsquareCenter() if (startXWithoutOffset < left - board.gsquareCenter()) startXWithoutOffset = left - board.gsquareCenter() let startX = startXWithoutOffset - model_Offset[0]; @@ -251,7 +251,7 @@ const highlights = (function(){ // Right - startXWithoutOffset = legalMoves.slides['1,0'][1] + 1 - board.gsquareCenter() + startXWithoutOffset = legalMoves.sliding['1,0'][1] + 1 - board.gsquareCenter() if (startXWithoutOffset > right + 1 - board.gsquareCenter()) startXWithoutOffset = right + 1 - board.gsquareCenter() startX = startXWithoutOffset - model_Offset[0]; @@ -264,13 +264,13 @@ const highlights = (function(){ function concatData_HighlightedMoves_Sliding_Vert (coords, bottom, top) { const legalMoves = selection.getLegalMovesOfSelectedPiece() - if (!legalMoves.slides['0,1']) return; // Break if there no legal vertical slide + if (!legalMoves.sliding['0,1']) return; // Break if there no legal vertical slide const [r,g,b,a] = options.getDefaultLegalMoveHighlight(); // Bottom - let startYWithoutOffset = legalMoves.slides['0,1'][0] - board.gsquareCenter() + let startYWithoutOffset = legalMoves.sliding['0,1'][0] - board.gsquareCenter() if (startYWithoutOffset < bottom - board.gsquareCenter()) startYWithoutOffset = bottom - board.gsquareCenter() let startY = startYWithoutOffset - model_Offset[1]; @@ -282,7 +282,7 @@ const highlights = (function(){ // Top - startYWithoutOffset = legalMoves.slides['0,1'][1] + 1 - board.gsquareCenter() + startYWithoutOffset = legalMoves.sliding['0,1'][1] + 1 - board.gsquareCenter() if (startYWithoutOffset > top + 1 - board.gsquareCenter()) startYWithoutOffset = top + 1 - board.gsquareCenter() startY = startYWithoutOffset - model_Offset[1]; @@ -295,7 +295,7 @@ const highlights = (function(){ function concatData_HighlightedMoves_Diagonals (coords, renderBoundingBox, r, g, b, a) { const legalMoves = selection.getLegalMovesOfSelectedPiece() - for (var strline in legalMoves.slides) { + for (var strline in legalMoves.sliding) { const line = math.getCoordsFromKey(strline); if (line[1] == 0 || line[0] == 0) {continue;}; const lineEqua = math.getLineFromCoords(line, coords); @@ -307,9 +307,9 @@ const highlights = (function(){ const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2); if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} // FIXME: This should not happen - if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a); - else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.slides[line], line, r, g, b, a) + if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} + if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.sliding[line], line, r, g, b, a); + else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.sliding[line], line, r, g, b, a) } } @@ -333,10 +333,10 @@ const highlights = (function(){ if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() -step[0] + 2 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() -step[1] + 2 - model_Offset[1] + let currentX = startTile[0] - board.gsquareCenter() - step[0] + 1 - model_Offset[0] + let currentY = startTile[1] - board.gsquareCenter() - step[1] + 1 - model_Offset[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, -1, -1, [-step[0], -step[1]], r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [-step[0], -step[1]], r, g, b, a) } { // Up Right moveset From 99c5c3205d49fcd1991c06c0cc9da084e523e8d4 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Tue, 16 Jul 2024 21:42:55 +0100 Subject: [PATCH 22/58] All arrow changes and arrow general fixes --- src/client/scripts/game/rendering/arrows.js | 36 ++++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index 71a89471f..c3a674a9c 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -97,6 +97,7 @@ const arrows = (function() { const slides = gamefile.startSnapshot.slidingPossible for (const line of slides) { + const axis = line[0] == 0 ? 1 : 0; const perpendicular = [-line[1], line[0]]; const linestr = math.getKeyFromCoords(line); @@ -113,7 +114,7 @@ const arrows = (function() { const boardSlidesEnd = Math.max(boardSlidesLeft, boardSlidesRight); for (let i=Math.ceil(boardSlidesStart); i<=Math.floor(boardSlidesEnd); i++) { - for (let x=0; x paddedBoundingBox.right || y > rightCorner[1] == (rightCorner[1]==paddedBoundingBox.top); - if (rightSide) { if (!right) right = piece; else if (piece.coords[axis] Date: Tue, 16 Jul 2024 22:29:56 +0100 Subject: [PATCH 23/58] Jsdoc and renaming --- src/client/scripts/game/rendering/arrows.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index c3a674a9c..136a4dcb9 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -161,7 +161,7 @@ const arrows = (function() { } // If we are in only-show-attackers mode - removeTypesWithIncorrectMoveset(slideArrows) + removeUnnecessaryArrows(slideArrows) // Calc the model data... @@ -196,7 +196,14 @@ const arrows = (function() { modelArrows = buffermodel.createModel_Colored(new Float32Array(dataArrows), 2, "TRIANGLES") } - function removeTypesWithIncorrectMoveset(arrows) { + /** + * Removes asrrows based on the mode. + * mode == 1 Removes arrows to pieces that cant slide in that direction + * mode == 2 Like mode 1 but will keep any arrows in directions that a selected piece can move + * Will not return anything as it alters the object it is given. + * @param {Object} arrows + */ + function removeUnnecessaryArrows(arrows) { if (mode === 0) return; const gamefile = game.getGamefile(); @@ -209,11 +216,11 @@ const arrows = (function() { } for (const strline in arrows) { if (attacklines.includes(strline)) {continue;} - removeTypesWithIncorrectMoveset_2(arrows[strline],strline) + removeTypesWithIncorrectMoveset(arrows[strline],strline) if (math.isEmpty(arrows[strline])) delete arrows[strline]; } - function removeTypesWithIncorrectMoveset_2(object, direction) { // horzRight, vertical/diagonalUp + function removeTypesWithIncorrectMoveset(object, direction) { // horzRight, vertical/diagonalUp for (const key in object) { // { type, coords } for (const side in object[key]) { From e151390261a5225bf4afa513e130e47562324e32 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 12:15:43 +0100 Subject: [PATCH 24/58] highlight.js fix --- .../scripts/game/chess/checkdetection.js | 1 + src/client/scripts/game/misc/math.js | 9 ++ .../scripts/game/rendering/highlights.js | 129 +++++------------- 3 files changed, 47 insertions(+), 92 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index f171eb9e3..2e47f2a8c 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -321,6 +321,7 @@ const checkdetection = (function(){ if (checklines.length > 1) { if (math.areLinesCollinear(checklines)) { // FIXME: this is a problem as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check + // Another case (3,0) (2,0) correct blocks are along (6,0) but thats not an organized line // Discuss before implementing a proper solution // For now lets just blank sliding } else { diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 10069c9c9..3d185ccf3 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -370,6 +370,13 @@ const math = (function() { } } + function getLineSteps(step, origin, coord) { + let x = Math.floor(coord[0]-origin[0]/step[0]) + if (step[0]!==0) return x; + let y = Math.floor(coord[1]-origin[1]/step[1]) + return y + } + function convertPixelsToWorldSpace_Virtual(value) { return (value / camera.getCanvasHeightVirtualPixels()) * (camera.getScreenBoundingBox(false).top - camera.getScreenBoundingBox(false).bottom) } @@ -925,7 +932,9 @@ const math = (function() { convertWorldSpaceToCoords_Rounded, convertCoordToWorldSpace, convertCoordToWorldSpace_ClampEdge, + clamp, closestPointOnLine, + getLineSteps, getBoundingBoxOfBoard, convertPixelsToWorldSpace_Virtual, convertWorldSpaceToPixels_Virtual, diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 993ed4b97..6454a73c8 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -239,7 +239,7 @@ const highlights = (function(){ // Left - let startXWithoutOffset = legalMoves.sliding['1,0'][0] - board.gsquareCenter() + let startXWithoutOffset = legalMoves.sliding['1,0'][0] + coords[0] - board.gsquareCenter() if (startXWithoutOffset < left - board.gsquareCenter()) startXWithoutOffset = left - board.gsquareCenter() let startX = startXWithoutOffset - model_Offset[0]; @@ -251,7 +251,7 @@ const highlights = (function(){ // Right - startXWithoutOffset = legalMoves.sliding['1,0'][1] + 1 - board.gsquareCenter() + startXWithoutOffset = legalMoves.sliding['1,0'][1] + coords[0] + 1 - board.gsquareCenter() if (startXWithoutOffset > right + 1 - board.gsquareCenter()) startXWithoutOffset = right + 1 - board.gsquareCenter() startX = startXWithoutOffset - model_Offset[0]; @@ -270,7 +270,7 @@ const highlights = (function(){ // Bottom - let startYWithoutOffset = legalMoves.sliding['0,1'][0] - board.gsquareCenter() + let startYWithoutOffset = legalMoves.sliding['0,1'][0] + coords[1] - board.gsquareCenter() if (startYWithoutOffset < bottom - board.gsquareCenter()) startYWithoutOffset = bottom - board.gsquareCenter() let startY = startYWithoutOffset - model_Offset[1]; @@ -282,7 +282,7 @@ const highlights = (function(){ // Top - startYWithoutOffset = legalMoves.sliding['0,1'][1] + 1 - board.gsquareCenter() + startYWithoutOffset = legalMoves.sliding['0,1'][1] + coords[1] + 1 - board.gsquareCenter() if (startYWithoutOffset > top + 1 - board.gsquareCenter()) startYWithoutOffset = top + 1 - board.gsquareCenter() startY = startYWithoutOffset - model_Offset[1]; @@ -295,129 +295,74 @@ const highlights = (function(){ function concatData_HighlightedMoves_Diagonals (coords, renderBoundingBox, r, g, b, a) { const legalMoves = selection.getLegalMovesOfSelectedPiece() - for (var strline in legalMoves.sliding) { + const lineSet = new Set(Object.keys(legalMoves.sliding)) + lineSet.delete('1,0') + lineSet.delete('0,1') + for (const strline of lineSet) { const line = math.getCoordsFromKey(strline); - if (line[1] == 0 || line[0] == 0) {continue;}; const lineEqua = math.getLineFromCoords(line, coords); - const lineGrad = line[1]/line[0]; const corner1 = math.getAABBCornerOfLine(line, true); const corner2 = math.getAABBCornerOfLine(line, false); const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1); const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2); - + if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} - if (lineGrad > 0) concatData_HighlightedMoves_Diagonal_Up(coords, intsect1Tile, intsect2Tile, legalMoves.sliding[line], line, r, g, b, a); - else concatData_HighlightedMoves_Diagonal_Down(coords, intsect1Tile, intsect2Tile, legalMoves.sliding[line], line, r, g, b, a) - } - } - - function concatData_HighlightedMoves_Diagonal_Up (coords, intsect1Tile, intsect2Tile, limits, step, r, g, b, a) { - { // Down Left moveset - let startTile = intsect2Tile - let endTile = intsect1Tile - - // Make sure it doesn't start before the tile right in front of us - if (startTile[0] > coords[0] - 1) startTile = [coords[0] - 1, coords[1] - 1] - let diagonalUpLimit = limits[0] - - // Make sure it doesn't phase through our move limit - if (endTile[0] < diagonalUpLimit) { - endTile[0] = diagonalUpLimit - endTile[1] = startTile[1] + diagonalUpLimit - startTile[0] - } - - // How many times will we iterate? - let iterateCount = startTile[0] - endTile[0] + 1 - if (iterateCount < 0) iterateCount = 0 - - // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() - step[0] + 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() - step[1] + 1 - model_Offset[1] - // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [-step[0], -step[1]], r, g, b, a) - } - - { // Up Right moveset - let startTile = intsect1Tile - let endTile = intsect2Tile - // Make sure it doesn't start before the tile right in front of us - if (startTile[0] < coords[0] + 1) startTile = [coords[0] + 1, coords[1] + 1] - let diagonalUpLimit = limits[1] - - // Make sure it doesn't phase through our move limit - if (endTile[0] > diagonalUpLimit) { - endTile[0] = diagonalUpLimit - endTile[1] = startTile[1] + diagonalUpLimit - startTile[0] - } - - // How many times will we iterate? - let iterateCount = endTile[0] - startTile[0] + 1 - if (iterateCount < 0) iterateCount = 0 - - // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + step[0] - 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + step[1] - 1 - model_Offset[1] + const intsect1Step = math.getLineSteps(line, coords, intsect1Tile) + const intsect2Step = math.getLineSteps(line, coords, intsect2Tile) - // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, step, r, g, b, a) + concatData_HighlightedMoves_Diagonal(coords, intsect1Step, intsect2Step, legalMoves.sliding[line], line, r, g, b, a); } } - function concatData_HighlightedMoves_Diagonal_Down (coords, intsect1Tile, intsect2Tile, limits, step , r, g, b, a) { - { // Up Left moveset - let startTile = intsect2Tile - let endTile = intsect1Tile - - // Make sure it doesn't start before the tile right in front of us - if (startTile[0] > coords[0] - 1) startTile = [coords[0] - 1, coords[1] + 1] - let diagonalDownLimit = limits[0] + function concatData_HighlightedMoves_Diagonal (coords, intsect1Step, intsect2Step, limits, step, r, g, b, a) { + { // Left moveset + let startStep = intsect1Step + let endStep = intsect2Step + + // Make sure it doesn't end before the tile right in front of us + if (endStep >= 0) endStep = -1 + let leftLimit = limits[0] // Make sure it doesn't phase through our move limit - if (endTile[0] < diagonalDownLimit) { - endTile[0] = diagonalDownLimit - endTile[1] = startTile[1] + startTile[0] - diagonalDownLimit + if (startStep < leftLimit) { + startStep = leftLimit } // How many times will we iterate? - let iterateCount = startTile[0] - endTile[0] + 1 + let iterateCount = endStep - startStep + 1 if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() - step[0] + 2 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() - step[1] - 1 - model_Offset[1] - + let currentX = startStep * step[0] - board.gsquareCenter() - model_Offset[0] + coords[0] + let currentY = startStep * step[1] - board.gsquareCenter() - model_Offset[1] + coords[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, -1, +1, [-step[0], -step[1]], r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [step[0], step[1]], r, g, b, a) } - - { // Down Right moveset - - let startTile = intsect1Tile - let endTile = intsect2Tile + { // Right moveset + let startStep = intsect1Step + let endStep = intsect2Step // Make sure it doesn't start before the tile right in front of us - if (startTile[0] < coords[0] + 1) startTile = [coords[0] + 1, coords[1] - 1] - let diagonalDownLimit = limits[1] + if (startStep <= 0) startStep = 1 + let rightLimit = limits[1] // Make sure it doesn't phase through our move limit - if (endTile[0] > diagonalDownLimit) { - endTile[0] = diagonalDownLimit - endTile[1] = startTile[1] + diagonalDownLimit - startTile[0] + if (endStep > rightLimit) { + endStep = rightLimit } // How many times will we iterate? - let iterateCount = endTile[0] - startTile[0] + 1 + let iterateCount = endStep - startStep + 1 if (iterateCount < 0) iterateCount = 0 // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startTile[0] - board.gsquareCenter() + step[0] - 1 - model_Offset[0] - let currentY = startTile[1] - board.gsquareCenter() + step[1] + 2 - model_Offset[1] - + let currentX = startStep * step[0] - board.gsquareCenter() - model_Offset[0] + coords[0] + let currentY = startStep * step[1] - board.gsquareCenter() - model_Offset[1] + coords[1] // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, -1, step, r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [step[0], step[1]], r, g, b, a) } } From 3128b1b012912963f17eeb85da88bd589f29252f Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 13:46:08 +0100 Subject: [PATCH 25/58] "fixed" edgecase FUTURE OPTIMIZATIONS NEEDED! --- .../scripts/game/chess/checkdetection.js | 36 ++++++++++++- src/client/scripts/game/misc/math.js | 50 +++++++++++++++++-- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 2e47f2a8c..7e08ec418 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -322,8 +322,40 @@ const checkdetection = (function(){ if (math.areLinesCollinear(checklines)) { // FIXME: this is a problem as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check // Another case (3,0) (2,0) correct blocks are along (6,0) but thats not an organized line - // Discuss before implementing a proper solution - // For now lets just blank sliding + + // Please can someone optimize this + + let fline = checklines[0]; + let fGcd = math.GCD(fline[0],fline[1]); + + const baseLine = [fline[0]/fGcd, fline[1]/fGcd]; + + let mult = []; + checklines.forEach((line) => {mult.push(math.GCD(line[0],line[1]))}); + const lcm = math.LCM(Object.values(mult)); + + const steps = [0,0] + for (const strline in moves.sliding) { + const line = math.getCoordsFromKey(strline); + if (!math.areLinesCollinear([line, baseLine])) continue; + const gcd = math.GCD(line[0], line[1]); + let rslides = [Math.floor(moves.sliding[strline][0]/lcm*gcd),Math.floor(moves.sliding[strline][1]/lcm*gcd)]; + if (rslides[0]steps[1]) steps[1] = rslides[1]; + } + + const line = [baseLine[0]*lcm,baseLine[1]*lcm] + + if (!gamefile.startSnapshot.slidingPossible.includes(line)) { + const strline = math.getKeyFromCoords(line) + tempslides[strline] = steps + } else { + for (i=steps[0]; i<=steps[1]; i++) { + if (i==0) continue; + moves.individual.push([line[0]*i,line[1]*i]) + } + } + } else { // Cannot slide to block all attack lines so blank the sliding // Could probably blank regular attacks too diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 3d185ccf3..afa95c551 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -189,7 +189,6 @@ const math = (function() { return `${getLineFromCoords(step,coords)}|${coordAxis - (Math.floor(coordAxis / deltaAxis) * deltaAxis)}` } - // TODO: implement this /** * Checks if all lines are colinear aka `[[1,0],[2,0]]` would be as they are both the same direction * @param {Number[][]} lines Array of vectors `[[1,0],[2,0]]` @@ -197,7 +196,12 @@ const math = (function() { */ function areLinesCollinear(lines) { - return false + let gradient + for (line of lines) { + if (!gradient) gradient = line[0]/line[1] + if (!isAproxEqual(line[0]/line[1], gradient)) return false; + } + return true } /** @@ -911,6 +915,44 @@ const math = (function() { return totalMilliseconds; } + function orderSwap(a,b) { + return [Math.min(a,b), Math.max(a,b)] + } + + /** + * Get the GCD of two numbers + * Copied from https://www.geeksforgeeks.org/gcd-greatest-common-divisor-practice-problems-for-competitive-programming/ + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ + function GCD(a, b) { + [a,b] = orderSwap(a,b) + if (b === 0) { + return a; + } else { + return gcd(b, a % b); + } + } + + /** + * Get the LCM of an array + * Copied from https://www.geeksforgeeks.org/lcm-of-given-array-elements/ + * @param {Number[]} arr + */ + function LCM(arr) { + // Initialize result + let ans = arr[0]; + + // ans contains LCM of arr[0], ..arr[i] + // after i'th iteration, + for (let i = 1; i < arr.length; i++) + ans = (((arr[i] * ans)) / + (gcd(arr[i], ans))); + + return ans; + } + return Object.freeze({ isPowerOfTwo, isAproxEqual, @@ -980,6 +1022,8 @@ const math = (function() { minutesToMillis, secondsToMillis, getTotalMilliseconds, - genUniqueID + genUniqueID, + GCD, + LCM }); })(); \ No newline at end of file From 90683758445f276e2ddc11c886c6f135cf4bb71c Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 13:49:29 +0100 Subject: [PATCH 26/58] cleanup --- src/client/scripts/game/rendering/highlights.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 6454a73c8..239ed2ee7 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -222,13 +222,6 @@ const highlights = (function(){ concatData_HighlightedMoves_Sliding_Vert(coords, boundingBoxOfRenderRange.bottom, boundingBoxOfRenderRange.top) // Calculate the data of the diagonals concatData_HighlightedMoves_Diagonals(coords, boundingBoxOfRenderRange, r, g, b, a) - /** - // Calculate the data of the up diagonal - concatData_HighlightedMoves_Diagonal_Up(coords, boundingBoxOfRenderRange, r, g, b, a) - - // Calculate the data of the down diagonal - concatData_HighlightedMoves_Diagonal_Down(coords, boundingBoxOfRenderRange, r, g, b, a) - */ } function concatData_HighlightedMoves_Sliding_Horz(coords, left, right) { From 1cd7a78fcd444977ee1cc678f90253407fba81e7 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 18:45:52 +0100 Subject: [PATCH 27/58] not me ignoring what the function does --- src/client/scripts/game/misc/math.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index afa95c551..a125b809a 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -915,10 +915,6 @@ const math = (function() { return totalMilliseconds; } - function orderSwap(a,b) { - return [Math.min(a,b), Math.max(a,b)] - } - /** * Get the GCD of two numbers * Copied from https://www.geeksforgeeks.org/gcd-greatest-common-divisor-practice-problems-for-competitive-programming/ @@ -927,11 +923,10 @@ const math = (function() { * @returns {Number} */ function GCD(a, b) { - [a,b] = orderSwap(a,b) if (b === 0) { return a; } else { - return gcd(b, a % b); + return GCD(b, a % b); } } @@ -948,7 +943,7 @@ const math = (function() { // after i'th iteration, for (let i = 1; i < arr.length; i++) ans = (((arr[i] * ans)) / - (gcd(arr[i], ans))); + (GCD(arr[i], ans))); return ans; } From 97be0ef97e94f772f7d5fbc9bd4da25c0d280827 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 20:18:05 +0100 Subject: [PATCH 28/58] Fixed highlights by doing maths --- src/client/scripts/game/misc/math.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index a125b809a..408a133f6 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -375,9 +375,9 @@ const math = (function() { } function getLineSteps(step, origin, coord) { - let x = Math.floor(coord[0]-origin[0]/step[0]) + let x = Math.floor((coord[0]-origin[0])/step[0]) if (step[0]!==0) return x; - let y = Math.floor(coord[1]-origin[1]/step[1]) + let y = Math.floor((coord[1]-origin[1])/step[1]) return y } From efb42ae2c4b6b0686550a6b1e9d40809581208c1 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 20:29:26 +0100 Subject: [PATCH 29/58] highlight shouldn't render outside bounding box --- src/client/scripts/game/misc/math.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 408a133f6..f93dfc103 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -375,9 +375,13 @@ const math = (function() { } function getLineSteps(step, origin, coord) { - let x = Math.floor((coord[0]-origin[0])/step[0]) + let x = (coord[0]-origin[0])/step[0] + if (x>0) x = Math.floor(x) + else x = Math.ceil(x) if (step[0]!==0) return x; let y = Math.floor((coord[1]-origin[1])/step[1]) + if (x>0) x = Math.floor(x) + else x = Math.ceil(x) return y } From 2c81b7ce24180cde40106e57720a31545815ec84 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 20:46:22 +0100 Subject: [PATCH 30/58] fixed icons being slightly out of place --- src/client/scripts/game/rendering/arrows.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index 136a4dcb9..eb9939d3f 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -169,6 +169,14 @@ const arrows = (function() { const boardScale = movement.getBoardScale(); const worldWidth = width * boardScale; let padding = (worldWidth/2) + sidePadding * boardScale; + let cpadding=padding/boardScale + { + paddedBoundingBox.top-=cpadding; + paddedBoundingBox.right-=cpadding; + paddedBoundingBox.bottom+=cpadding; + paddedBoundingBox.left+=cpadding + } + console.log(cpadding) if (perspective.getEnabled()) padding = 0; for (const strline in slideArrows) { const line = math.getCoordsFromKey(strline) @@ -184,8 +192,9 @@ const arrows = (function() { const isLeft = side==="l" const corner = math.getAABBCornerOfLine(direction, isLeft) const renderCoords = math.getLineIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) + if (!renderCoords) continue; const arrowDirection = isLeft ? [-direction[0],-direction[1]] : direction - concatData(renderCoords, piece.type, corner, worldWidth, padding, piece.coords, arrowDirection, !isLeft) + concatData(renderCoords, piece.type, corner, worldWidth, 0, piece.coords, arrowDirection, !isLeft) } } } From d5e8580cc0259436fcd638c9c98df1f3a175b15e Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 21:17:27 +0100 Subject: [PATCH 31/58] cleanup + perspective highlightlines --- src/client/scripts/game/rendering/arrows.js | 2 +- src/client/scripts/game/rendering/highlightline.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index eb9939d3f..add30cb1e 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -176,7 +176,7 @@ const arrows = (function() { paddedBoundingBox.bottom+=cpadding; paddedBoundingBox.left+=cpadding } - console.log(cpadding) + if (perspective.getEnabled()) padding = 0; for (const strline in slideArrows) { const line = math.getCoordsFromKey(strline) diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index 7beeee07a..d1b1e485c 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -36,6 +36,7 @@ const highlightline = (function(){ const snapDist = miniimage.gwidthWorld() / 2; + const a = perspective.distToRenderBoard /** @type {BoundingBox} */ let boundingBox = perspective.getEnabled() ? { left: -a, right: a, bottom: -a, top: a } : camera.getScreenBoundingBox(false) From 65693b65ecd4221073385f5237e342a4e5e6bc8c Mon Sep 17 00:00:00 2001 From: Idonotus Date: Wed, 17 Jul 2024 21:57:54 +0100 Subject: [PATCH 32/58] more math fixes woth highlights --- src/client/scripts/game/misc/math.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index f93dfc103..af36b1ff4 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -374,13 +374,13 @@ const math = (function() { } } - function getLineSteps(step, origin, coord) { + function getLineSteps(step, origin, coord, isLeft) { let x = (coord[0]-origin[0])/step[0] - if (x>0) x = Math.floor(x) + if (!isLeft) x = Math.floor(x) else x = Math.ceil(x) if (step[0]!==0) return x; let y = Math.floor((coord[1]-origin[1])/step[1]) - if (x>0) x = Math.floor(x) + if (!isLeft) x = Math.floor(x) else x = Math.ceil(x) return y } From 8b61690b03b269b45fa9352096e0727dbe97f4c3 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:44:11 -0600 Subject: [PATCH 33/58] Some cleaning up, added `existingTypes` to the startSnapshot --- src/client/scripts/game/chess/gamefile.js | 7 ++-- src/client/scripts/game/chess/variant.js | 43 ++++++++++++++++------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/client/scripts/game/chess/gamefile.js b/src/client/scripts/game/chess/gamefile.js index a0bb56ca8..167d2a8a5 100644 --- a/src/client/scripts/game/chess/gamefile.js +++ b/src/client/scripts/game/chess/gamefile.js @@ -49,9 +49,10 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) /** The bounding box surrounding the starting position, without padding. * @type {BoundingBox} */ box: undefined, - /** Possible slide mopves*/ - slidingPossible: [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]], - + /** A set of all types of pieces that are in this game, without their color extension: `['pawns','queens']` */ + existingTypes: undefined, + /** Possible sliding moves in this game, dependant on what pieces there are. */ + slidingPossible: [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]] } this.gameRules = { diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index dc97d9b6d..331be6dd7 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -43,35 +43,54 @@ const variant = (function() { if (options) initStartSnapshotAndGamerulesFromOptions(gamefile, metadata, options) // Ignores the "Variant" metadata, and just uses the specified startingPosition else initStartSnapshotAndGamerules(gamefile, metadata) // Default (built-in variant, not pasted) + initExistingTypes(gamefile); initPieceMovesets(gamefile) - - initsliding(gamefile) + initSlidingMoves(gamefile) } /** - * + * Sets the `existingTypes` property of the `startSnapshot` of the gamefile, + * which contains all types of pieces in the game, without their color extension. * @param {gamefile} gamefile */ - function initsliding(gamefile) { - gamefile.startSnapshot.slidingPossible = getPossibleSlides(gamefile.pieceMovesets, gamefile.startSnapshot.position) - } - - function getPossibleSlides(movesets, position) { - const teamtypes = new Set(Object.values(position)); // Make a set of all pieces in game + function initExistingTypes(gamefile) { + const teamtypes = new Set(Object.values(gamefile.startSnapshot.position)); // Make a set of all pieces in game const rawtypes = new Set(); for (const tpiece of teamtypes) { rawtypes.add(math.trimWorBFromType(tpiece)); // Make a set wit the team colour trimmed } - const slides = new Set(['1,0']); // Always on for castling, If castling can be disabled, change this? + gamefile.startSnapshot.existingTypes = rawtypes; + } + + /** + * Inits the `slidingMoves` property of the `startSnapshot` of the gamefile. + * This contains the information of what slides are possible, according to + * what piece types are in this game. + * @param {gamefile} gamefile + */ + function initSlidingMoves(gamefile) { + gamefile.startSnapshot.slidingPossible = getPossibleSlides(gamefile) + } + + /** + * Calculates all possible slides that should be possible in the provided game, + * excluding pieces that aren't in the provided position. + * @param {gamefile} gamefile + */ + function getPossibleSlides(gamefile) { + const rawtypes = gamefile.startSnapshot.existingTypes; + const movesets = gamefile.pieceMovesets; + // '1,0' is required if castling is enabled. The other default ones are so that arrows mode 'all' has atleast use to you. + const slides = new Set(['1,0','0,1','1,1','1,-1']); for (const type of rawtypes) { let moveset = movesets[type]; if (!moveset) continue; moveset = moveset(); if (!moveset.sliding) continue; - Object.keys(moveset.sliding).forEach(slide => {slides.add(slide)}); + Object.keys(moveset.sliding).forEach( slide => { slides.add(slide) }); } let temp = []; - slides.forEach(slideline => {temp.push(math.getCoordsFromKey(slideline))}) + slides.forEach(slideline => { temp.push(math.getCoordsFromKey(slideline)) }) return temp; } From d2e6e9a6cdd1f0425148d5555a2fb114ccf2fabc Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:46:42 -0600 Subject: [PATCH 34/58] I presume this is now auto filled out --- src/client/scripts/game/chess/gamefile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/game/chess/gamefile.js b/src/client/scripts/game/chess/gamefile.js index 167d2a8a5..7005b6ee4 100644 --- a/src/client/scripts/game/chess/gamefile.js +++ b/src/client/scripts/game/chess/gamefile.js @@ -52,7 +52,7 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) /** A set of all types of pieces that are in this game, without their color extension: `['pawns','queens']` */ existingTypes: undefined, /** Possible sliding moves in this game, dependant on what pieces there are. */ - slidingPossible: [[1,1],[1,-1],[1,0],[0,1],[1,2],[1,-2],[2,1],[2,-1]] + slidingPossible: undefined } this.gameRules = { From 960cf4d28c17a842dbf646d188feb88dadc61d7b Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Wed, 17 Jul 2024 22:53:26 -0600 Subject: [PATCH 35/58] Ups ya forgot the archbishop slides --- src/client/scripts/game/chess/movesets.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/scripts/game/chess/movesets.js b/src/client/scripts/game/chess/movesets.js index ea9798b72..ceb8e04d5 100644 --- a/src/client/scripts/game/chess/movesets.js +++ b/src/client/scripts/game/chess/movesets.js @@ -62,8 +62,8 @@ const movesets = (function() { return { individual: [], sliding: { - '1,0': [-slideLimit, slideLimit], - '0,1': [-slideLimit, slideLimit] + '1,0': [-slideLimit, slideLimit], + '0,1': [-slideLimit, slideLimit] } } }, @@ -116,8 +116,10 @@ const movesets = (function() { [-2,1],[-1,2],[1,2],[2,1], [-2,-1],[-1,-2],[1,-2],[2,-1] ], - '1,1': [-slideLimit, slideLimit], - '1,-1': [-slideLimit, slideLimit] + sliding: { + '1,1': [-slideLimit, slideLimit], + '1,-1': [-slideLimit, slideLimit] + } } }, amazons: function () { From c9c09c94465f689ad0775212b1569fc1ba0e5d5f Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:02:56 -0600 Subject: [PATCH 36/58] Lines now have the correct key! Fixed checkdetection.js after I broke everything lol --- .../scripts/game/chess/checkdetection.js | 211 ++++++++++-------- src/client/scripts/game/chess/legalmoves.js | 21 +- src/client/scripts/game/chess/selection.js | 2 +- src/client/scripts/game/chess/variant.js | 3 +- src/client/scripts/game/misc/math.js | 104 +++++++-- src/client/scripts/game/rendering/arrows.js | 4 +- .../scripts/game/rendering/highlightline.js | 4 +- .../scripts/game/rendering/highlights.js | 4 +- 8 files changed, 230 insertions(+), 123 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 7e08ec418..46cd16205 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -44,18 +44,20 @@ const checkdetection = (function(){ if (!coord) return false; if (colorOfFriendly !== 'white' && colorOfFriendly !== 'black') throw new Error(`Cannot detect if an opponent is attacking the square of the team of color ${colorOfFriendly}!`) + let atleast1Attacker = false; + // How do we find out if this square is attacked? // 1. We check every square within a 3 block radius to see if there's any attacking pieces. - if (doesVicinityAttackSquare(gamefile, coord, colorOfFriendly, attackers)) return true; + if (doesVicinityAttackSquare(gamefile, coord, colorOfFriendly, attackers)) atleast1Attacker = true; // What about pawns? Could they capture us? - if (doesPawnAttackSquare(gamefile, coord, colorOfFriendly, attackers)) return true; + if (doesPawnAttackSquare(gamefile, coord, colorOfFriendly, attackers)) atleast1Attacker = true; // 2. We check every orthogonal and diagonal to see if there's any attacking pieces. - if (doesSlideAttackSquare(gamefile, coord, colorOfFriendly, attackers)) return true; + if (doesSlideAttackSquare(gamefile, coord, colorOfFriendly, attackers)) atleast1Attacker = true; - return false; // Being attacked if true + return atleast1Attacker; // Being attacked if true } // Checks to see if any piece within a 3-block radius can capture. Ignores sliding movesets. @@ -113,22 +115,17 @@ const checkdetection = (function(){ } // Returns true if there's any sliding piece that can capture on that square - /** - * - * @param {gamefile} gamefile - * @param {number[][]} coords - * @param {string} color - * @param {Array} attackers - * @returns - */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { + + let atleast1Attacker = false; + for (const line of gamefile.startSnapshot.slidingPossible) { const strline = math.getKeyFromCoords(line) const key = math.getKeyFromLine(line, coords) - if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) return true; + if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) atleast1Attacker = true; } - return false; + return atleast1Attacker; } // Returns true if a piece on the specified line can capture on that square @@ -136,7 +133,9 @@ const checkdetection = (function(){ function doesLineAttackSquare(gamefile, line, direction, coords, colorOfFriendlys, attackers) { if (!line) return false; // No line, no pieces to attack - const lineIsVertical = direction[0]==0 + + let foundCheckersCount = 0; + for (let a = 0; a < line.length; a++) { // { coords, type } const thisPiece = line[a]; @@ -146,24 +145,26 @@ const checkdetection = (function(){ const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) - if (!thisPieceMoveset.sliding) {continue}; + if (!thisPieceMoveset.sliding) continue; // Piece has no sliding movesets. const moveset = thisPieceMoveset.sliding[math.getKeyFromCoords(direction)]; - if (!moveset) {continue}; + if (!moveset) continue; // Piece can't slide in the direction we're looking in const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction, moveset, thisPiece.coords, thisPieceColor) - if (!thisPieceLegalSlide) continue; // This piece has no horizontal moveset, NEXT piece on this line! + if (!thisPieceLegalSlide) continue; // This piece can't move in the direction of this line, NEXT piece! // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} // const isWithinMoveset = math.boxContainsSquare(rectangle, coords) - const isWithinMoveset = legalmoves.doesslidingetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords) + const isWithinMoveset = legalmoves.doesSlidingNetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords) - if (isWithinMoveset) { - if (attackers) appendAttackerToList(attackers, { coords: thisPiece.coords, slidingCheck: true }) - return true; // There'll never be more than 1 checker on the same line - // There are 2 sides? s<-k->s - } + if (!isWithinMoveset) continue; // This piece can't slide so far as to reach us, NEXT piece! + + // This piece is checking us!! + + foundCheckersCount++; + appendAttackerToList(attackers, { coords: thisPiece.coords, slidingCheck: true }) + if (foundCheckersCount >= 2) return true; // There'll never be more than 2 checkers on the same line (until witches become a thing) } - return false; + return foundCheckersCount > 0; } /** @@ -223,7 +224,7 @@ const checkdetection = (function(){ // Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { - if (math.isEmpty(moves.sliding)) return; + if (!moves.sliding) return; // No sliding moves to remove const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces @@ -287,85 +288,100 @@ const checkdetection = (function(){ return true; function eraseAllSlidingMoves() { - legalMoves.sliding = {} + delete legalMoves.sliding; } } + /** + * + * @param {*} gamefile + * @param {LegalMoves} moves + * @param {*} kingCoords + * @param {*} pieceSelected + * @param {*} color + * @returns + */ function removeSlidingMovesThatOpenDiscovered (gamefile, moves, kingCoords, pieceSelected, color) { - + const selectedPieceCoords = pieceSelected.coords; let sameLines = []; for (const line of gamefile.startSnapshot.slidingPossible) { // Only check current possible slides if (math.getKeyFromLine(line, kingCoords) !== math.getKeyFromLine(line, selectedPieceCoords)) continue; - sameLines.push(line); + sameLines.push(line); // The piece is on the same line as the king! }; // If not sharing any common line, there's no way we can open a discovered - if (sameLines.length===0) return; + if (sameLines.length === 0) return; // Delete the piece, and add it back when we're done! const deletedPiece = math.deepCopyObject(pieceSelected); movepiece.deletePiece(gamefile, pieceSelected, { updateData: false }); - let checklines = []; + // let checklines = []; for (const line of sameLines) { const strline = math.getKeyFromCoords(line); const key = math.getKeyFromLine(line,kingCoords); - const opensDiscovered = doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], 'horizontal', kingCoords, color, []) + const opensDiscovered = doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, kingCoords, color, []) if (!opensDiscovered) continue; - checklines.push(line); - } - const tempslides = {} - r : { - if (checklines.length > 1) { - if (math.areLinesCollinear(checklines)) { - // FIXME: this is a problem as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check - // Another case (3,0) (2,0) correct blocks are along (6,0) but thats not an organized line - - // Please can someone optimize this - - let fline = checklines[0]; - let fGcd = math.GCD(fline[0],fline[1]); - - const baseLine = [fline[0]/fGcd, fline[1]/fGcd]; - - let mult = []; - checklines.forEach((line) => {mult.push(math.GCD(line[0],line[1]))}); - const lcm = math.LCM(Object.values(mult)); - - const steps = [0,0] - for (const strline in moves.sliding) { - const line = math.getCoordsFromKey(strline); - if (!math.areLinesCollinear([line, baseLine])) continue; - const gcd = math.GCD(line[0], line[1]); - let rslides = [Math.floor(moves.sliding[strline][0]/lcm*gcd),Math.floor(moves.sliding[strline][1]/lcm*gcd)]; - if (rslides[0]steps[1]) steps[1] = rslides[1]; - } - - const line = [baseLine[0]*lcm,baseLine[1]*lcm] - - if (!gamefile.startSnapshot.slidingPossible.includes(line)) { - const strline = math.getKeyFromCoords(line) - tempslides[strline] = steps - } else { - for (i=steps[0]; i<=steps[1]; i++) { - if (i==0) continue; - moves.individual.push([line[0]*i,line[1]*i]) - } - } - - } else { - // Cannot slide to block all attack lines so blank the sliding - // Could probably blank regular attacks too - } - } else if (checklines.length === 1) { - const strline = math.getKeyFromCoords(checklines[0]) - if (!moves.sliding[strline]) break r; - tempslides[strline] = moves.sliding[strline] + // checklines.push(line); + // Delete all lines except this one (because if we move off of it we would be in check!) + for (const direction of moves) { // [2,1] + if (math.areCoordsEqual(direction, line)) continue; // Same line + const key = math.getKeyFromCoords(line); + delete moves.sliding[key] } } + // const tempslides = {} + // r : { + // if (checklines.length > 1) { + // if (math.areLinesCollinear(checklines)) { + // // FIXME: this is a problem as (2,0) (1,0) if (1,0) is added it can slide into (2,0) gaps opening check + // // Another case (3,0) (2,0) correct blocks are along (6,0) but thats not an organized line + + // // Please can someone optimize this + + // let fline = checklines[0]; + // let fGcd = math.GCD(fline[0],fline[1]); + + // const baseLine = [fline[0]/fGcd, fline[1]/fGcd]; + + // let mult = []; + // checklines.forEach((line) => {mult.push(math.GCD(line[0],line[1]))}); + // const lcm = math.LCM(Object.values(mult)); + + // const steps = [0,0] + // for (const strline in moves.sliding) { + // const line = math.getCoordsFromKey(strline); + // if (!math.areLinesCollinear([line, baseLine])) continue; + // const gcd = math.GCD(line[0], line[1]); + // let rslides = [Math.floor(moves.sliding[strline][0]/lcm*gcd),Math.floor(moves.sliding[strline][1]/lcm*gcd)]; + // if (rslides[0]steps[1]) steps[1] = rslides[1]; + // } + + // const line = [baseLine[0]*lcm,baseLine[1]*lcm] + + // if (!gamefile.startSnapshot.slidingPossible.includes(line)) { + // const strline = math.getKeyFromCoords(line) + // tempslides[strline] = steps + // } else { + // for (i=steps[0]; i<=steps[1]; i++) { + // if (i==0) continue; + // moves.individual.push([line[0]*i,line[1]*i]) + // } + // } + + // } else { + // // Cannot slide to block all attack lines so blank the sliding + // // Could probably blank regular attacks too + // } + // } else if (checklines.length === 1) { + // const strline = math.getKeyFromCoords(checklines[0]) + // if (!moves.sliding[strline]) break r; + // tempslides[strline] = moves.sliding[strline] + // } + // } // Add the piece back with the EXACT SAME index it had before!! movepiece.addPiece(gamefile, deletedPiece.type, deletedPiece.coords, deletedPiece.index, { updateData: false }) @@ -373,30 +389,39 @@ const checkdetection = (function(){ // Appends moves to moves.individual if the selected pieces is able to get between squares 1 & 2 function appendBlockingMoves (square1, square2, moves, coords) { // coords is of the selected piece - // What is the line between our king and the attacking piece? let direction = [square1[0] - square2[0], square1[1] - square2[1]]; + /** @type {BoundingBox} */ const box = { left: Math.min(square1[0],square2[0]), right: Math.max(square1[0],square2[0]), - top: Math.min(square1[1],square2[1]), - bottom: Math.max(square1[1],square2[1]) + top: Math.max(square1[1],square2[1]), + bottom: Math.min(square1[1],square2[1]) } - function appendBlockPointIfLegal (blockPoint,line) { - if (!math.isAproxEqual(blockPoint[0],Math.round(blockPoint[0])) || - !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) {console.log("A"); return}; // Block is off grid so probably not valid - blockPoint=[Math.round(blockPoint[0]), Math.round(blockPoint[1])] - if (math.getKeyFromLine(line,blockPoint)!==math.getKeyFromLine(line, coords)) {console.log("C"); return}; // stop line multiples being annoying + function appendBlockPointIfLegal (blockPoint) { + // Idon us's old code + // if (!math.isAproxEqual(blockPoint[0],Math.round(blockPoint[0])) || + // !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) {console.log("A"); return}; // Block is off grid so probably not valid + // blockPoint=[Math.round(blockPoint[0]), Math.round(blockPoint[1])] + // if (math.getKeyFromLine(line,blockPoint)!==math.getKeyFromLine(line, coords)) {console.log("C"); return}; // stop line multiples being annoying + + // Naviary's new code + if (blockPoint === null) return; // None (or infinite) intersection points! + if (!math.boxContainsSquare(box, blockPoint)) return; // Intersection point not between our 2 points, but outside of them. + if (!math.areCoordsIntegers(blockPoint)) return; // It doesn't intersect at a whole number, impossible for our piece to move here! + if (math.areCoordsEqual(blockPoint, square1)) return; // Can't move onto our piece that's in check.. + if (math.areCoordsEqual(blockPoint, square2)) return; // nor to the piece that is checking us (those are added prior to this if it's legal)! + // Can our piece legally move there? if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! - } + } for (const linestr in moves.sliding) { const line = math.getCoordsFromKey(linestr) - const c1 = math.getLineFromCoords(line, coords) - const c2 = math.getLineFromCoords(direction,square2) + const c1 = math.getCFromLineInGeneralForm(line, coords); // Line of our selected piece + const c2 = math.getCFromLineInGeneralForm(direction,square2) // Line between our 2 squares const blockPoint = math.getLineIntersection(line[0], line[1], c1, direction[0], direction[1], c2) appendBlockPointIfLegal(blockPoint, line) } diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index a3f8f9fa0..c8658df49 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -158,11 +158,19 @@ const legalmoves = (function(){ return individualMoves; } - // Takes in specified organized list, direction of the slide, the current moveset... - // Shortens the moveset by pieces that block it's path. + /** + * Takes in specified organized list, direction of the slide, the current moveset... + * Shortens the moveset by pieces that block it's path. + * @param {*} organizedLine + * @param {*} line + * @param {*} slidinget + * @param {*} coords + * @param {*} color + * @returns + */ function slide_CalcLegalLimit (organizedLine, line, slidinget, coords, color) { - if (!slidinget) return // Return undefined if there is no slide moveset + if (!slidinget) return; // Return undefined if there is no slide moveset // The default slide is [-Infinity, Infinity], change that if there are any pieces blocking our path! @@ -234,7 +242,7 @@ const legalmoves = (function(){ let clickedCoordsLine = math.getKeyFromLine(line,endCoords); if (!limits||selectedPieceLine!=clickedCoordsLine) continue; - if (!doesslidingetContainSquare(limits, line, startCoords, endCoords)) continue; + if (!doesSlidingNetContainSquare(limits, line, startCoords, endCoords)) continue; return true; } return false; @@ -340,7 +348,7 @@ const legalmoves = (function(){ // TODO: moveset changes // This requires coords be on the same line as the sliding moveset. - function doesslidingetContainSquare (slidinget, line, pieceCoords, coords) { + function doesSlidingNetContainSquare(slidinget, line, pieceCoords, coords) { const axis = line[0]===0 ? 1 : 0 const coordMag = coords[axis]; @@ -352,6 +360,7 @@ const legalmoves = (function(){ return true; } + /** * Accepts the calculated legal moves, tests to see if there are any * @param {LegalMoves} moves @@ -376,7 +385,7 @@ const legalmoves = (function(){ getPieceMoveset, calculate, checkIfMoveLegal, - doesslidingetContainSquare, + doesSlidingNetContainSquare, hasAtleast1Move, slide_CalcLegalLimit, isOpponentsMoveLegal diff --git a/src/client/scripts/game/chess/selection.js b/src/client/scripts/game/chess/selection.js index 06be17410..e5353c145 100644 --- a/src/client/scripts/game/chess/selection.js +++ b/src/client/scripts/game/chess/selection.js @@ -38,7 +38,7 @@ const selection = (function() { /** * Returns the pre-calculated legal moves of the selected piece. - * @returns {LegalMoves} The selected piece, if there is one: `{ type, index, coords }`. + * @returns {LegalMoves} */ function getLegalMovesOfSelectedPiece() { return legalMoves; } diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index 331be6dd7..31bef1ef1 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -80,8 +80,7 @@ const variant = (function() { function getPossibleSlides(gamefile) { const rawtypes = gamefile.startSnapshot.existingTypes; const movesets = gamefile.pieceMovesets; - // '1,0' is required if castling is enabled. The other default ones are so that arrows mode 'all' has atleast use to you. - const slides = new Set(['1,0','0,1','1,1','1,-1']); + const slides = new Set(['1,0']); // '1,0' is required if castling is enabled. for (const type of rawtypes) { let moveset = movesets[type]; if (!moveset) continue; diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index af36b1ff4..8e70a68a1 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -34,13 +34,34 @@ const math = (function() { return Math.abs(a-b) box.right) return false; if (square[1] < box.bottom) return false; @@ -167,14 +187,31 @@ const math = (function() { * c=b*y-intercept so is unique for each line * Not unique when step can be factored * eg [2,2] - * @param {number[]} step `[deltax, deltay]` - * @param {number[]} coords `[x,y]` + * @param {number[]} step - The x-step and y-step of the line: `[deltax, deltay]` + * @param {number[]} coords - A point the line intersects: `[x,y]` * @returns {number} integer c */ - function getLineFromCoords(step, coords) { + function getCFromLineInGeneralForm(step, coords) { + // Idon us's old equation return step[0]*coords[1]-step[1]*coords[0] } + /** + * Returns the y interscept of the line with step dx and dy that intersects the coordinates. + * If the line is vertical, this returns the x intercept instead. + * @param {*} step + * @param {*} coords + * @returns {number} + */ + function getYIntceptOfLine(step, coords) { + // Naviary's new equation + const lineIsVertical = step[0] === 0; + const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; + const slope = lineIsVertical ? step[0] / step[1] : step[1] / step[0]; + // console.log(step, coords, lineIsVertical ? coords[0] + slope * (xLine - coords[1]) : coords[1] + slope * (xLine - coords[0])) + return lineIsVertical ? coords[0] + slope * (xLine - coords[1]) : coords[1] + slope * (xLine - coords[0]); + } + /** * Gets a unique key from the line equation. * Compatable with factorable steps like `[2,2]`. @@ -183,11 +220,44 @@ const math = (function() { * @returns {String} the key `id|intercept` */ function getKeyFromLine(step, coords) { - const lineIsVertical = step[0]===0; - const deltaAxis = lineIsVertical ? step[1] : step[0]; - const coordAxis = lineIsVertical ? coords[1] : coords[0]; - return `${getLineFromCoords(step,coords)}|${coordAxis - (Math.floor(coordAxis / deltaAxis) * deltaAxis)}` + // See these desmos graphs for inspiration for finding what line the coords are on: + // https://www.desmos.com/calculator/d0uf1sqipn + // https://www.desmos.com/calculator/t9wkt3kbfo + + // Idon us's old equation + // const lineIsVertical = step[0] === 0; + // const deltaAxis = lineIsVertical ? step[0] : step[1]; + // const coordAxis = lineIsVertical ? coords[0] : coords[1]; + // return `${getCFromLineInGeneralForm(step,coords)}|${coordAxis - (Math.floor(coordAxis / deltaAxis) * deltaAxis)}` + + // Naviary's new equation + const lineIsVertical = step[0] === 0; + const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; + const yIntcept = getYIntceptOfLine(step, coords); + return `${yIntcept}|${xLine}`; } + + /** + * Checks if both the x-coordinate and the y-coordinate of a point are integers. + * @param {number} x - The x-coordinate of the point. + * @param {number} y - The y-coordinate of the point. + * @returns {boolean} - Returns true if both coordinates are integers, otherwise false. + */ + function areCoordsIntegers(coords) { + return Number.isInteger(coords[0]) && Number.isInteger(coords[1]); + } + + // /** + // * ALTERNATIVE to {@link areCoordsIntegers}, if we end up having floating point imprecision problems! + // * + // * Checks if a number is effectively an integer considering floating point imprecision. + // * @param {number} num - The number to check. + // * @param {number} [epsilon=Number.EPSILON] - The tolerance for floating point imprecision. + // * @returns {boolean} - Returns true if the number is effectively an integer, otherwise false. + // */ + // function isEffectivelyInteger(num, epsilon = Number.EPSILON) { + // return Math.abs(num - Math.round(num)) < epsilon; + // } /** * Checks if all lines are colinear aka `[[1,0],[2,0]]` would be as they are both the same direction @@ -961,8 +1031,10 @@ const math = (function() { roundPointToNearestGridpoint, boxContainsBox, boxContainsSquare, - getLineFromCoords, + getCFromLineInGeneralForm, + getYIntceptOfLine, getKeyFromLine, + areCoordsIntegers, areLinesCollinear, deepCopyObject, getKeyFromCoords, diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index add30cb1e..f57fc2e07 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -107,8 +107,8 @@ const arrows = (function() { boardCornerLeft = math.getCornerOfBoundingBox(paddedBoundingBox,boardCornerLeft); boardCornerRight = math.getCornerOfBoundingBox(paddedBoundingBox,boardCornerRight); - const boardSlidesRight = math.getLineFromCoords(line, boardCornerLeft); - const boardSlidesLeft = math.getLineFromCoords(line, boardCornerRight); + const boardSlidesRight = math.getCFromLineInGeneralForm(line, boardCornerLeft); + const boardSlidesLeft = math.getCFromLineInGeneralForm(line, boardCornerRight); const boardSlidesStart = Math.min(boardSlidesLeft, boardSlidesRight); const boardSlidesEnd = Math.max(boardSlidesLeft, boardSlidesRight); diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index d1b1e485c..8b35f8694 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -46,7 +46,7 @@ const highlightline = (function(){ let closestPoint; for (var strline in legalmoves.sliding) { const line = math.getCoordsFromKey(strline); - const diag = math.getLineFromCoords(line, worldSpaceCoords); + const diag = math.getCFromLineInGeneralForm(line, worldSpaceCoords); const lineIsVertical = line[0]===0 const corner1 = math.getAABBCornerOfLine(line, true); @@ -123,7 +123,7 @@ const highlightline = (function(){ boundingBox = perspective.getEnabled() ? math.generatePerspectiveBoundingBox(perspectiveLimitToTeleport) : board.gboundingBox(); const line = closestPoint.line - const diag = math.getLineFromCoords(line, pieceCoords) + const diag = math.getCFromLineInGeneralForm(line, pieceCoords) const lineIsVertical = line[0]===0 const corner1 = math.getAABBCornerOfLine(line, true); diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 239ed2ee7..b516cef55 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -215,6 +215,8 @@ const highlights = (function(){ // We COULD minimize how often we regenerate the buffer model by extending these lines beyond our field of view. // BUT currently we're regenerating every frame so let's just render to screen edge! + if (!selection.getLegalMovesOfSelectedPiece().sliding) return; // No sliding moves + // First we need to calculate the data of the horizontal slide concatData_HighlightedMoves_Sliding_Horz(coords, boundingBoxOfRenderRange.left, boundingBoxOfRenderRange.right) @@ -293,7 +295,7 @@ const highlights = (function(){ lineSet.delete('0,1') for (const strline of lineSet) { const line = math.getCoordsFromKey(strline); - const lineEqua = math.getLineFromCoords(line, coords); + const lineEqua = math.getCFromLineInGeneralForm(line, coords); const corner1 = math.getAABBCornerOfLine(line, true); const corner2 = math.getAABBCornerOfLine(line, false); From 103fe10c5c22a0b99ce65e344b40ffaf790f7196 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:15:29 -0600 Subject: [PATCH 37/58] Comment changes --- src/client/scripts/game/misc/math.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 8e70a68a1..626dec7b5 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -192,7 +192,6 @@ const math = (function() { * @returns {number} integer c */ function getCFromLineInGeneralForm(step, coords) { - // Idon us's old equation return step[0]*coords[1]-step[1]*coords[0] } @@ -204,7 +203,6 @@ const math = (function() { * @returns {number} */ function getYIntceptOfLine(step, coords) { - // Naviary's new equation const lineIsVertical = step[0] === 0; const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; const slope = lineIsVertical ? step[0] / step[1] : step[1] / step[0]; @@ -217,7 +215,7 @@ const math = (function() { * Compatable with factorable steps like `[2,2]`. * @param {Number[]} step Line step `[deltax,deltay]` * @param {Number[]} coords `[x,y]` - * @returns {String} the key `id|intercept` + * @returns {String} the key `yIntcept|smallest_x_line_intcepts` */ function getKeyFromLine(step, coords) { // See these desmos graphs for inspiration for finding what line the coords are on: From a5cb0de22aaade605e2c59addee8103c84c7cfb4 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:35:48 -0600 Subject: [PATCH 38/58] Added whitespace --- src/client/scripts/game/chess/legalmoves.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index c8658df49..f2c7cd30f 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -235,12 +235,12 @@ const legalmoves = (function(){ } for (var strline in legalMoves.sliding) { - let line=math.getCoordsFromKey(strline); + let line = math.getCoordsFromKey(strline); let limits = legalMoves.sliding[strline]; let selectedPieceLine = math.getKeyFromLine(line,startCoords); let clickedCoordsLine = math.getKeyFromLine(line,endCoords); - if (!limits||selectedPieceLine!=clickedCoordsLine) continue; + if (!limits || selectedPieceLine !== clickedCoordsLine) continue; if (!doesSlidingNetContainSquare(limits, line, startCoords, endCoords)) continue; return true; @@ -350,7 +350,7 @@ const legalmoves = (function(){ // This requires coords be on the same line as the sliding moveset. function doesSlidingNetContainSquare(slidinget, line, pieceCoords, coords) { - const axis = line[0]===0 ? 1 : 0 + const axis = line[0] === 0 ? 1 : 0 const coordMag = coords[axis]; const min = slidinget[0] * line[axis] + pieceCoords[axis] const max = slidinget[1] * line[axis] + pieceCoords[axis] From 6a69470c19bbad453c6c5224e85febd106e0aabd Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:02:45 -0600 Subject: [PATCH 39/58] Fixed discovered checks --- src/client/scripts/game/chess/checkdetection.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 46cd16205..59d08f0ad 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -326,11 +326,12 @@ const checkdetection = (function(){ if (!opensDiscovered) continue; // checklines.push(line); // Delete all lines except this one (because if we move off of it we would be in check!) - for (const direction of moves) { // [2,1] - if (math.areCoordsEqual(direction, line)) continue; // Same line - const key = math.getKeyFromCoords(line); - delete moves.sliding[key] + for (const direction of Object.keys(moves.sliding)) { // 'dx,dy' + const directionNumbArray = direction.split(',').map(Number); // [dx,dy] + if (math.areCoordsEqual(directionNumbArray, line)) continue; // Same line + delete moves.sliding[direction]; } + } // const tempslides = {} // r : { From 4bb1247a974552237b19f3dbad70a675595bb876 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:06:47 -0600 Subject: [PATCH 40/58] Use math func instead --- src/client/scripts/game/chess/checkdetection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 59d08f0ad..5a1912140 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -327,7 +327,7 @@ const checkdetection = (function(){ // checklines.push(line); // Delete all lines except this one (because if we move off of it we would be in check!) for (const direction of Object.keys(moves.sliding)) { // 'dx,dy' - const directionNumbArray = direction.split(',').map(Number); // [dx,dy] + const directionNumbArray = math.getCoordsFromKey(direction) // [dx,dy] if (math.areCoordsEqual(directionNumbArray, line)) continue; // Same line delete moves.sliding[direction]; } From d1c9c1ea3649aeb226f0d93e1e282ff0b90c6d13 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Thu, 18 Jul 2024 19:16:35 +0100 Subject: [PATCH 41/58] fix negative values generating new keys --- src/client/scripts/game/misc/math.js | 31 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 626dec7b5..9341afd68 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -182,6 +182,10 @@ const math = (function() { return { left, right, bottom, top } } + function posMod(a,b) { + return a - (Math.floor(a / b) * b) + } + /** * Uses the calculation of ax + by = c * c=b*y-intercept so is unique for each line @@ -204,8 +208,8 @@ const math = (function() { */ function getYIntceptOfLine(step, coords) { const lineIsVertical = step[0] === 0; - const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; - const slope = lineIsVertical ? step[0] / step[1] : step[1] / step[0]; + const xLine = lineIsVertical ? posMod(coords[1], step[1]) : posMod(coords[0], step[0]); + const slope = lineIsVertical ? 0 /**step[0] / step[1]*/ : step[1] / step[0]; // console.log(step, coords, lineIsVertical ? coords[0] + slope * (xLine - coords[1]) : coords[1] + slope * (xLine - coords[0])) return lineIsVertical ? coords[0] + slope * (xLine - coords[1]) : coords[1] + slope * (xLine - coords[0]); } @@ -223,14 +227,15 @@ const math = (function() { // https://www.desmos.com/calculator/t9wkt3kbfo // Idon us's old equation - // const lineIsVertical = step[0] === 0; - // const deltaAxis = lineIsVertical ? step[0] : step[1]; - // const coordAxis = lineIsVertical ? coords[0] : coords[1]; - // return `${getCFromLineInGeneralForm(step,coords)}|${coordAxis - (Math.floor(coordAxis / deltaAxis) * deltaAxis)}` + const lineIsVertical = step[0] === 0; + const deltaAxis = lineIsVertical ? step[1] : step[0]; + const coordAxis = lineIsVertical ? coords[1] : coords[0]; + const xLine = posMod(coordAxis, deltaAxis) + //return `${getCFromLineInGeneralForm(step,coords)}|${xLine}` // Naviary's new equation - const lineIsVertical = step[0] === 0; - const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; + //const lineIsVertical = step[0] === 0; + //const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; const yIntcept = getYIntceptOfLine(step, coords); return `${yIntcept}|${xLine}`; } @@ -265,9 +270,12 @@ const math = (function() { function areLinesCollinear(lines) { let gradient - for (line of lines) { - if (!gradient) gradient = line[0]/line[1] - if (!isAproxEqual(line[0]/line[1], gradient)) return false; + for (const line of lines) { + console.log(line) + const lgradient = line[1]/line[0] + if (!gradient) gradient = lgradient + if (!Number.isFinite(gradient)&&!Number.isFinite(lgradient)) {console.log(lgradient,gradient);continue}; + if (!isAproxEqual(lgradient, gradient)) return false; } return true } @@ -1029,6 +1037,7 @@ const math = (function() { roundPointToNearestGridpoint, boxContainsBox, boxContainsSquare, + posMod, getCFromLineInGeneralForm, getYIntceptOfLine, getKeyFromLine, From e97d756865bbf5aa008a269d88931d88f89e4f8a Mon Sep 17 00:00:00 2001 From: Idonotus Date: Thu, 18 Jul 2024 20:58:06 +0100 Subject: [PATCH 42/58] key format changes --- src/client/scripts/game/misc/math.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 9341afd68..08573d03a 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -217,9 +217,10 @@ const math = (function() { /** * Gets a unique key from the line equation. * Compatable with factorable steps like `[2,2]`. + * Discuss before changing func please as this may have unintended side-effects. * @param {Number[]} step Line step `[deltax,deltay]` * @param {Number[]} coords `[x,y]` - * @returns {String} the key `yIntcept|smallest_x_line_intcepts` + * @returns {String} the key `c|smallest_x_line_intcepts` */ function getKeyFromLine(step, coords) { // See these desmos graphs for inspiration for finding what line the coords are on: @@ -231,13 +232,13 @@ const math = (function() { const deltaAxis = lineIsVertical ? step[1] : step[0]; const coordAxis = lineIsVertical ? coords[1] : coords[0]; const xLine = posMod(coordAxis, deltaAxis) - //return `${getCFromLineInGeneralForm(step,coords)}|${xLine}` + return `${getCFromLineInGeneralForm(step,coords)}|${xLine}` // Naviary's new equation //const lineIsVertical = step[0] === 0; //const xLine = lineIsVertical ? coords[1] % step[1] : coords[0] % step[0]; - const yIntcept = getYIntceptOfLine(step, coords); - return `${yIntcept}|${xLine}`; + //const yIntcept = getYIntceptOfLine(step, coords); + //return `${yIntcept}|${xLine}`; } /** From eb3448bab3a28309cc12f0d1bac55d56ad78c524 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:04:21 -0600 Subject: [PATCH 43/58] Cleaned up a lot in checkdetection.js. Added a lot of JSDoc. Moved some funcs to organizedlines.js --- .../scripts/game/chess/checkdetection.js | 192 +++++++++++------- src/client/scripts/game/chess/gamefile.js | 2 +- src/client/scripts/game/chess/legalmoves.js | 59 +++--- .../scripts/game/chess/organizedlines.js | 49 ++++- .../scripts/game/chess/specialdetect.js | 2 +- src/client/scripts/game/misc/math.js | 132 +++++------- src/client/scripts/game/rendering/arrows.js | 4 +- .../scripts/game/rendering/highlightline.js | 4 +- .../scripts/game/rendering/highlights.js | 2 +- 9 files changed, 248 insertions(+), 198 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 5a1912140..0b4d4f787 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -114,54 +114,68 @@ const checkdetection = (function(){ return false; } - // Returns true if there's any sliding piece that can capture on that square + /** + * Calculates if any sliding piece can attack the specified square. + * Appends attackers to the provided `attackers` array. + * @param {gamefile} gamefile + * @param {number[]} coords - The square to test if it can be attacked + * @param {string} color - The color of friendly pieces + * @param {Object[]} attackers - A running list of attackers on this square. Any new found attackers will be appended to this this. + * @returns {boolean} true if this square is under attack + */ function doesSlideAttackSquare (gamefile, coords, color, attackers) { let atleast1Attacker = false; - for (const line of gamefile.startSnapshot.slidingPossible) { - const strline = math.getKeyFromCoords(line) - const key = math.getKeyFromLine(line, coords) - if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, coords, color, attackers)) atleast1Attacker = true; + for (const direction of gamefile.startSnapshot.slidingPossible) { // [dx,dy] + const directionKey = math.getKeyFromCoords(direction) + const key = organizedlines.getKeyFromLine(direction, coords) + if (doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[directionKey][key], direction, coords, color, attackers)) atleast1Attacker = true; } return atleast1Attacker; } - // Returns true if a piece on the specified line can capture on that square - // THIS REQUIRES coords be already on the line. - function doesLineAttackSquare(gamefile, line, direction, coords, colorOfFriendlys, attackers) { - - if (!line) return false; // No line, no pieces to attack + /** + * Returns true if a piece on the specified line can capture on that square. + * THIS REQUIRES `coords` be already on the line!!! + * Appends any attackeres to the provided `attackers` array. + * @param {gamefile} gamefile + * @param {Piece[]} line - The line of pieces + * @param {number[]} direction - Step of the line: [dx,dy] + * @param {number} coords - The coordinates of the square to test if any piece on the line can move to. + * @param {string} color - The color of friendlies. We will exclude pieces of the same color, because they cannot capture friendlies. + * @param {Object[]} [attackers] - The running list of attackers threatening these coordinates. Any attackers found will be appended to this list. LEAVE BLANK to save compute not adding them to this list! + * @returns {boolean} true if the square is under threat + */ + function doesLineAttackSquare(gamefile, line, direction, coords, color, attackers) { + if (!line) return false; // This line doesn't exist, then obviously no pieces can attack our square + const directionKey = math.getKeyFromCoords(direction); // 'dx,dy' let foundCheckersCount = 0; - for (let a = 0; a < line.length; a++) { // { coords, type } - const thisPiece = line[a]; + // Iterate through every piece on the line, and test if they can attack our square + for (const thisPiece of line) { // { coords, type } const thisPieceColor = math.getPieceColorFromType(thisPiece.type) - if (colorOfFriendlys === thisPieceColor) continue; // Same team, can't capture us, CONTINUE next piece! - if (thisPieceColor === 'neutral') continue; + if (color === thisPieceColor) continue; // Same team, can't capture us, CONTINUE to next piece! + if (thisPieceColor === 'neutral') continue; // Neutrals can't move, that means they can't make captures, right? const thisPieceMoveset = legalmoves.getPieceMoveset(gamefile, thisPiece.type) if (!thisPieceMoveset.sliding) continue; // Piece has no sliding movesets. - const moveset = thisPieceMoveset.sliding[math.getKeyFromCoords(direction)]; - if (!moveset) continue; // Piece can't slide in the direction we're looking in + const moveset = thisPieceMoveset.sliding[directionKey]; + if (!moveset) continue; // Piece can't slide in the direction our line is going const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece can't move in the direction of this line, NEXT piece! - // const rectangle = {left: thisPieceLegalSlide[0], right: thisPieceLegalSlide[1], bottom: coords[1], top: coords[1]} - // const isWithinMoveset = math.boxContainsSquare(rectangle, coords) - const isWithinMoveset = legalmoves.doesSlidingNetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords) - - if (!isWithinMoveset) continue; // This piece can't slide so far as to reach us, NEXT piece! + if (!legalmoves.doesSlidingNetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords)) continue; // This piece can't slide so far as to reach us, NEXT piece! - // This piece is checking us!! + // This piece is attacking this square! + if (!attackers) return true; // Attackers array isn't being tracked, just insta-return to save compute not finding other attackers! foundCheckersCount++; appendAttackerToList(attackers, { coords: thisPiece.coords, slidingCheck: true }) - if (foundCheckersCount >= 2) return true; // There'll never be more than 2 checkers on the same line (until witches become a thing) } return foundCheckersCount > 0; @@ -207,7 +221,7 @@ const checkdetection = (function(){ if (!individualMoves) return; // Simulate the move, then check the game state for check - for (let i = individualMoves.length - 1; i >= 0; i--) { + for (let i = individualMoves.length - 1; i >= 0; i--) { // Iterate backwards so we don't run into issues as we delete indices while iterating const thisMove = individualMoves[i]; // [x,y] if (doesMovePutInCheck(gamefile, pieceSelected, thisMove, color)) individualMoves.splice(i, 1); // Remove the move } @@ -221,13 +235,22 @@ const checkdetection = (function(){ return movepiece.simulateMove(gamefile, move, color).isCheck; } - // Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece - function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { + /** + * Removes sliding moves from the provided legal moves object that are illegal (i.e. they result in check). + * This can happen if they don't address an existing check, OR they open a discovered attack on your king. + * + * Time complexity: O(slides) basically O(1) unless you add a ton of slides to a single piece + * @param {gamefile} gamefile + * @param {LegalMoves} moves - The legal moves object to delete illegal slides from. + * @param {Piece} pieceSelected - The piece of which the running legal moves are for. + * @param {string} color - The color of friendlies + */ + function removeSlidingMovesThatPutYouInCheck (gamefile, moves, pieceSelected, color) { if (!moves.sliding) return; // No sliding moves to remove - const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); // List of coordinates of all our royal jumping pieces - + /** List of coordinates of all our royal jumping pieces @type {number[][]} */ + const royalCoords = gamefileutility.getJumpingRoyalCoords(gamefile, color); if (royalCoords.length === 0) return; // No king, no open discoveries, don't remove any sliding moves // There are 2 ways a sliding move can put you in check: @@ -265,7 +288,7 @@ const checkdetection = (function(){ // 1. Capture the checking piece - const capturingNotPossible = attackerCount > 1; // Capturing not possible with a double-check, forced to dodge, or block if possible. + const capturingNotPossible = attackerCount > 1; // Capturing not possible with a double-check (atleast not with a sliding move), forced to dodge, or block if possible. // Check if the piece has the ability to capture if (!capturingNotPossible && legalmoves.checkIfMoveLegal(legalMoves, selectedPieceCoords, attacker.coords, { ignoreIndividualMoves: true })) { @@ -278,39 +301,36 @@ const checkdetection = (function(){ // then there's no way to block. const dist = math.chebyshevDistance(royalCoords[0], attacker.coords) if (!attacker.slidingCheck || dist === 1) { - eraseAllSlidingMoves(); + delete legalMoves.sliding; // Erase all sliding moves return true; } appendBlockingMoves(royalCoords[0], attacker.coords, legalMoves, selectedPieceCoords) - eraseAllSlidingMoves(); + delete legalMoves.sliding; // Erase all sliding moves return true; - - function eraseAllSlidingMoves() { - delete legalMoves.sliding; - } } /** - * - * @param {*} gamefile - * @param {LegalMoves} moves - * @param {*} kingCoords - * @param {*} pieceSelected - * @param {*} color - * @returns + * Deletes any sliding moves from the provided running legal moves that + * open up a discovered attack on the specified coordinates + * @param {gamefile} gamefile + * @param {LegalMoves} moves - The running legal moves of the selected piece + * @param {number[]} kingCoords - The coordinates to see what sliding moves open up a discovered on + * @param {Piece} pieceSelected - The piece with the provided running legal moves + * @param {string} color - The color of friendlies */ function removeSlidingMovesThatOpenDiscovered (gamefile, moves, kingCoords, pieceSelected, color) { - const selectedPieceCoords = pieceSelected.coords; - let sameLines = []; - for (const line of gamefile.startSnapshot.slidingPossible) { // Only check current possible slides - if (math.getKeyFromLine(line, kingCoords) !== math.getKeyFromLine(line, selectedPieceCoords)) continue; - sameLines.push(line); // The piece is on the same line as the king! + /** A list of line directions that we're sharing with the king! */ + let sameLines = []; // [ [dx,dy], [dx,dy] ] + // Only check current possible slides + for (const line of gamefile.startSnapshot.slidingPossible) { // [dx,dy] + const lineKey1 = organizedlines.getKeyFromLine(line, kingCoords); + const lineKey2 = organizedlines.getKeyFromLine(line, selectedPieceCoords); + if (lineKey1 !== lineKey2) continue; // Not same line + sameLines.push(line); // The piece is sharing this line with the king }; - - // If not sharing any common line, there's no way we can open a discovered if (sameLines.length === 0) return; @@ -318,21 +338,28 @@ const checkdetection = (function(){ const deletedPiece = math.deepCopyObject(pieceSelected); movepiece.deletePiece(gamefile, pieceSelected, { updateData: false }); - // let checklines = []; - for (const line of sameLines) { - const strline = math.getKeyFromCoords(line); - const key = math.getKeyFromLine(line,kingCoords); - const opensDiscovered = doesLineAttackSquare(gamefile, gamefile.piecesOrganizedByLines[strline][key], line, kingCoords, color, []) + // let checklines = []; // For Idon's code below + // For every line direction we share with the king... + for (const direction1 of sameLines) { // [dx,dy] + const strline = math.getKeyFromCoords(direction1); // 'dx,dy' + const key = organizedlines.getKeyFromLine(direction1,kingCoords); // 'C|X' + const line = gamefile.piecesOrganizedByLines[strline][key]; + const opensDiscovered = doesLineAttackSquare(gamefile, line, direction1, kingCoords, color) if (!opensDiscovered) continue; - // checklines.push(line); + // The piece opens a discovered if it were to be gone! + // checklines.push(line); // For Idon's code below // Delete all lines except this one (because if we move off of it we would be in check!) - for (const direction of Object.keys(moves.sliding)) { // 'dx,dy' - const directionNumbArray = math.getCoordsFromKey(direction) // [dx,dy] - if (math.areCoordsEqual(directionNumbArray, line)) continue; // Same line - delete moves.sliding[direction]; + for (const direction2 of Object.keys(moves.sliding)) { // 'dx,dy' + const direction2NumbArray = math.getCoordsFromKey(direction2) // [dx,dy] + if (math.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 + // ON THE same line!! It's so tricky to know what squares would keep the discovered closed. + // See the discussion on discord: https://discord.com/channels/1114425729569017918/1260357580845224138/1263583566563119165 // const tempslides = {} // r : { // if (checklines.length > 1) { @@ -389,11 +416,20 @@ const checkdetection = (function(){ } // Appends moves to moves.individual if the selected pieces is able to get between squares 1 & 2 - function appendBlockingMoves (square1, square2, moves, coords) { // coords is of the selected piece + + /** + * Appends legal blocking moves to the provided moves object if the piece + * is able to get between squares 1 & 2. + * @param {number[]} square1 - `[x,y]` + * @param {number[]} square2 - `[x,y]` + * @param {LegalMoves} moves - The moves object of the piece + * @param {number[]} coords - The coordinates of the piece with the provided legal moves: `[x,y]` + */ + function appendBlockingMoves(square1, square2, moves, coords) { // coords is of the selected piece // What is the line between our king and the attacking piece? - let direction = [square1[0] - square2[0], square1[1] - square2[1]]; + let direction = [square1[0] - square2[0], square1[1] - square2[1]]; // [dx,dy] - /** @type {BoundingBox} */ + /** The minimum bounding box that contains our 2 squares, at opposite corners. @type {BoundingBox} */ const box = { left: Math.min(square1[0],square2[0]), right: Math.max(square1[0],square2[0]), @@ -401,30 +437,28 @@ const checkdetection = (function(){ bottom: Math.min(square1[1],square2[1]) } - function appendBlockPointIfLegal (blockPoint) { + + for (const lineKey in moves.sliding) { // 'dx,dy' + const line = math.getCoordsFromKey(lineKey) // [dx,dy] + const c1 = organizedlines.getCFromLine(line, coords); // Line of our selected piece + const c2 = organizedlines.getCFromLine(direction,square2) // Line between our 2 squares + const blockPoint = math.getLineIntersection(line[0], line[1], c1, direction[0], direction[1], c2) // The intersection point of the 2 lines. + // Idon us's old code // if (!math.isAproxEqual(blockPoint[0],Math.round(blockPoint[0])) || - // !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) {console.log("A"); return}; // Block is off grid so probably not valid + // !math.isAproxEqual(blockPoint[1],Math.round(blockPoint[1]))) {console.log("A"); continue}; // Block is off grid so probably not valid // blockPoint=[Math.round(blockPoint[0]), Math.round(blockPoint[1])] - // if (math.getKeyFromLine(line,blockPoint)!==math.getKeyFromLine(line, coords)) {console.log("C"); return}; // stop line multiples being annoying + // if (organizedlines.getKeyFromLine(line,blockPoint)!==organizedlines.getKeyFromLine(line, coords)) {console.log("C"); continue}; // stop line multiples being annoying // Naviary's new code - if (blockPoint === null) return; // None (or infinite) intersection points! - if (!math.boxContainsSquare(box, blockPoint)) return; // Intersection point not between our 2 points, but outside of them. - if (!math.areCoordsIntegers(blockPoint)) return; // It doesn't intersect at a whole number, impossible for our piece to move here! - if (math.areCoordsEqual(blockPoint, square1)) return; // Can't move onto our piece that's in check.. - if (math.areCoordsEqual(blockPoint, square2)) return; // nor to the piece that is checking us (those are added prior to this if it's legal)! - + if (blockPoint === null) continue; // None (or infinite) intersection points! + if (!math.boxContainsSquare(box, blockPoint)) continue; // Intersection point not between our 2 points, but outside of them. + if (!math.areCoordsIntegers(blockPoint)) continue; // It doesn't intersect at a whole number, impossible for our piece to move here! + if (math.areCoordsEqual(blockPoint, square1)) continue; // Can't move onto our piece that's in check.. + if (math.areCoordsEqual(blockPoint, square2)) continue; // nor to the piece that is checking us (those are added prior to this if it's legal)! + // Can our piece legally move there? if (legalmoves.checkIfMoveLegal(moves, coords, blockPoint, { ignoreIndividualMoves: true })) moves.individual.push(blockPoint) // Can block! - } - - for (const linestr in moves.sliding) { - const line = math.getCoordsFromKey(linestr) - const c1 = math.getCFromLineInGeneralForm(line, coords); // Line of our selected piece - const c2 = math.getCFromLineInGeneralForm(direction,square2) // Line between our 2 squares - const blockPoint = math.getLineIntersection(line[0], line[1], c1, direction[0], direction[1], c2) - appendBlockPointIfLegal(blockPoint, line) } } diff --git a/src/client/scripts/game/chess/gamefile.js b/src/client/scripts/game/chess/gamefile.js index 7005b6ee4..31340dd17 100644 --- a/src/client/scripts/game/chess/gamefile.js +++ b/src/client/scripts/game/chess/gamefile.js @@ -51,7 +51,7 @@ function gamefile(metadata, { moves = [], variantOptions, gameConclusion } = {}) box: undefined, /** A set of all types of pieces that are in this game, without their color extension: `['pawns','queens']` */ existingTypes: undefined, - /** Possible sliding moves in this game, dependant on what pieces there are. */ + /** Possible sliding moves in this game, dependant on what pieces there are: `[[1,1],[1,0]]` @type {number[][]}*/ slidingPossible: undefined } diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index f2c7cd30f..d37924f18 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -101,7 +101,7 @@ const legalmoves = (function(){ for (let i=0; i max) return false; + const min = slidinget[0] * direction[axis] + pieceCoords[axis] + const max = slidinget[1] * direction[axis] + pieceCoords[axis] - return true; + return coordMag >= min && coordMag <= max; } /** diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index bc4e26918..1d6e0897c 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -67,7 +67,7 @@ const organizedlines = { let lines = gamefile.startSnapshot.slidingPossible for (let i = 0; ivalue) return min; if (max Date: Fri, 19 Jul 2024 01:01:50 -0600 Subject: [PATCH 44/58] Moved checkmate alg to new script --- .../scripts/game/chess/checkdetection.js | 211 +---------------- src/client/scripts/game/chess/checkmate.js | 214 ++++++++++++++++++ src/client/scripts/game/chess/wincondition.js | 2 +- 3 files changed, 216 insertions(+), 211 deletions(-) create mode 100644 src/client/scripts/game/chess/checkmate.js diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 0b4d4f787..8a2412027 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -462,219 +462,10 @@ const checkdetection = (function(){ } } - /** - * Calculates if the provided gamefile is over by checkmate or a repetition draw - * @param {gamefile} gamefile - The gamefile to detect if it's in checkmate - * @returns {string | false} The color of the player who won by checkmate. 'white checkmate', 'black checkmate', or 'draw repetition', 'draw stalemate'. Or *false* if the game isn't over. - */ - function detectCheckmateOrDraw(gamefile) { - - // Is there a draw by repetition? - if (detectRepetitionDraw(gamefile)) return 'draw repetition' - - // The game also will be over when the player has zero legal moves remaining, lose or draw. - // Iterate through every piece, calculating its legal moves. The first legal move we find, we - // know the game is not over yet... - - const whosTurn = gamefile.whosTurn; - const whiteOrBlack = whosTurn === 'white' ? pieces.white : pieces.black; - for (let i = 0; i < whiteOrBlack.length; i++) { - const thisType = whiteOrBlack[i]; - const thesePieces = gamefile.ourPieces[thisType] - for (let a = 0; a < thesePieces.length; a++) { - const coords = thesePieces[a]; - if (!coords) continue; // Piece undefined. We leave in deleted pieces so others retain their index! - const index = gamefileutility.getPieceIndexByTypeAndCoords(gamefile, thisType, coords) - const thisPiece = { type: thisType, coords, index }; // { index, coords } - const moves = legalmoves.calculate(gamefile, thisPiece) - if (!legalmoves.hasAtleast1Move(moves)) continue; - return false; - } - } - - // We made it through every single piece without finding a single move. - // So is this draw or checkmate? Depends on whether the current state is check! - // Also make sure that checkmate can't happen if the winCondition is NOT checkmate! - const usingCheckmate = wincondition.isOpponentUsingWinCondition(gamefile, 'checkmate') - if (gamefile.inCheck && usingCheckmate) { - if (whosTurn === 'white') return 'black checkmate' // Black wins - else return 'white checkmate' // White wins - } else return 'draw stalemate'; - } - - // /** THE OLD CHECKMATE ALGORITHM, THAT IS ASYNCHRONIOUS. NO LONGER USED. ASYNC STUFF IS TOO MUCH OF A KNIGHTMARE. - // * USE ROYALCAPTURE TO REMOVE FREEZES. JUST DON'T DO STALEMATE DETECTION IF THERE'S TOO MANY PIECES. - // * - // * Calculates if the provided gamefile is over by checkmate or a repetition draw - // * @param {gamefile} gamefile - The gamefile to detect if it's in checkmate - // * @returns {string} The color of the player who won by checkmate. 'white checkmate', 'black checkmate', or 'draw repetition', or 'draw stalemate' - // */ - // // Returns false when game is not over, 'white' if white has won, 'black', or 'draw' - // async function detectCheckmateOrDraw(gamefile) { - - // // Is there a draw by repetition? - - // if (detectRepetitionDraw(gamefile)) return 'draw repetition' - - // // No repetition - - // // The game also will be over when the player has zero legal moves remaining, lose or draw. - - // const whosTurn = gamefile.whosTurn; - - // // Iterate through every piece, calculating its legal moves. The first legal move we find, we - // // know the game is not over yet. - - // // How much time can we spend on this potentially long task? - // const ourPieceCount = gamefileutility.getPieceCountOfColorFromPiecesByType(gamefile.ourPieces, whosTurn); - // let pieceLimitToRecalcTime = 50; - // let piecesSinceLastCheck = 0; - // let piecesComplete = 0; - // let startTime = performance.now(); - // let timeToStop = startTime + loadbalancer.getLongTaskTime(); - - // gamefile.legalMoveSearch.isRunning = true; - // gamefile.mesh.locked++; - - // // console.log('Begin checking for checkmate!') - // // main.startTimer() - - // const whiteOrBlack = whosTurn === 'white' ? pieces.white : pieces.black; - // for (let i = 0; i < whiteOrBlack.length; i++) { - // const thisType = whiteOrBlack[i]; - // const thesePieces = gamefile.ourPieces[thisType] - // for (let a = 0; a < thesePieces.length; a++) { - // const coords = thesePieces[a]; - // if (!coords) continue; // Piece undefined. We leave in deleted pieces so others retain their index! - // const index = gamefileutility.getPieceIndexByTypeAndCoords(gamefile, thisType, coords) - // const thisPiece = { type: thisType, coords, index }; // { index, coords } - // const moves = legalmoves.calculate(gamefile, thisPiece) - // if (legalmoves.hasAtleast1Move(moves)) { - // // main.stopTimer((time) => console.log(`Checkmate alg finished! ${time} milliseconds! ${thisType} ${coords}`)) - // stats.hideMoveLooking(); - // gamefile.legalMoveSearch.isRunning = false; - // gamefile.mesh.locked--; - // return false; - // } - - // // If we've spent too much time, sleep! - // piecesSinceLastCheck++; - // piecesComplete++; - // if (piecesSinceLastCheck >= pieceLimitToRecalcTime) { - // piecesSinceLastCheck = 0; - // await sleepIfUsedTooMuchTime(); - // if (gamefile.legalMoveSearch.terminate) { - // console.log("Legal move search terminated."); - // gamefile.legalMoveSearch.terminate = false; - // gamefile.legalMoveSearch.isRunning = false; - // gamefile.mesh.locked--; - // stats.hideMoveLooking(); - // return; - // } - // if (main.gforceCalc()) { - // pieceLimitToRecalcTime = Infinity; - // main.sforceCalc(false); - // } - // } - // } - // } - - // async function sleepIfUsedTooMuchTime() { - - // if (!usedTooMuchTime()) return; // Keep processing... - - // // console.log(`Too much! Sleeping.. Used ${performance.now() - startTime} of our allocated ${maxTimeToSpend}`) - // const percentComplete = piecesComplete / ourPieceCount; - // stats.updateMoveLooking(percentComplete); - // await main.sleep(0); - // startTime = performance.now(); - // timeToStop = startTime + loadbalancer.getLongTaskTime(); - // } - - // function usedTooMuchTime() { - // return performance.now() >= timeToStop; - // } - - // stats.hideMoveLooking(); - // gamefile.legalMoveSearch.isRunning = false; - // gamefile.mesh.locked--; - - // // main.stopTimer((time) => console.log(`Checkmate alg finished! ${time} milliseconds!`)) - - // // We made it through every single piece without finding a single move. - // // So is this draw or checkmate? Depends on whether the current state is check! - // // Also make sure that checkmate can't happen if the winCondition is NOT checkmate! - // const usingCheckmate = wincondition.isOpponentUsingWinCondition(gamefile, 'checkmate') - // if (gamefile.inCheck && usingCheckmate) { - - // if (whosTurn === 'white') return 'black checkmate' // Black wins - // else return 'white checkmate' // White wins - - // } else return 'draw stalemate'; - // } - - /** - * Tests if the provided gamefile has had a repetition draw. - * @param {gamefile} gamefile - The gamefile - * @returns {boolean} *true* if there has been a repetition draw - */ - // Complexity O(m) where m is the move count since - // the last pawn push or capture! - function detectRepetitionDraw(gamefile) { - const moveList = gamefile.moves; - - const deficit = { }; // `x,y,type` - const surplus = { }; // `x,y,type` - - let equalPositionsFound = 0; - - let index = moveList.length - 1; - while (index >= 0) { - - // Moves are in the format: { type, startCoords, endCoords, captured: 'type'} - /** @type {Move} */ - const thisMove = moveList[index] - - // If the move was a pawn push or capture, no further equal positions, terminate the loop. - if (thisMove.captured || thisMove.type.startsWith('pawns')) break; - - // If this move was undo'd, there would be a DEFICIT on its endCoords - const endCoords = thisMove.endCoords; - let key = `${endCoords[0]},${endCoords[1]},${thisMove.type}` - // If there is a SURPLUS with this exact same key, delete that instead! It's been canceled-out. - if (surplus[key]) delete surplus[key] - else deficit[key] = true; - - // There would also be a SURPLUS on its startCoords - const startCoords = thisMove.startCoords; - key = `${startCoords[0]},${startCoords[1]},${thisMove.type}` - // If there is a DEFICIT with this exact same key, delete that instead! It's been canceled-out. - if (deficit[key]) delete deficit[key] - else surplus[key] = true; - - // If both the deficit and surplus objects are EMPTY, this position is equal to our current position! - const deficitKeys = Object.keys(deficit); - const surplusKeys = Object.keys(surplus); - if (deficitKeys.length === 0 && surplusKeys.length === 0) { - equalPositionsFound++; - if (equalPositionsFound === 2) break; // Enough to confirm a repetition draw! - } - - // Prep for next iteration, decrement index. - // WILL BE -1 if we've reached the beginning of the game! - index--; - } - - // Loop is finished. How many equal positions did we find? - return equalPositionsFound === 2; // TRUE if there's a repetition draw! - } - - return Object.freeze({ detectCheck, removeMovesThatPutYouInCheck, - doesMovePutInCheck, - detectCheckmateOrDraw, + doesMovePutInCheck }) })(); \ No newline at end of file diff --git a/src/client/scripts/game/chess/checkmate.js b/src/client/scripts/game/chess/checkmate.js new file mode 100644 index 000000000..44237fe7c --- /dev/null +++ b/src/client/scripts/game/chess/checkmate.js @@ -0,0 +1,214 @@ + +const checkmate = (function() { + + /** + * Calculates if the provided gamefile is over by checkmate or a repetition draw + * @param {gamefile} gamefile - The gamefile to detect if it's in checkmate + * @returns {string | false} The color of the player who won by checkmate. 'white checkmate', 'black checkmate', or 'draw repetition', 'draw stalemate'. Or *false* if the game isn't over. + */ + function detectCheckmateOrDraw(gamefile) { + + // Is there a draw by repetition? + if (detectRepetitionDraw(gamefile)) return 'draw repetition' + + // The game also will be over when the player has zero legal moves remaining, lose or draw. + // Iterate through every piece, calculating its legal moves. The first legal move we find, we + // know the game is not over yet... + + const whosTurn = gamefile.whosTurn; + const whiteOrBlack = whosTurn === 'white' ? pieces.white : pieces.black; + for (let i = 0; i < whiteOrBlack.length; i++) { + const thisType = whiteOrBlack[i]; + const thesePieces = gamefile.ourPieces[thisType] + for (let a = 0; a < thesePieces.length; a++) { + const coords = thesePieces[a]; + if (!coords) continue; // Piece undefined. We leave in deleted pieces so others retain their index! + const index = gamefileutility.getPieceIndexByTypeAndCoords(gamefile, thisType, coords) + const thisPiece = { type: thisType, coords, index }; // { index, coords } + const moves = legalmoves.calculate(gamefile, thisPiece) + if (!legalmoves.hasAtleast1Move(moves)) continue; + return false; + } + } + + // We made it through every single piece without finding a single move. + // So is this draw or checkmate? Depends on whether the current state is check! + // Also make sure that checkmate can't happen if the winCondition is NOT checkmate! + const usingCheckmate = wincondition.isOpponentUsingWinCondition(gamefile, 'checkmate') + if (gamefile.inCheck && usingCheckmate) { + if (whosTurn === 'white') return 'black checkmate' // Black wins + else return 'white checkmate' // White wins + } else return 'draw stalemate'; + } + + // /** THE OLD CHECKMATE ALGORITHM, THAT IS ASYNCHRONIOUS. NO LONGER USED. ASYNC STUFF IS TOO MUCH OF A KNIGHTMARE. + // * USE ROYALCAPTURE TO REMOVE FREEZES. JUST DON'T DO STALEMATE DETECTION IF THERE'S TOO MANY PIECES. + // * + // * Calculates if the provided gamefile is over by checkmate or a repetition draw + // * @param {gamefile} gamefile - The gamefile to detect if it's in checkmate + // * @returns {string} The color of the player who won by checkmate. 'white checkmate', 'black checkmate', or 'draw repetition', or 'draw stalemate' + // */ + // // Returns false when game is not over, 'white' if white has won, 'black', or 'draw' + // async function detectCheckmateOrDraw(gamefile) { + + // // Is there a draw by repetition? + + // if (detectRepetitionDraw(gamefile)) return 'draw repetition' + + // // No repetition + + // // The game also will be over when the player has zero legal moves remaining, lose or draw. + + // const whosTurn = gamefile.whosTurn; + + // // Iterate through every piece, calculating its legal moves. The first legal move we find, we + // // know the game is not over yet. + + // // How much time can we spend on this potentially long task? + // const ourPieceCount = gamefileutility.getPieceCountOfColorFromPiecesByType(gamefile.ourPieces, whosTurn); + // let pieceLimitToRecalcTime = 50; + // let piecesSinceLastCheck = 0; + // let piecesComplete = 0; + // let startTime = performance.now(); + // let timeToStop = startTime + loadbalancer.getLongTaskTime(); + + // gamefile.legalMoveSearch.isRunning = true; + // gamefile.mesh.locked++; + + // // console.log('Begin checking for checkmate!') + // // main.startTimer() + + // const whiteOrBlack = whosTurn === 'white' ? pieces.white : pieces.black; + // for (let i = 0; i < whiteOrBlack.length; i++) { + // const thisType = whiteOrBlack[i]; + // const thesePieces = gamefile.ourPieces[thisType] + // for (let a = 0; a < thesePieces.length; a++) { + // const coords = thesePieces[a]; + // if (!coords) continue; // Piece undefined. We leave in deleted pieces so others retain their index! + // const index = gamefileutility.getPieceIndexByTypeAndCoords(gamefile, thisType, coords) + // const thisPiece = { type: thisType, coords, index }; // { index, coords } + // const moves = legalmoves.calculate(gamefile, thisPiece) + // if (legalmoves.hasAtleast1Move(moves)) { + // // main.stopTimer((time) => console.log(`Checkmate alg finished! ${time} milliseconds! ${thisType} ${coords}`)) + // stats.hideMoveLooking(); + // gamefile.legalMoveSearch.isRunning = false; + // gamefile.mesh.locked--; + // return false; + // } + + // // If we've spent too much time, sleep! + // piecesSinceLastCheck++; + // piecesComplete++; + // if (piecesSinceLastCheck >= pieceLimitToRecalcTime) { + // piecesSinceLastCheck = 0; + // await sleepIfUsedTooMuchTime(); + // if (gamefile.legalMoveSearch.terminate) { + // console.log("Legal move search terminated."); + // gamefile.legalMoveSearch.terminate = false; + // gamefile.legalMoveSearch.isRunning = false; + // gamefile.mesh.locked--; + // stats.hideMoveLooking(); + // return; + // } + // if (main.gforceCalc()) { + // pieceLimitToRecalcTime = Infinity; + // main.sforceCalc(false); + // } + // } + // } + // } + + // async function sleepIfUsedTooMuchTime() { + + // if (!usedTooMuchTime()) return; // Keep processing... + + // // console.log(`Too much! Sleeping.. Used ${performance.now() - startTime} of our allocated ${maxTimeToSpend}`) + // const percentComplete = piecesComplete / ourPieceCount; + // stats.updateMoveLooking(percentComplete); + // await main.sleep(0); + // startTime = performance.now(); + // timeToStop = startTime + loadbalancer.getLongTaskTime(); + // } + + // function usedTooMuchTime() { + // return performance.now() >= timeToStop; + // } + + // stats.hideMoveLooking(); + // gamefile.legalMoveSearch.isRunning = false; + // gamefile.mesh.locked--; + + // // main.stopTimer((time) => console.log(`Checkmate alg finished! ${time} milliseconds!`)) + + // // We made it through every single piece without finding a single move. + // // So is this draw or checkmate? Depends on whether the current state is check! + // // Also make sure that checkmate can't happen if the winCondition is NOT checkmate! + // const usingCheckmate = wincondition.isOpponentUsingWinCondition(gamefile, 'checkmate') + // if (gamefile.inCheck && usingCheckmate) { + + // if (whosTurn === 'white') return 'black checkmate' // Black wins + // else return 'white checkmate' // White wins + + // } else return 'draw stalemate'; + // } + + /** + * Tests if the provided gamefile has had a repetition draw. + * @param {gamefile} gamefile - The gamefile + * @returns {boolean} *true* if there has been a repetition draw + */ + // Complexity O(m) where m is the move count since + // the last pawn push or capture! + function detectRepetitionDraw(gamefile) { + const moveList = gamefile.moves; + + const deficit = { }; // `x,y,type` + const surplus = { }; // `x,y,type` + + let equalPositionsFound = 0; + + let index = moveList.length - 1; + while (index >= 0) { + + // Moves are in the format: { type, startCoords, endCoords, captured: 'type'} + /** @type {Move} */ + const thisMove = moveList[index] + + // If the move was a pawn push or capture, no further equal positions, terminate the loop. + if (thisMove.captured || thisMove.type.startsWith('pawns')) break; + + // If this move was undo'd, there would be a DEFICIT on its endCoords + const endCoords = thisMove.endCoords; + let key = `${endCoords[0]},${endCoords[1]},${thisMove.type}` + // If there is a SURPLUS with this exact same key, delete that instead! It's been canceled-out. + if (surplus[key]) delete surplus[key] + else deficit[key] = true; + + // There would also be a SURPLUS on its startCoords + const startCoords = thisMove.startCoords; + key = `${startCoords[0]},${startCoords[1]},${thisMove.type}` + // If there is a DEFICIT with this exact same key, delete that instead! It's been canceled-out. + if (deficit[key]) delete deficit[key] + else surplus[key] = true; + + // If both the deficit and surplus objects are EMPTY, this position is equal to our current position! + const deficitKeys = Object.keys(deficit); + const surplusKeys = Object.keys(surplus); + if (deficitKeys.length === 0 && surplusKeys.length === 0) { + equalPositionsFound++; + if (equalPositionsFound === 2) break; // Enough to confirm a repetition draw! + } + + // Prep for next iteration, decrement index. + // WILL BE -1 if we've reached the beginning of the game! + index--; + } + + // Loop is finished. How many equal positions did we find? + return equalPositionsFound === 2; // TRUE if there's a repetition draw! + } + + return Object.freeze({ + detectCheckmateOrDraw, + }) +})() \ No newline at end of file diff --git a/src/client/scripts/game/chess/wincondition.js b/src/client/scripts/game/chess/wincondition.js index 7051776eb..f98bc3f31 100644 --- a/src/client/scripts/game/chess/wincondition.js +++ b/src/client/scripts/game/chess/wincondition.js @@ -37,7 +37,7 @@ const wincondition = (function() { || detectThreecheck(gamefile) || detectKoth(gamefile) - || checkdetection.detectCheckmateOrDraw(gamefile) // Also checks for repetition draw! + || checkmate.detectCheckmateOrDraw(gamefile) // Also checks for repetition draw! // This needs to be last so that a draw isn't enforced in a true win || detectMoveRule(gamefile) // 50-move-rule || insufficientmaterial.detectInsufficientMaterial(gamefile) // checks for insufficient material From d911421e98214ddc38d6b1a3707770ed56949887 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:04:11 -0600 Subject: [PATCH 45/58] Description for checkmate.js --- src/client/scripts/game/chess/checkmate.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client/scripts/game/chess/checkmate.js b/src/client/scripts/game/chess/checkmate.js index 44237fe7c..c75b98e06 100644 --- a/src/client/scripts/game/chess/checkmate.js +++ b/src/client/scripts/game/chess/checkmate.js @@ -1,4 +1,11 @@ +/** + * This script contains our checkmate algorithm, + * and 3-fold repetition algorithm. + */ + +"use strict"; + const checkmate = (function() { /** From 9970506e963ca4ffe6e82d7e5309f0131c7c4f7d Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:50:03 -0600 Subject: [PATCH 46/58] Auto swap to royalcapture when games with colinear slides are detected --- src/client/scripts/game/chess/game.js | 4 +++ .../scripts/game/chess/organizedlines.js | 32 +++++++++++++++++++ src/client/scripts/game/chess/wincondition.js | 21 +++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/client/scripts/game/chess/game.js b/src/client/scripts/game/chess/game.js index 7cda9e9f5..1296faed7 100644 --- a/src/client/scripts/game/chess/game.js +++ b/src/client/scripts/game/chess/game.js @@ -143,8 +143,12 @@ const game = (function(){ if (newGamefile.startSnapshot.pieceCount >= gamefileutility.pieceCountToDisableCheckmate) { miniimage.disable(); arrows.setMode(0); // Disables arrows + wincondition.swapCheckmateForRoyalCapture(gamefile); // Checkmate alg too slow, use royalcapture instead! } else miniimage.enable(); + // If there are so many hippogonals so as to create issues with discovered attacks, let's use royal capture instead! + if (organizedlines.areColinearLinesPresentInGame(gamefile)) wincondition.swapCheckmateForRoyalCapture(gamefile); + guipromotion.initUI(gamefile.gameRules.promotionsAllowed) // Regenerate the mesh of all the pieces. diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index 1d6e0897c..a7854fe61 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -301,5 +301,37 @@ const organizedlines = { const deltaAxis = lineIsVertical ? step[1] : step[0]; const coordAxis = lineIsVertical ? coords[1] : coords[0]; return math.posMod(coordAxis, deltaAxis) + }, + + /** + * Tests if the provided gamefile has colinear organized lines present in the game. + * This can occur if there are sliders that can move in the same exact direction as others. + * For example, [2,0] and [3,0]. We typically like to know this information because + * we want to avoid having trouble with calculating legal moves surrounding discovered attacks + * by using royalcapture instead of checkmate. + * @param {gamefile} gamefile + */ + areColinearLinesPresentInGame(gamefile) { + const slidingPossible = gamefile.startSnapshot.slidingPossible; // [[1,1],[1,0]] + + // How to know if 2 lines are colinear? + // They will have the exact same slope! + + // Iterate through each line, comparing its slope with every other line + for (let a = 0; a < slidingPossible.length - 1; a++) { + const line1 = slidingPossible[a]; // [dx,dy] + const slope1 = line1[1] / line1[0]; // Rise/Run + const line1IsVertical = isNaN(slope1); + + for (let b = a+1; b < slidingPossible.length; b++) { + const line2 = slidingPossible[b]; // [dx,dy] + const slope2 = line2[1] / line2[0]; // Rise/Run + const line2IsVertical = isNaN(slope2); + + if (line1IsVertical && line2IsVertical) return true; // Colinear! + if (slope1 === slope2) return true; // Colinear! + } + } + return false; } }; \ No newline at end of file diff --git a/src/client/scripts/game/chess/wincondition.js b/src/client/scripts/game/chess/wincondition.js index f98bc3f31..8a6e74f47 100644 --- a/src/client/scripts/game/chess/wincondition.js +++ b/src/client/scripts/game/chess/wincondition.js @@ -242,6 +242,24 @@ const wincondition = (function() { else if (victor === 'aborted') return '0-0'; throw new Error(`Cannot get game result from strange victor "${victor}"!`); } + + /** + * Swaps the "checkmate" win condition for "royalcapture" in the gamefile if applicable. + * + * @param {gamefile} gamefile - The gamefile containing game data. + */ + function swapCheckmateForRoyalCapture(gamefile) { + // Check if the game is using the "royalcapture" win condition + if (doesColorHaveWinCondition(gamefile, 'white', 'checkmate')) { + math.removeObjectFromArray(gamefile.gameRules.winConditions.white, 'checkmate'); + gamefile.gameRules.winConditions.white.push('royalcapture'); + } + if (doesColorHaveWinCondition(gamefile, 'black', 'checkmate')) { + math.removeObjectFromArray(gamefile.gameRules.winConditions.black, 'checkmate'); + gamefile.gameRules.winConditions.black.push('royalcapture'); + } + console.log("Swapped checkmate wincondition for royalcapture.") + } return Object.freeze({ validWinConditions, @@ -252,7 +270,8 @@ const wincondition = (function() { getWinConditionCountOfColor, isGameConclusionDecisive, getVictorAndConditionFromGameConclusion, - getResultFromVictor + getResultFromVictor, + swapCheckmateForRoyalCapture }) })(); From e740a4fa077f9880820b95b2568d804c0672e6d9 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:52:27 -0600 Subject: [PATCH 47/58] Func renamed --- src/client/scripts/game/chess/game.js | 2 +- src/client/scripts/game/chess/organizedlines.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/scripts/game/chess/game.js b/src/client/scripts/game/chess/game.js index 1296faed7..25e5f38b8 100644 --- a/src/client/scripts/game/chess/game.js +++ b/src/client/scripts/game/chess/game.js @@ -147,7 +147,7 @@ const game = (function(){ } else miniimage.enable(); // If there are so many hippogonals so as to create issues with discovered attacks, let's use royal capture instead! - if (organizedlines.areColinearLinesPresentInGame(gamefile)) wincondition.swapCheckmateForRoyalCapture(gamefile); + if (organizedlines.areColinearSlidesPresentInGame(gamefile)) wincondition.swapCheckmateForRoyalCapture(gamefile); guipromotion.initUI(gamefile.gameRules.promotionsAllowed) diff --git a/src/client/scripts/game/chess/organizedlines.js b/src/client/scripts/game/chess/organizedlines.js index a7854fe61..2c9d5dca7 100644 --- a/src/client/scripts/game/chess/organizedlines.js +++ b/src/client/scripts/game/chess/organizedlines.js @@ -311,7 +311,7 @@ const organizedlines = { * by using royalcapture instead of checkmate. * @param {gamefile} gamefile */ - areColinearLinesPresentInGame(gamefile) { + areColinearSlidesPresentInGame(gamefile) { const slidingPossible = gamefile.startSnapshot.slidingPossible; // [[1,1],[1,0]] // How to know if 2 lines are colinear? From e6f46974afe8e222ff1b498d5ed92fa7fab61d11 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 19 Jul 2024 11:00:22 +0100 Subject: [PATCH 48/58] improve arrow required algorithm --- src/client/scripts/game/rendering/arrows.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index f26e31689..e3a158daa 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -97,7 +97,6 @@ const arrows = (function() { const slides = gamefile.startSnapshot.slidingPossible for (const line of slides) { - const axis = line[0] == 0 ? 1 : 0; const perpendicular = [-line[1], line[0]]; const linestr = math.getKeyFromCoords(line); @@ -112,19 +111,16 @@ const arrows = (function() { const boardSlidesStart = Math.min(boardSlidesLeft, boardSlidesRight); const boardSlidesEnd = Math.max(boardSlidesLeft, boardSlidesRight); + for (const key in gamefile.piecesOrganizedByLines[linestr]) { + const intsects = key.split("|").map(Number) + if (boardSlidesStart > intsects || boardSlidesEnd < intsects) continue; + const pieces = calcPiecesOffScreen(line, gamefile.piecesOrganizedByLines[linestr][key]) - for (let i=Math.ceil(boardSlidesStart); i<=Math.floor(boardSlidesEnd); i++) { - for (let x=0; x Date: Fri, 19 Jul 2024 11:45:50 +0100 Subject: [PATCH 49/58] Document of `math.getLineSteps` --- src/client/scripts/game/misc/math.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 51057c6b8..379e0411d 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -420,6 +420,15 @@ const math = (function() { }; } + /** + * + * This assumes the coord is on the same line as origin + * @param {Number[]} step Slide step `[dx,dy]` + * @param {Number[]} origin Coordinate of move origin `[x,y]` + * @param {Number[]} coord Coordinate `[x,y]` + * @param {boolean} isLeft + * @returns {Number} The steps from origin + */ function getLineSteps(step, origin, coord, isLeft) { let x = (coord[0]-origin[0])/step[0] if (!isLeft) x = Math.floor(x) From 05609ebdccf3a82a5c3ae52bbc7d8f55a13199bc Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 19 Jul 2024 13:31:26 +0100 Subject: [PATCH 50/58] highlight outside of render fix hopefull for the last time --- src/client/scripts/game/misc/math.js | 5 +- .../scripts/game/rendering/highlights.js | 57 +++++++++++++------ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 379e0411d..ac85dbf64 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -431,12 +431,13 @@ const math = (function() { */ function getLineSteps(step, origin, coord, isLeft) { let x = (coord[0]-origin[0])/step[0] + console.log(x) if (!isLeft) x = Math.floor(x) else x = Math.ceil(x) if (step[0]!==0) return x; let y = Math.floor((coord[1]-origin[1])/step[1]) - if (!isLeft) x = Math.floor(x) - else x = Math.ceil(x) + if (!isLeft) y = Math.floor(y) + else y = Math.ceil(y) return y } diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 7f6c55828..1a6e1ee54 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -305,15 +305,16 @@ const highlights = (function(){ if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} - const intsect1Step = math.getLineSteps(line, coords, intsect1Tile) - const intsect2Step = math.getLineSteps(line, coords, intsect2Tile) - - concatData_HighlightedMoves_Diagonal(coords, intsect1Step, intsect2Step, legalMoves.sliding[line], line, r, g, b, a); + const intsect1Step = math.getLineSteps(line, coords, intsect1Tile, true) + const intsect2Step = math.getLineSteps(line, coords, intsect2Tile, false) + + concatData_HighlightedMoves_Diagonal(coords, intsect1Step, intsect2Step, legalMoves.sliding[strline], renderBoundingBox, line, r, g, b, a, intsect1Tile, intsect2Tile); } } - function concatData_HighlightedMoves_Diagonal (coords, intsect1Step, intsect2Step, limits, step, r, g, b, a) { - { // Left moveset + function concatData_HighlightedMoves_Diagonal (coords, intsect1Step, intsect2Step, limits, boundingBox, step, r, g, b, a, Tile1, Tile2) { + boundingBox = math.deepCopyObject(boundingBox) + left : { // Left moveset let startStep = intsect1Step let endStep = intsect2Step @@ -328,15 +329,27 @@ const highlights = (function(){ // How many times will we iterate? let iterateCount = endStep - startStep + 1 - if (iterateCount < 0) iterateCount = 0 + if (iterateCount < 0) break left; // Init starting coords of the data, this will increment by 1 every iteration let currentX = startStep * step[0] - board.gsquareCenter() - model_Offset[0] + coords[0] let currentY = startStep * step[1] - board.gsquareCenter() - model_Offset[1] + coords[1] + + let start = [startStep*step[0]+coords[0],startStep*step[1]+coords[1]] + if (!math.boxContainsSquare(boundingBox,start)) { + console.warn(`Line starts out of bounds at ${start}`); + debugger; + } + let end = [endStep*step[0]+coords[0],endStep*step[1]+coords[1]] + if (!math.boxContainsSquare(boundingBox, end)) { + console.warn(`Line ends out of bounds at ${end}`); + debugger; + } + // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [step[0], step[1]], r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, step, r, g, b, a) } - { // Right moveset + right : { // Right moveset let startStep = intsect1Step let endStep = intsect2Step @@ -351,22 +364,34 @@ const highlights = (function(){ // How many times will we iterate? let iterateCount = endStep - startStep + 1 - if (iterateCount < 0) iterateCount = 0 + if (iterateCount < 0) break right; // Init starting coords of the data, this will increment by 1 every iteration let currentX = startStep * step[0] - board.gsquareCenter() - model_Offset[0] + coords[0] let currentY = startStep * step[1] - board.gsquareCenter() - model_Offset[1] + coords[1] + + let start = [startStep*step[0]+coords[0],startStep*step[1]+coords[1]] + if (!math.boxContainsSquare(boundingBox,start)) { + console.warn(`Line starts out of bounds at ${start}`); + debugger; + } + let end = [endStep*step[0]+coords[0],endStep*step[1]+coords[1]] + if (!math.boxContainsSquare(boundingBox, end)) { + console.warn(`Line ends out of bounds at ${end}`); + debugger; + } + // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [step[0], step[1]], r, g, b, a) + addDataDiagonalVariant(iterateCount, currentX, currentY, step, r, g, b, a) } } // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the hop values which are -1 or +1 dependant on the direction we're rendering - function addDataDiagonalVariant (iterateCount, currentX, currentY, xHop, yHop, step, r, g, b, a) { - if (Number.isNaN(currentX) || Number.isNaN(currentY)) throw new Error(`CurrentX or CurrentY (${CurrentX},${CurrentY}) are NaN`) - for (let i = 0; i < iterateCount; i++) { - const endX = currentX + xHop - const endY = currentY + yHop + function addDataDiagonalVariant (iterateCount, currentX, currentY, step, r, g, b, a) { + if (Number.isNaN(currentX) || Number.isNaN(currentY)) throw new Error(`CurrentX or CurrentY (${currentX},${currentY}) are NaN`) + for (let i = 0; i < iterateCount; i++) { + const endX = currentX + 1 + const endY = currentY + 1 data.push(...bufferdata.getDataQuad_Color3D(currentX, currentY, endX, endY, z, r, g, b, a)) // Prepare for next iteration currentX += step[0] From cb50110879f641fc7a6b7c341d2b1321f523af57 Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 19 Jul 2024 17:44:37 +0100 Subject: [PATCH 51/58] cleanup and documentation i did comment the func --- src/client/scripts/game/misc/math.js | 49 ++++++++++++------- src/client/scripts/game/rendering/arrows.js | 2 +- .../scripts/game/rendering/highlightline.js | 8 +-- .../scripts/game/rendering/highlights.js | 6 +-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index ac85dbf64..1ac55256d 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -234,10 +234,9 @@ const math = (function() { function areLinesCollinear(lines) { let gradient for (const line of lines) { - console.log(line) const lgradient = line[1]/line[0] if (!gradient) gradient = lgradient - if (!Number.isFinite(gradient)&&!Number.isFinite(lgradient)) {console.log(lgradient,gradient);continue}; + if (!Number.isFinite(gradient)&&!Number.isFinite(lgradient)) {continue}; if (!isAproxEqual(lgradient, gradient)) return false; } return true @@ -431,11 +430,10 @@ const math = (function() { */ function getLineSteps(step, origin, coord, isLeft) { let x = (coord[0]-origin[0])/step[0] - console.log(x) if (!isLeft) x = Math.floor(x) else x = Math.ceil(x) if (step[0]!==0) return x; - let y = Math.floor((coord[1]-origin[1])/step[1]) + let y = (coord[1]-origin[1])/step[1] if (!isLeft) y = Math.floor(y) else y = Math.ceil(y) return y @@ -449,6 +447,13 @@ const math = (function() { return (value / (camera.getScreenBoundingBox(false).top - camera.getScreenBoundingBox(false).bottom)) * camera.getCanvasHeightVirtualPixels() } + /** + * Gets the corner/side that the line can intersect with. + * Used for `getCornerOfBoundingBox` and `getIntersectionEntryTile` + * @param {Number[]} line + * @param {boolean} leftSide + * @returns {String} The string repr of a corner/side + */ function getAABBCornerOfLine(line, leftSide) { let corner = ""; v: { @@ -463,7 +468,13 @@ const math = (function() { return corner; } - // Top left as failsafe + /** + * Get the corner coordinate of the bounding box. + * Will revert to top left if the corners sides aren't provided. + * @param {BoundingBox} boundingBox + * @param {String} corner + * @returns {Number[]} + */ function getCornerOfBoundingBox(boundingBox, corner) { const { left, right, top, bottom } = boundingBox; let yval = corner.startsWith('bottom') ? bottom : top; @@ -471,37 +482,42 @@ const math = (function() { return [xval, yval] } - function getLineIntersectionEntryTile (dx, dy, c, boundingBox, corner) { + /** + * Returns point, if there is one, of a line with specified slope intersection with the screen edge on desired corner + * @param {Number} dx X step of line + * @param {Number} dy Y step of line + * @param {Number} c C of line + * @param {BoundingBox} boundingBox + * @param {String} corner + * @returns {?Number[]} + */ + function getIntersectionEntryTile (dx, dy, c, boundingBox, corner) { const { left, right, top, bottom } = boundingBox; - let xIntersectBottom = undefined; - let xIntersectTop = undefined; - let yIntersectLeft = undefined; - let yIntersectRight = undefined; // Check for intersection with left side of rectangle if (corner.endsWith('left')) { - yIntersectLeft = ((left * dy) + c) / dx; + const yIntersectLeft = ((left * dy) + c) / dx; if (yIntersectLeft >= bottom && yIntersectLeft <= top) return [left, yIntersectLeft] } // Check for intersection with bottom side of rectangle if (corner.startsWith('bottom')) { - xIntersectBottom = ((bottom * dx) - c) / dy; + const xIntersectBottom = ((bottom * dx) - c) / dy; if (xIntersectBottom >= left && xIntersectBottom <= right) return [xIntersectBottom, bottom] } // Check for intersection with right side of rectangle if (corner.endsWith('right')) { - yIntersectRight = ((right * dy) + c) / dx; + const yIntersectRight = ((right * dy) + c) / dx; if (yIntersectRight >= bottom && yIntersectRight <= top) return [right, yIntersectRight]; } // Check for intersection with top side of rectangle if (corner.startsWith('top')) { - xIntersectTop = ((top * dx) - c) / dy; + const xIntersectTop = ((top * dx) - c) / dy; if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; } } - + /* // Returns point, if there is one, of a line with specified slope "b" intersection screen edge on desired corner function getIntersectionEntryTile (slope, b, boundingBox, corner) { // corner: "topright"/"bottomright"... const { left, right, top, bottom } = boundingBox; @@ -529,7 +545,7 @@ const math = (function() { const xIntersectTop = (top - b) * slope; if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; } - } + }*/ function convertWorldSpaceToGrid(value) { return value / movement.getBoardScale() @@ -1037,7 +1053,6 @@ const math = (function() { convertWorldSpaceToPixels_Virtual, getAABBCornerOfLine, getCornerOfBoundingBox, - getLineIntersectionEntryTile, getIntersectionEntryTile, convertWorldSpaceToGrid, euclideanDistance, diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index e3a158daa..1f58182ac 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -187,7 +187,7 @@ const arrows = (function() { if (piece.type === 'voidsN') continue; const isLeft = side==="l" const corner = math.getAABBCornerOfLine(direction, isLeft) - const renderCoords = math.getLineIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) + const renderCoords = math.getIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) if (!renderCoords) continue; const arrowDirection = isLeft ? [-direction[0],-direction[1]] : direction concatData(renderCoords, piece.type, corner, worldWidth, 0, piece.coords, arrowDirection, !isLeft) diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index a1b472c9b..1c8646828 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -51,7 +51,7 @@ const highlightline = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); - let point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + let point1 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); if (!point1) {continue}; const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, false); const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord); @@ -59,7 +59,7 @@ const highlightline = (function(){ const corner2 = math.getAABBCornerOfLine(line, false); - let point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + let point2 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); if (!point2) continue; // I hate this const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, true); const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord); @@ -128,13 +128,13 @@ const highlightline = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); - point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + point1 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, false); point1 = capPointAtSlideLimit(point1, leftLimitPointCoord, false, lineIsVertical); const corner2 = math.getAABBCornerOfLine(line, false); - point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + point2 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, true); point2 = capPointAtSlideLimit(point2, rightLimitPointCoord, true, lineIsVertical); diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 1a6e1ee54..aa11915ea 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -299,11 +299,11 @@ const highlights = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); const corner2 = math.getAABBCornerOfLine(line, false); - const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1); - const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2); + const intsect1Tile = math.getIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1); + const intsect2Tile = math.getIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2); if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} + if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with bounding box.`); continue;} const intsect1Step = math.getLineSteps(line, coords, intsect1Tile, true) const intsect2Step = math.getLineSteps(line, coords, intsect2Tile, false) From 3a3e4eb01aa8c7baf629e8c5f36f5f35596b757b Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 19 Jul 2024 17:56:08 +0100 Subject: [PATCH 52/58] Lesson learned: review a mass rename --- src/client/scripts/game/chess/legalmoves.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index d37924f18..7f3c01c47 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -163,19 +163,19 @@ const legalmoves = (function(){ * Shortens the moveset by pieces that block it's path. * @param {Piece[]} line - The list of pieces on this line * @param {number[]} direction - The direction of the line: `[dx,dy]` - * @param {number[]} slidinget - How far this piece can slide in this direction: `[left,right]`. If the line is vertical, this is `[bottom,top]` - * @param {number[]} coords - The coordinates of the piece with the specified slidinget. + * @param {number[]} slideMoveset - How far this piece can slide in this direction: `[left,right]`. If the line is vertical, this is `[bottom,top]` + * @param {number[]} coords - The coordinates of the piece with the specified slideMoveset. * @param {string} color - The color of friendlies */ - function slide_CalcLegalLimit (line, direction, slidinget, coords, color) { + function slide_CalcLegalLimit (line, direction, slideMoveset, coords, color) { - if (!slidinget) return; // Return undefined if there is no slide moveset + if (!slideMoveset) return; // Return undefined if there is no slide moveset // The default slide is [-Infinity, Infinity], change that if there are any pieces blocking our path! // For most we'll be comparing the x values, only exception is the vertical lines. const axis = direction[0] == 0 ? 1 : 0 - const limit = math.copyCoords(slidinget); + const limit = math.copyCoords(slideMoveset); // Iterate through all pieces on same line for (let i = 0; i < line.length; i++) { // What are the coords of this piece? @@ -349,19 +349,19 @@ const legalmoves = (function(){ // This requires coords be on the same line as the sliding moveset. /** - * Tests if the piece's precalculated slidinget is able to reach the provided coords. + * Tests if the piece's precalculated slideMoveset is able to reach the provided coords. * ASSUMES the coords are on the direction of travel!!! - * @param {number[]} slidinget - The distance the piece can move along this line: `[left,right]`. If the line is vertical, this will be `[bottom,top]`. + * @param {number[]} slideMoveset - The distance the piece can move along this line: `[left,right]`. If the line is vertical, this will be `[bottom,top]`. * @param {number[]} direction - The direction of the line: `[dx,dy]` * @param {number[]} pieceCoords - The coordinates of the piece with the provided sliding net * @param {number[]} coords - The coordinates we want to know if they can reach. * @returns {boolean} true if the piece is able to slide to the coordinates */ - function doesSlidingNetContainSquare(slidinget, direction, pieceCoords, coords) { + function doesSlidingNetContainSquare(slideMoveset, direction, pieceCoords, coords) { const axis = direction[0] === 0 ? 1 : 0 const coordMag = coords[axis]; - const min = slidinget[0] * direction[axis] + pieceCoords[axis] - const max = slidinget[1] * direction[axis] + pieceCoords[axis] + const min = slideMoveset[0] * direction[axis] + pieceCoords[axis] + const max = slideMoveset[1] * direction[axis] + pieceCoords[axis] return coordMag >= min && coordMag <= max; } From 4eba1678ea43de4612d818805d5bc692b4aaef7e Mon Sep 17 00:00:00 2001 From: Idonotus Date: Fri, 19 Jul 2024 19:23:40 +0100 Subject: [PATCH 53/58] CleanUp --- src/client/scripts/game/chess/checkdetection.js | 2 +- src/client/scripts/game/chess/legalmoves.js | 15 ++++++--------- src/client/scripts/game/misc/math.js | 2 +- src/client/scripts/game/rendering/highlights.js | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 8a2412027..ad66dafc7 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -169,7 +169,7 @@ const checkdetection = (function(){ const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece can't move in the direction of this line, NEXT piece! - if (!legalmoves.doesSlidingNetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords)) continue; // This piece can't slide so far as to reach us, NEXT piece! + if (!legalmoves.doesSlidingMovesetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords)) continue; // This piece can't slide so far as to reach us, NEXT piece! // This piece is attacking this square! diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 7f3c01c47..4792bd814 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -180,7 +180,7 @@ const legalmoves = (function(){ for (let i = 0; i < line.length; i++) { // What are the coords of this piece? const thisPiece = line[i] // { type, coords } - const thisPieceSteps = Math.floor((thisPiece.coords[axis]-coords[axis])/direction[axis]) + const thisPieceSteps = math.getLineSteps(direction, coords, thisPiece.coords) const thisPieceColor = math.getPieceColorFromType(thisPiece.type) const isFriendlyPiece = color === thisPieceColor const isVoid = thisPiece.type === 'voidsN'; @@ -241,7 +241,7 @@ const legalmoves = (function(){ let clickedCoordsLine = organizedlines.getKeyFromLine(line,endCoords); if (!limits || selectedPieceLine !== clickedCoordsLine) continue; - if (!doesSlidingNetContainSquare(limits, line, startCoords, endCoords)) continue; + if (!doesSlidingMovesetContainSquare(limits, line, startCoords, endCoords)) continue; return true; } return false; @@ -357,13 +357,10 @@ const legalmoves = (function(){ * @param {number[]} coords - The coordinates we want to know if they can reach. * @returns {boolean} true if the piece is able to slide to the coordinates */ - function doesSlidingNetContainSquare(slideMoveset, direction, pieceCoords, coords) { - const axis = direction[0] === 0 ? 1 : 0 - const coordMag = coords[axis]; - const min = slideMoveset[0] * direction[axis] + pieceCoords[axis] - const max = slideMoveset[1] * direction[axis] + pieceCoords[axis] + function doesSlidingMovesetContainSquare(slideMoveset, direction, pieceCoords, coords) { + const step = math.getLineSteps(direction, pieceCoords, coords) - return coordMag >= min && coordMag <= max; + return step >= slideMoveset[0] && step <= slideMoveset[1]; } /** @@ -390,7 +387,7 @@ const legalmoves = (function(){ getPieceMoveset, calculate, checkIfMoveLegal, - doesSlidingNetContainSquare, + doesSlidingMovesetContainSquare, hasAtleast1Move, slide_CalcLegalLimit, isOpponentsMoveLegal diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 1ac55256d..3b8a8d407 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -420,7 +420,7 @@ const math = (function() { } /** - * + * Gets the amount of steps from the origin point to the coord. * This assumes the coord is on the same line as origin * @param {Number[]} step Slide step `[dx,dy]` * @param {Number[]} origin Coordinate of move origin `[x,y]` diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index aa11915ea..aaae26893 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -386,7 +386,7 @@ const highlights = (function(){ } } - // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the hop values which are -1 or +1 dependant on the direction we're rendering + // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the step which is dependant on the direction we're rendering function addDataDiagonalVariant (iterateCount, currentX, currentY, step, r, g, b, a) { if (Number.isNaN(currentX) || Number.isNaN(currentY)) throw new Error(`CurrentX or CurrentY (${currentX},${currentY}) are NaN`) for (let i = 0; i < iterateCount; i++) { From b7ccd11d15972eea9f247a3ad238264332b46d8c Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:33:38 -0600 Subject: [PATCH 54/58] Updated highlights.js algorithm to be bug free and more friendly to changing the shapre of legal move highlights (whether it's a square or a dot!) --- src/client/scripts/game/misc/math.js | 56 +++--- .../scripts/game/rendering/highlights.js | 188 +++++++++++------- 2 files changed, 147 insertions(+), 97 deletions(-) diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 51057c6b8..0a0113702 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -420,17 +420,6 @@ const math = (function() { }; } - function getLineSteps(step, origin, coord, isLeft) { - let x = (coord[0]-origin[0])/step[0] - if (!isLeft) x = Math.floor(x) - else x = Math.ceil(x) - if (step[0]!==0) return x; - let y = Math.floor((coord[1]-origin[1])/step[1]) - if (!isLeft) x = Math.floor(x) - else x = Math.ceil(x) - return y - } - function convertPixelsToWorldSpace_Virtual(value) { return (value / camera.getCanvasHeightVirtualPixels()) * (camera.getScreenBoundingBox(false).top - camera.getScreenBoundingBox(false).bottom) } @@ -439,16 +428,25 @@ const math = (function() { return (value / (camera.getScreenBoundingBox(false).top - camera.getScreenBoundingBox(false).bottom)) * camera.getCanvasHeightVirtualPixels() } - function getAABBCornerOfLine(line, leftSide) { + /** + * Returns the side of the box, in english language, the line intersects with the box. + * If {@link negateSide} is false, it will return the positive X/Y side. + * If the line is orthogonal, it will only return top/bottom/left/right. + * Otherwise, it will return the corner name. + * @param {number[]} line - [dx,dy] + * @param {boolean} negateSide + * @returns {string} Which side/corner the line passes through. [0,1] & false => "top" [2,1] & true => "bottomleft" + */ + function getAABBCornerOfLine(line, negateSide) { let corner = ""; v: { - if (line[1]==0) break v; // Horizontal so parallel with top/bottom lines - corner += ((line[0]>0==line[1]>0)==leftSide==(line[0]!=0)) ? "bottom" : "top" + if (line[1] === 0) break v; // Horizontal so parallel with top/bottom lines + corner += ((line[0] > 0 === line[1] > 0) === negateSide === (line[0] !== 0)) ? "bottom" : "top" // Gonna be honest I have no idea how this works but it does sooooooo its staying } h: { - if (line[0]==0) break h; // Vertical so parallel with left/right lines - corner += leftSide ? "left" : "right" + if (line[0] === 0) break h; // Vertical so parallel with left/right lines + corner += negateSide ? "left" : "right" } return corner; } @@ -461,35 +459,44 @@ const math = (function() { return [xval, yval] } + /** + * Returns the tile-point the line intersects, on the specified side, of the provided box. + * DOES NOT round to nearest tile, but returns the floating point intersection. + * @param {number} dx - X change of the line + * @param {number} dy - Y change of the line + * @param {number} c - The c value of the line + * @param {BoundingBox} boundingBox - The box + * @param {string} corner - What side/corner the line intersects, in english language. "left"/"topright"... + * @returns {number[] | undefined} - The tile the line intersects, on the specified side, of the provided box, if it does intersect, otherwise undefined. + */ function getLineIntersectionEntryTile (dx, dy, c, boundingBox, corner) { const { left, right, top, bottom } = boundingBox; - let xIntersectBottom = undefined; - let xIntersectTop = undefined; - let yIntersectLeft = undefined; - let yIntersectRight = undefined; + // Check for intersection with left side of rectangle if (corner.endsWith('left')) { - yIntersectLeft = ((left * dy) + c) / dx; + const yIntersectLeft = ((left * dy) + c) / dx; if (yIntersectLeft >= bottom && yIntersectLeft <= top) return [left, yIntersectLeft] } // Check for intersection with bottom side of rectangle if (corner.startsWith('bottom')) { - xIntersectBottom = ((bottom * dx) - c) / dy; + const xIntersectBottom = ((bottom * dx) - c) / dy; if (xIntersectBottom >= left && xIntersectBottom <= right) return [xIntersectBottom, bottom] } // Check for intersection with right side of rectangle if (corner.endsWith('right')) { - yIntersectRight = ((right * dy) + c) / dx; + const yIntersectRight = ((right * dy) + c) / dx; if (yIntersectRight >= bottom && yIntersectRight <= top) return [right, yIntersectRight]; } // Check for intersection with top side of rectangle if (corner.startsWith('top')) { - xIntersectTop = ((top * dx) - c) / dy; + const xIntersectTop = ((top * dx) - c) / dy; if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; } + + // Doesn't intersect any tile in the box. } // Returns point, if there is one, of a line with specified slope "b" intersection screen edge on desired corner @@ -1021,7 +1028,6 @@ const math = (function() { convertCoordToWorldSpace_ClampEdge, clamp, closestPointOnLine, - getLineSteps, getBoundingBoxOfBoard, convertPixelsToWorldSpace_Virtual, convertWorldSpaceToPixels_Virtual, diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 7f6c55828..47e623a6c 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -204,19 +204,16 @@ const highlights = (function(){ // renderBoundingBox should always be greater than screen bounding box // Concats the highlighted square sliding move data to data function concatData_HighlightedMoves_Sliding () { // { left, right, bottom, top} The size of the box we should render within + if (!selection.getLegalMovesOfSelectedPiece().sliding) return; // No sliding moves const coords = selection.getPieceSelected().coords - const legalMovesHighlightColor = options.getDefaultLegalMoveHighlight(); - - const [r,g,b,a] = legalMovesHighlightColor; + const [r,g,b,a] = options.getDefaultLegalMoveHighlight(); // Legal moves highlight color // How do we go about calculating the vertex data of our sliding moves? // We COULD minimize how often we regenerate the buffer model by extending these lines beyond our field of view. // BUT currently we're regenerating every frame so let's just render to screen edge! - if (!selection.getLegalMovesOfSelectedPiece().sliding) return; // No sliding moves - // First we need to calculate the data of the horizontal slide concatData_HighlightedMoves_Sliding_Horz(coords, boundingBoxOfRenderRange.left, boundingBoxOfRenderRange.right) @@ -288,92 +285,140 @@ const highlights = (function(){ data.push(...bufferdata.getDataQuad_Color3D(startX, startY, endX, endY, z, r, g, b, a)) } + // Adds the vertex data of all legal slide diagonals (not orthogonal), no matter the step size/slope function concatData_HighlightedMoves_Diagonals (coords, renderBoundingBox, r, g, b, a) { const legalMoves = selection.getLegalMovesOfSelectedPiece() const lineSet = new Set(Object.keys(legalMoves.sliding)) lineSet.delete('1,0') lineSet.delete('0,1') - for (const strline of lineSet) { - const line = math.getCoordsFromKey(strline); - const lineEqua = organizedlines.getCFromLine(line, coords); - const corner1 = math.getAABBCornerOfLine(line, true); - const corner2 = math.getAABBCornerOfLine(line, false); - const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner1); - const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], lineEqua, renderBoundingBox, corner2); + const offset = game.getGamefile().mesh.offset; + const vertexData = bufferdata.getDataQuad_Color3D_FromCoord_WithOffset(offset, coords, z, [r,g,b,a]) // Square / dot highlighting 1 legal move + + for (const strline of lineSet) { + const line = math.getCoordsFromKey(strline); // [dx,dy] + const C = organizedlines.getCFromLine(line, coords); - if (!intsect1Tile && !intsect2Tile) {continue;} // If there's no intersection point, it's off the screen, don't bother rendering. - if (!intsect1Tile || !intsect2Tile) {console.error(`Line only has one intersect with square.`); continue;} + const corner1 = math.getAABBCornerOfLine(line, true); // "right" + const corner2 = math.getAABBCornerOfLine(line, false); // "bottomleft" + const intsect1Tile = math.getLineIntersectionEntryTile(line[0], line[1], C, renderBoundingBox, corner1); + const intsect2Tile = math.getLineIntersectionEntryTile(line[0], line[1], C, renderBoundingBox, corner2); - const intsect1Step = math.getLineSteps(line, coords, intsect1Tile) - const intsect2Step = math.getLineSteps(line, coords, intsect2Tile) + if (!intsect1Tile && !intsect2Tile) continue; // If there's no intersection point, it's off the screen, don't bother rendering. + if (!intsect1Tile || !intsect2Tile) { console.error(`Line only has one intersect with square.`); continue; } - concatData_HighlightedMoves_Diagonal(coords, intsect1Step, intsect2Step, legalMoves.sliding[line], line, r, g, b, a); + concatData_HighlightedMoves_Diagonal(coords, line, intsect1Tile, intsect2Tile, legalMoves.sliding[line], vertexData); } } - function concatData_HighlightedMoves_Diagonal (coords, intsect1Step, intsect2Step, limits, step, r, g, b, a) { - { // Left moveset - let startStep = intsect1Step - let endStep = intsect2Step - - // Make sure it doesn't end before the tile right in front of us - if (endStep >= 0) endStep = -1 - let leftLimit = limits[0] - - // Make sure it doesn't phase through our move limit - if (startStep < leftLimit) { - startStep = leftLimit - } - - // How many times will we iterate? - let iterateCount = endStep - startStep + 1 - if (iterateCount < 0) iterateCount = 0 - - // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startStep * step[0] - board.gsquareCenter() - model_Offset[0] + coords[0] - let currentY = startStep * step[1] - board.gsquareCenter() - model_Offset[1] + coords[1] - // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [step[0], step[1]], r, g, b, a) + /** + * Adds the vertex of a directional movement line, in both directions, of ANY SLOPED + * step EXCEPT those that are orthogonal! This works with ALL diagonal or hippogonals! + * @param {number[]} coords - [x,y] of the piece + * @param {number[]} step - Of the line / moveset + * @param {number[]} intsect1Tile - What point this line intersect the left side of the screen box. + * @param {number[]} intsect2Tile - What point this line intersect the right side of the screen box. + * @param {number[]} limits - Slide limit: [-7,Infinity] + * @param {number[]} vertexData - The vertex data of a single legal move highlight (square or dot). + */ + function concatData_HighlightedMoves_Diagonal (coords, step, intsect1Tile, intsect2Tile, limits, vertexData) { + + // Right moveset + concatData_HighlightedMoves_Diagonal_Split(coords, step, intsect1Tile, intsect2Tile, limits[1], math.deepCopyObject(vertexData)) + + // Left moveset + const negStep = [step[0] * -1, step[1] * -1]; + concatData_HighlightedMoves_Diagonal_Split(coords, negStep, intsect1Tile, intsect2Tile, Math.abs(limits[0]), math.deepCopyObject(vertexData)) + } + + /** + * Adds the vertex of a single directional ray (split in 2 from a normal slide). + * @param {number[]} coords - [x,y] of the piece + * @param {number[]} step - Of the line / moveset. THIS NEEDS TO BE NEGATED if the ray is pointing to the left!! + * @param {number[]} intsect1Tile - What point this line intersect the left side of the screen box. + * @param {number[]} intsect2Tile - What point this line intersect the right side of the screen box. + * @param {number} limit - Needs to be POSITIVE. + * @param {number[]} vertexData - The vertex data of a single legal move highlight (square or dot). + */ + function concatData_HighlightedMoves_Diagonal_Split(coords, step, intsect1Tile, intsect2Tile, limit, vertexData) { + if (limit === 0) return; // Quick exit + + const stepIsPositive = step[0] > 0; + const entryIntsectTile = stepIsPositive ? intsect1Tile : intsect2Tile; + const exitIntsectTile = stepIsPositive ? intsect2Tile : intsect1Tile; + + // Where the piece would land after 1 step + let startCoords = [coords[0] + step[0], coords[1] + step[1]]; + // Is the piece + // Is the piece left, off-screen, of our intsect1Tile? + if (stepIsPositive && startCoords[0] < entryIntsectTile[0] || !stepIsPositive && startCoords[0] > entryIntsectTile[0]) { // Modify the start square + const distToEntryIntsectTile = entryIntsectTile[0] - startCoords[0]; // Can be negative + const distInSteps = Math.ceil(distToEntryIntsectTile / step[0]); // Should always be positive + const distRoundedUpToNearestStep = distInSteps * step[0]; // Can be negative + const newStartX = startCoords[0] + distRoundedUpToNearestStep; + const yToXStepRatio = step[1] / step[0]; + const newStartY = startCoords[1] + distRoundedUpToNearestStep * yToXStepRatio; + startCoords = [newStartX, newStartY] } - { // Right moveset - let startStep = intsect1Step - let endStep = intsect2Step - - // Make sure it doesn't start before the tile right in front of us - if (startStep <= 0) startStep = 1 - let rightLimit = limits[1] - - // Make sure it doesn't phase through our move limit - if (endStep > rightLimit) { - endStep = rightLimit - } - - // How many times will we iterate? - let iterateCount = endStep - startStep + 1 - if (iterateCount < 0) iterateCount = 0 - - // Init starting coords of the data, this will increment by 1 every iteration - let currentX = startStep * step[0] - board.gsquareCenter() - model_Offset[0] + coords[0] - let currentY = startStep * step[1] - board.gsquareCenter() - model_Offset[1] + coords[1] - // Generate data of each highlighted square - addDataDiagonalVariant(iterateCount, currentX, currentY, +1, +1, [step[0], step[1]], r, g, b, a) + + let endCoords = exitIntsectTile; + // Is the exitIntsectTile farther than we can legally slide? + const xWeShouldEnd = coords[0] + step[0] * limit; + if (stepIsPositive && xWeShouldEnd < endCoords[0] || !stepIsPositive && xWeShouldEnd > endCoords[0]) { + const yWeShouldEnd = coords[1] + step[1] * limit; + endCoords = [xWeShouldEnd, yWeShouldEnd] } + + // Shift the vertex data of our first step to the right place + const vertexDataXDiff = startCoords[0] - coords[0]; + const vertexDataYDiff = startCoords[1] - coords[1]; + shiftVertexData(vertexData, vertexDataXDiff, vertexDataYDiff); // The vertex data of the 1st step! + + // Calculate how many times we need to iteratively shift this vertex data and append it to our vertex data array + const xDist = stepIsPositive ? endCoords[0] - startCoords[0] : startCoords[0] - endCoords[0]; + if (xDist < 0) return; // Early exit. The piece is up-right of our screen + const iterationCount = Math.floor((xDist + Math.abs(step[0])) / Math.abs(step[0])); // How many legal move square/dots to render on this line + + addDataDiagonalVariant(vertexData, step, iterationCount) } - // Calculates the vertex data of a single diagonal direction eminating from piece. Current x & y is the starting values, followed by the hop values which are -1 or +1 dependant on the direction we're rendering - function addDataDiagonalVariant (iterateCount, currentX, currentY, xHop, yHop, step, r, g, b, a) { - if (Number.isNaN(currentX) || Number.isNaN(currentY)) throw new Error(`CurrentX or CurrentY (${CurrentX},${CurrentY}) are NaN`) + /** + * Accepts the vertex data of a legal move highlight (square/dot), and recursively + * adds it to the vertex data list, shifting by the step size. + * @param {number[]} vertexData - The vertex data of the legal move highlight (square/dot). Stride 7 (3 vertex values, 4 color). + * @param {number[]} step - [dx,dy] + * @param {number} iterateCount + */ + function addDataDiagonalVariant (vertexData, step, iterateCount) { for (let i = 0; i < iterateCount; i++) { - const endX = currentX + xHop - const endY = currentY + yHop - data.push(...bufferdata.getDataQuad_Color3D(currentX, currentY, endX, endY, z, r, g, b, a)) - // Prepare for next iteration - currentX += step[0] - currentY += step[1] + data.push(...vertexData) + shiftVertexData(vertexData, step[0], step[1]); } } + /** + * Shifts the provided vertex data. Stride 7 (three vertex values, 4 color). + * Use this when copying and shifting the data of legal move highlights (square/dots). + * @param {number[]} data + * @param {number} x + * @param {number} y + */ + function shiftVertexData(data, x, y) { + // Skip the z and the color indices + data[0] += x; + data[1] += y; + data[7] += x; + data[8] += y; + data[14] += x; + data[15] += y; + data[21] += x; + data[22] += y; + data[28] += x; + data[29] += y; + data[35] += x; + data[36] += y; + } + // Generates buffer model and renders the outline of the render range of our highlights, useful in developer mode. function renderBoundingBoxOfRenderRange() { const color = [1,0,1, 1]; @@ -401,7 +446,6 @@ const highlights = (function(){ model.render(); } - return Object.freeze({ render, regenModel From 63cab0d7374618501a9b2562b3555980c65231fa Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:12:03 -0600 Subject: [PATCH 55/58] Fix --- .gitignore | 5 +- build.mjs | 17 +- dev-utils/pieces/svg/fairy/compressed/air.svg | 2 +- .../pieces/svg/fairy/compressed/amazonsW.svg | 2 +- .../svg/fairy/compressed/archbishopsB.svg | 37 +- .../svg/fairy/compressed/archbishopsW.svg | 37 +- .../pieces/svg/fairy/compressed/camelsB.svg | 2 +- .../pieces/svg/fairy/compressed/camelsW.svg | 2 +- .../{centaur-b.svg => centaursB.svg} | 2 +- .../{centaur-w.svg => centaursW.svg} | 2 +- .../svg/fairy/compressed/chancellorsB.svg | 2 +- .../svg/fairy/compressed/chancellorsW.svg | 2 +- .../svg/fairy/compressed/commonersB.svg | 106 +--- .../svg/fairy/compressed/commonersW.svg | 95 +--- .../pieces/svg/fairy/compressed/dragonsB.svg | 2 +- .../pieces/svg/fairy/compressed/dragonsW.svg | 2 +- .../pieces/svg/fairy/compressed/dragosB.svg | 2 +- .../pieces/svg/fairy/compressed/dragosW.svg | 2 +- .../pieces/svg/fairy/compressed/giraffesB.svg | 21 +- .../pieces/svg/fairy/compressed/giraffesW.svg | 21 +- .../pieces/svg/fairy/compressed/guardsB.svg | 115 +--- .../pieces/svg/fairy/compressed/guardsW.svg | 104 +--- .../pieces/svg/fairy/compressed/hawksB.svg | 105 +--- .../pieces/svg/fairy/compressed/hawksW.svg | 109 +--- .../pieces/svg/fairy/compressed/kelpiesB.svg | 2 +- .../pieces/svg/fairy/compressed/kelpiesW.svg | 2 +- .../svg/fairy/compressed/knightridersB.svg | 47 +- .../svg/fairy/compressed/knightridersW.svg | 43 +- .../svg/fairy/compressed/obstaclesN.svg | 2 +- .../pieces/svg/fairy/compressed/rosesB.svg | 2 +- .../pieces/svg/fairy/compressed/rosesW.svg | 2 +- .../svg/fairy/compressed/royalcentaursB.svg | 2 +- .../svg/fairy/compressed/royalcentaursW.svg | 2 +- .../svg/fairy/compressed/royalqueensB.svg | 2 +- .../svg/fairy/compressed/royalqueensW.svg | 2 +- .../svg/fairy/compressed/unicornosB.svg | 2 +- .../svg/fairy/compressed/unicornosW.svg | 2 +- .../pieces/svg/fairy/compressed/unicornsB.svg | 2 +- .../pieces/svg/fairy/compressed/unicornsW.svg | 2 +- .../pieces/svg/fairy/compressed/zebrasB.svg | 2 +- .../pieces/svg/fairy/compressed/zebrasW.svg | 2 +- .../{centaur-b.svg => centaursB.svg} | 0 .../{centaur-w.svg => centaursW.svg} | 0 .../svg/standard/compressed/bishopsB.svg | 1 + .../svg/standard/compressed/bishopsW.svg | 1 + .../pieces/svg/standard/compressed/kingsB.svg | 1 + .../pieces/svg/standard/compressed/kingsW.svg | 1 + .../svg/standard/compressed/knightsB.svg | 1 + .../svg/standard/compressed/knightsW.svg | 1 + .../pieces/svg/standard/compressed/pawnsB.svg | 1 + .../pieces/svg/standard/compressed/pawnsW.svg | 1 + .../svg/standard/compressed/queensB.svg | 1 + .../svg/standard/compressed/queensW.svg | 1 + .../pieces/svg/standard/compressed/rooksB.svg | 1 + .../pieces/svg/standard/compressed/rooksW.svg | 1 + .../{ => uncompressed}/Chess_bdt45.svg | 24 +- .../{ => uncompressed}/Chess_blt45.svg | 24 +- .../{ => uncompressed}/Chess_kdt45.svg | 0 .../{ => uncompressed}/Chess_klt45.svg | 0 .../{ => uncompressed}/Chess_ndt45.svg | 44 +- .../{ => uncompressed}/Chess_nlt45.svg | 38 +- .../{ => uncompressed}/Chess_pdt45.svg | 0 .../{ => uncompressed}/Chess_plt45.svg | 0 .../{ => uncompressed}/Chess_qdt45.svg | 0 .../{ => uncompressed}/Chess_qlt45.svg | 0 .../{ => uncompressed}/Chess_rdt45.svg | 0 .../{ => uncompressed}/Chess_rlt45.svg | 50 +- docs/NAVIGATING.md | 27 +- docs/TRANSLATIONS.md | 59 ++ nodemon.json | 2 +- package-lock.json | 191 ++++++- package.json | 7 +- readme.md | 4 +- src/client/css/createaccount.css | 15 - src/client/css/credits.css | 15 - src/client/css/footer.css | 26 + src/client/css/index.css | 16 - src/client/css/login.css | 15 - src/client/css/member.css | 15 - src/client/css/news.css | 16 - src/client/css/termsofservice.css | 15 - src/client/scripts/createaccount.js | 23 +- .../scripts/game/chess/checkdetection.js | 2 +- .../scripts/game/chess/copypastegame.js | 28 +- src/client/scripts/game/chess/legalmoves.js | 29 +- src/client/scripts/game/gui/gui.js | 2 +- src/client/scripts/game/gui/guigameinfo.js | 112 ++-- src/client/scripts/game/gui/guipause.js | 14 +- src/client/scripts/game/gui/guiplay.js | 8 +- src/client/scripts/game/gui/stats.js | 6 +- src/client/scripts/game/gui/statustext.js | 4 +- src/client/scripts/game/htmlscript.js | 2 +- .../scripts/game/misc/browsersupport.js | 2 +- src/client/scripts/game/misc/invites.js | 20 +- src/client/scripts/game/misc/math.js | 41 +- src/client/scripts/game/misc/onlinegame.js | 28 +- src/client/scripts/game/rendering/arrows.js | 22 +- .../scripts/game/rendering/highlightline.js | 8 +- .../scripts/game/rendering/miniimage.js | 4 +- src/client/scripts/game/rendering/options.js | 4 +- .../scripts/game/rendering/perspective.js | 8 +- .../scripts/game/rendering/piecesmodel.js | 2 +- src/client/scripts/game/rendering/shaders.js | 4 +- src/client/scripts/game/rendering/webgl.js | 2 +- src/client/scripts/game/websocket.js | 29 +- src/client/scripts/languagepicker.js | 42 ++ src/client/scripts/login.js | 13 +- src/client/scripts/member.js | 20 +- src/client/scripts/memberHeader.js | 4 +- src/client/scripts/validation.js | 8 +- src/client/views/components/footer.ejs | 17 + .../{createaccount.html => createaccount.ejs} | 38 +- src/client/views/credits.ejs | 75 +++ src/client/views/credits.html | 74 --- src/client/views/errors/{400.html => 400.ejs} | 4 +- src/client/views/errors/{401.html => 401.ejs} | 2 +- src/client/views/errors/{404.html => 404.ejs} | 2 +- src/client/views/errors/{409.html => 409.ejs} | 4 +- src/client/views/errors/{500.html => 500.ejs} | 4 +- src/client/views/index.ejs | 81 +++ src/client/views/index.html | 81 --- src/client/views/{login.html => login.ejs} | 35 +- src/client/views/member.ejs | 73 +++ src/client/views/member.html | 69 --- src/client/views/{news.html => news.ejs} | 79 +-- src/client/views/{play.html => play.ejs} | 273 ++++----- src/client/views/termsofservice.ejs | 90 +++ src/client/views/termsofservice.html | 88 --- src/server/config/setupTranslations.js | 292 ++++++++++ src/server/controllers/authController.js | 17 +- .../controllers/createaccountController.js | 26 +- src/server/controllers/logoutController.js | 2 +- src/server/controllers/memberController.js | 2 +- .../controllers/refreshTokenController.js | 6 +- .../controllers/removeAccountController.js | 4 +- .../controllers/verifyAccountController.js | 2 +- src/server/middleware/errorHandler.js | 2 +- src/server/middleware/middleware.js | 10 + src/server/middleware/rateLimit.js | 6 +- src/server/middleware/send404.js | 6 +- src/server/middleware/verifyRoles.js | 4 +- src/server/routes/createaccount.js | 4 +- src/server/routes/member.js | 2 +- src/server/routes/root.js | 116 ++-- src/server/server.js | 8 + src/server/utility/HTMLScriptInjector.js | 185 ++---- translation/changes.json | 4 + translation/en-US.toml | 533 ++++++++++++++++++ translation/fr-FR.toml | 533 ++++++++++++++++++ 149 files changed, 2835 insertions(+), 2040 deletions(-) rename dev-utils/pieces/svg/fairy/compressed/{centaur-b.svg => centaursB.svg} (95%) rename dev-utils/pieces/svg/fairy/compressed/{centaur-w.svg => centaursW.svg} (94%) rename dev-utils/pieces/svg/fairy/uncompressed/{centaur-b.svg => centaursB.svg} (100%) rename dev-utils/pieces/svg/fairy/uncompressed/{centaur-w.svg => centaursW.svg} (100%) create mode 100644 dev-utils/pieces/svg/standard/compressed/bishopsB.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/bishopsW.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/kingsB.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/kingsW.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/knightsB.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/knightsW.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/pawnsB.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/pawnsW.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/queensB.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/queensW.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/rooksB.svg create mode 100644 dev-utils/pieces/svg/standard/compressed/rooksW.svg rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_bdt45.svg (99%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_blt45.svg (99%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_kdt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_klt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_ndt45.svg (98%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_nlt45.svg (98%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_pdt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_plt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_qdt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_qlt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_rdt45.svg (100%) rename dev-utils/pieces/svg/standard/{ => uncompressed}/Chess_rlt45.svg (97%) create mode 100644 docs/TRANSLATIONS.md create mode 100644 src/client/css/footer.css create mode 100644 src/client/scripts/languagepicker.js create mode 100644 src/client/views/components/footer.ejs rename src/client/views/{createaccount.html => createaccount.ejs} (58%) create mode 100644 src/client/views/credits.ejs delete mode 100644 src/client/views/credits.html rename src/client/views/errors/{400.html => 400.ejs} (76%) rename src/client/views/errors/{401.html => 401.ejs} (85%) rename src/client/views/errors/{404.html => 404.ejs} (85%) rename src/client/views/errors/{409.html => 409.ejs} (61%) rename src/client/views/errors/{500.html => 500.ejs} (76%) create mode 100644 src/client/views/index.ejs delete mode 100644 src/client/views/index.html rename src/client/views/{login.html => login.ejs} (55%) create mode 100644 src/client/views/member.ejs delete mode 100644 src/client/views/member.html rename src/client/views/{news.html => news.ejs} (50%) rename src/client/views/{play.html => play.ejs} (75%) create mode 100644 src/client/views/termsofservice.ejs delete mode 100644 src/client/views/termsofservice.html create mode 100644 src/server/config/setupTranslations.js create mode 100644 translation/changes.json create mode 100644 translation/en-US.toml create mode 100644 translation/fr-FR.toml diff --git a/.gitignore b/.gitignore index 604552189..cfff9ca83 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ logs dist # JetBrains IDEs -.idea/ \ No newline at end of file +.idea/ + +# PNPM +pnpm-lock.yaml \ No newline at end of file diff --git a/build.mjs b/build.mjs index 9755511a2..1b76d8732 100644 --- a/build.mjs +++ b/build.mjs @@ -7,6 +7,7 @@ import { readdir, cp as copy, rm as remove, readFile, writeFile } from "node:fs/promises"; import { minify } from "terser"; +import { injectScriptsIntoPlayEjs } from "./src/server/utility/HTMLScriptInjector.js" import { DEV_BUILD } from "./src/server/config/config.js"; /** @@ -45,6 +46,8 @@ if (DEV_BUILD){ recursive: true, force: true }); + // overwrite play.ejs by injecting all needed scripts into it: + await writeFile(`./dist/views/play.ejs`, injectScriptsIntoPlayEjs(), 'utf8'); } else{ // in prod mode, copy all clientside files over to dist, except for those contained in scripts await copy("./src/client", "./dist", { @@ -60,8 +63,8 @@ if (DEV_BUILD){ const clientScripts = await getExtFiles("./src/client/scripts", ".js"); clientFiles.push(...clientScripts.map(v => `scripts/${v}`)); - const filesToWrite = []; // array of output files that will need to be written - let gamecode = ""; // string containing all code in /game except for htmlscript.js + // string containing all code in /game except for htmlscript.js: + let gamecode = ""; for (const file of clientFiles) { // If the client script is htmlscript.js or not in scripts/game, then minify it and copy it over @@ -72,7 +75,7 @@ if (DEV_BUILD){ compress: true, // Enable compression sourceMap: false }); - filesToWrite.push(writeFile(`./dist/${file}`, minified.code, 'utf8')); + await writeFile(`./dist/${file}`, minified.code, 'utf8'); } // Collect the code of all js files in /game except for htmlscript.js: else{ @@ -86,8 +89,8 @@ if (DEV_BUILD){ compress: true, sourceMap: false }); - filesToWrite.push(writeFile(`./dist/scripts/game/app.js`, minifiedgame.code, 'utf8')); - - // finally, write to the needed files - await Promise.all(filesToWrite); + await writeFile(`./dist/scripts/game/app.js`, minifiedgame.code, 'utf8'); + + // overwrite play.ejs by injecting all needed scripts into it: + await writeFile(`./dist/views/play.ejs`, injectScriptsIntoPlayEjs(), 'utf8'); } \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/air.svg b/dev-utils/pieces/svg/fairy/compressed/air.svg index ff24564ed..8078fb56c 100644 --- a/dev-utils/pieces/svg/fairy/compressed/air.svg +++ b/dev-utils/pieces/svg/fairy/compressed/air.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/amazonsW.svg b/dev-utils/pieces/svg/fairy/compressed/amazonsW.svg index 1939b8567..0efebc61c 100644 --- a/dev-utils/pieces/svg/fairy/compressed/amazonsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/amazonsW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/archbishopsB.svg b/dev-utils/pieces/svg/fairy/compressed/archbishopsB.svg index 5faab63e0..9580b3b74 100644 --- a/dev-utils/pieces/svg/fairy/compressed/archbishopsB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/archbishopsB.svg @@ -1,36 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/archbishopsW.svg b/dev-utils/pieces/svg/fairy/compressed/archbishopsW.svg index 111461a3a..86ae918dd 100644 --- a/dev-utils/pieces/svg/fairy/compressed/archbishopsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/archbishopsW.svg @@ -1,36 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/camelsB.svg b/dev-utils/pieces/svg/fairy/compressed/camelsB.svg index d4d095aaa..8d0446fce 100644 --- a/dev-utils/pieces/svg/fairy/compressed/camelsB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/camelsB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/camelsW.svg b/dev-utils/pieces/svg/fairy/compressed/camelsW.svg index 14aa116f4..bc920fa28 100644 --- a/dev-utils/pieces/svg/fairy/compressed/camelsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/camelsW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/centaur-b.svg b/dev-utils/pieces/svg/fairy/compressed/centaursB.svg similarity index 95% rename from dev-utils/pieces/svg/fairy/compressed/centaur-b.svg rename to dev-utils/pieces/svg/fairy/compressed/centaursB.svg index 855b49bdc..aa3b00055 100644 --- a/dev-utils/pieces/svg/fairy/compressed/centaur-b.svg +++ b/dev-utils/pieces/svg/fairy/compressed/centaursB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/centaur-w.svg b/dev-utils/pieces/svg/fairy/compressed/centaursW.svg similarity index 94% rename from dev-utils/pieces/svg/fairy/compressed/centaur-w.svg rename to dev-utils/pieces/svg/fairy/compressed/centaursW.svg index fdd0551f9..90c34501a 100644 --- a/dev-utils/pieces/svg/fairy/compressed/centaur-w.svg +++ b/dev-utils/pieces/svg/fairy/compressed/centaursW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/chancellorsB.svg b/dev-utils/pieces/svg/fairy/compressed/chancellorsB.svg index dc7b340bd..f47869f11 100644 --- a/dev-utils/pieces/svg/fairy/compressed/chancellorsB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/chancellorsB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/chancellorsW.svg b/dev-utils/pieces/svg/fairy/compressed/chancellorsW.svg index b22002d7f..dcb45068c 100644 --- a/dev-utils/pieces/svg/fairy/compressed/chancellorsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/chancellorsW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/commonersB.svg b/dev-utils/pieces/svg/fairy/compressed/commonersB.svg index 09954492e..f538bcc9c 100644 --- a/dev-utils/pieces/svg/fairy/compressed/commonersB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/commonersB.svg @@ -1,105 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/commonersW.svg b/dev-utils/pieces/svg/fairy/compressed/commonersW.svg index 12f2b27a5..74e79e739 100644 --- a/dev-utils/pieces/svg/fairy/compressed/commonersW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/commonersW.svg @@ -1,94 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/dragonsB.svg b/dev-utils/pieces/svg/fairy/compressed/dragonsB.svg index 0c7bd3db1..02960cb0a 100644 --- a/dev-utils/pieces/svg/fairy/compressed/dragonsB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/dragonsB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/dragonsW.svg b/dev-utils/pieces/svg/fairy/compressed/dragonsW.svg index da29bf9f5..c4992b905 100644 --- a/dev-utils/pieces/svg/fairy/compressed/dragonsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/dragonsW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/dragosB.svg b/dev-utils/pieces/svg/fairy/compressed/dragosB.svg index fccda10dd..034d3fe5e 100644 --- a/dev-utils/pieces/svg/fairy/compressed/dragosB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/dragosB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/dragosW.svg b/dev-utils/pieces/svg/fairy/compressed/dragosW.svg index 3b239f1e7..b13dbdb91 100644 --- a/dev-utils/pieces/svg/fairy/compressed/dragosW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/dragosW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/giraffesB.svg b/dev-utils/pieces/svg/fairy/compressed/giraffesB.svg index 04978c838..410980b5e 100644 --- a/dev-utils/pieces/svg/fairy/compressed/giraffesB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/giraffesB.svg @@ -1,20 +1 @@ - - - - - Giraffe - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/giraffesW.svg b/dev-utils/pieces/svg/fairy/compressed/giraffesW.svg index 5fe7bff3a..ed467038f 100644 --- a/dev-utils/pieces/svg/fairy/compressed/giraffesW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/giraffesW.svg @@ -1,20 +1 @@ - - - - - Giraffe - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/guardsB.svg b/dev-utils/pieces/svg/fairy/compressed/guardsB.svg index 3aa248cc4..ef4528c58 100644 --- a/dev-utils/pieces/svg/fairy/compressed/guardsB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/guardsB.svg @@ -1,114 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/guardsW.svg b/dev-utils/pieces/svg/fairy/compressed/guardsW.svg index 362c65bcb..d03588d66 100644 --- a/dev-utils/pieces/svg/fairy/compressed/guardsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/guardsW.svg @@ -1,103 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/hawksB.svg b/dev-utils/pieces/svg/fairy/compressed/hawksB.svg index cae55c61e..f98f64e87 100644 --- a/dev-utils/pieces/svg/fairy/compressed/hawksB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/hawksB.svg @@ -1,104 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/hawksW.svg b/dev-utils/pieces/svg/fairy/compressed/hawksW.svg index a9f5461d8..1f044e055 100644 --- a/dev-utils/pieces/svg/fairy/compressed/hawksW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/hawksW.svg @@ -1,108 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/kelpiesB.svg b/dev-utils/pieces/svg/fairy/compressed/kelpiesB.svg index 19048085f..9235859dd 100644 --- a/dev-utils/pieces/svg/fairy/compressed/kelpiesB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/kelpiesB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/kelpiesW.svg b/dev-utils/pieces/svg/fairy/compressed/kelpiesW.svg index d926be68d..60b5a33d9 100644 --- a/dev-utils/pieces/svg/fairy/compressed/kelpiesW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/kelpiesW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/knightridersB.svg b/dev-utils/pieces/svg/fairy/compressed/knightridersB.svg index b38d2dedc..2c3b9ea18 100644 --- a/dev-utils/pieces/svg/fairy/compressed/knightridersB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/knightridersB.svg @@ -1,46 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/knightridersW.svg b/dev-utils/pieces/svg/fairy/compressed/knightridersW.svg index aefaefeac..d01c0c0fa 100644 --- a/dev-utils/pieces/svg/fairy/compressed/knightridersW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/knightridersW.svg @@ -1,42 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/obstaclesN.svg b/dev-utils/pieces/svg/fairy/compressed/obstaclesN.svg index 1f69b9ea5..c919e2110 100644 --- a/dev-utils/pieces/svg/fairy/compressed/obstaclesN.svg +++ b/dev-utils/pieces/svg/fairy/compressed/obstaclesN.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/rosesB.svg b/dev-utils/pieces/svg/fairy/compressed/rosesB.svg index 923f824cd..cf72c0f56 100644 --- a/dev-utils/pieces/svg/fairy/compressed/rosesB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/rosesB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/rosesW.svg b/dev-utils/pieces/svg/fairy/compressed/rosesW.svg index 97537065c..c3cfa1636 100644 --- a/dev-utils/pieces/svg/fairy/compressed/rosesW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/rosesW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/royalcentaursB.svg b/dev-utils/pieces/svg/fairy/compressed/royalcentaursB.svg index 82bdfed80..ecbd2ca89 100644 --- a/dev-utils/pieces/svg/fairy/compressed/royalcentaursB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/royalcentaursB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/royalcentaursW.svg b/dev-utils/pieces/svg/fairy/compressed/royalcentaursW.svg index bb713cbf9..95933b5c1 100644 --- a/dev-utils/pieces/svg/fairy/compressed/royalcentaursW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/royalcentaursW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/royalqueensB.svg b/dev-utils/pieces/svg/fairy/compressed/royalqueensB.svg index 35ae6d86f..c6e7b382b 100644 --- a/dev-utils/pieces/svg/fairy/compressed/royalqueensB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/royalqueensB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/royalqueensW.svg b/dev-utils/pieces/svg/fairy/compressed/royalqueensW.svg index c27df5f75..f3a9f5561 100644 --- a/dev-utils/pieces/svg/fairy/compressed/royalqueensW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/royalqueensW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/unicornosB.svg b/dev-utils/pieces/svg/fairy/compressed/unicornosB.svg index 9af66c523..c41a16178 100644 --- a/dev-utils/pieces/svg/fairy/compressed/unicornosB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/unicornosB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/unicornosW.svg b/dev-utils/pieces/svg/fairy/compressed/unicornosW.svg index 6348915fc..5c71e9f8e 100644 --- a/dev-utils/pieces/svg/fairy/compressed/unicornosW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/unicornosW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/unicornsB.svg b/dev-utils/pieces/svg/fairy/compressed/unicornsB.svg index 41bd9fae2..aa762864b 100644 --- a/dev-utils/pieces/svg/fairy/compressed/unicornsB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/unicornsB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/unicornsW.svg b/dev-utils/pieces/svg/fairy/compressed/unicornsW.svg index 7fc0d620c..cd0a148a6 100644 --- a/dev-utils/pieces/svg/fairy/compressed/unicornsW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/unicornsW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/zebrasB.svg b/dev-utils/pieces/svg/fairy/compressed/zebrasB.svg index 55795ffae..91b7dc92c 100644 --- a/dev-utils/pieces/svg/fairy/compressed/zebrasB.svg +++ b/dev-utils/pieces/svg/fairy/compressed/zebrasB.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/compressed/zebrasW.svg b/dev-utils/pieces/svg/fairy/compressed/zebrasW.svg index 173afb0df..96e3527d3 100644 --- a/dev-utils/pieces/svg/fairy/compressed/zebrasW.svg +++ b/dev-utils/pieces/svg/fairy/compressed/zebrasW.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/dev-utils/pieces/svg/fairy/uncompressed/centaur-b.svg b/dev-utils/pieces/svg/fairy/uncompressed/centaursB.svg similarity index 100% rename from dev-utils/pieces/svg/fairy/uncompressed/centaur-b.svg rename to dev-utils/pieces/svg/fairy/uncompressed/centaursB.svg diff --git a/dev-utils/pieces/svg/fairy/uncompressed/centaur-w.svg b/dev-utils/pieces/svg/fairy/uncompressed/centaursW.svg similarity index 100% rename from dev-utils/pieces/svg/fairy/uncompressed/centaur-w.svg rename to dev-utils/pieces/svg/fairy/uncompressed/centaursW.svg diff --git a/dev-utils/pieces/svg/standard/compressed/bishopsB.svg b/dev-utils/pieces/svg/standard/compressed/bishopsB.svg new file mode 100644 index 000000000..e13117920 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/bishopsB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/bishopsW.svg b/dev-utils/pieces/svg/standard/compressed/bishopsW.svg new file mode 100644 index 000000000..a1e09ab44 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/bishopsW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/kingsB.svg b/dev-utils/pieces/svg/standard/compressed/kingsB.svg new file mode 100644 index 000000000..11d092168 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/kingsB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/kingsW.svg b/dev-utils/pieces/svg/standard/compressed/kingsW.svg new file mode 100644 index 000000000..a6790ebbb --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/kingsW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/knightsB.svg b/dev-utils/pieces/svg/standard/compressed/knightsB.svg new file mode 100644 index 000000000..3efed6a80 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/knightsB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/knightsW.svg b/dev-utils/pieces/svg/standard/compressed/knightsW.svg new file mode 100644 index 000000000..9c4f7b580 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/knightsW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/pawnsB.svg b/dev-utils/pieces/svg/standard/compressed/pawnsB.svg new file mode 100644 index 000000000..7d4e9c6d8 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/pawnsB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/pawnsW.svg b/dev-utils/pieces/svg/standard/compressed/pawnsW.svg new file mode 100644 index 000000000..6c6335de6 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/pawnsW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/queensB.svg b/dev-utils/pieces/svg/standard/compressed/queensB.svg new file mode 100644 index 000000000..3733dd019 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/queensB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/queensW.svg b/dev-utils/pieces/svg/standard/compressed/queensW.svg new file mode 100644 index 000000000..5e988a97a --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/queensW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/rooksB.svg b/dev-utils/pieces/svg/standard/compressed/rooksB.svg new file mode 100644 index 000000000..9e710515d --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/rooksB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/compressed/rooksW.svg b/dev-utils/pieces/svg/standard/compressed/rooksW.svg new file mode 100644 index 000000000..48a4a27b8 --- /dev/null +++ b/dev-utils/pieces/svg/standard/compressed/rooksW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev-utils/pieces/svg/standard/Chess_bdt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_bdt45.svg similarity index 99% rename from dev-utils/pieces/svg/standard/Chess_bdt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_bdt45.svg index b6a800614..e88c40606 100644 --- a/dev-utils/pieces/svg/standard/Chess_bdt45.svg +++ b/dev-utils/pieces/svg/standard/uncompressed/Chess_bdt45.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/dev-utils/pieces/svg/standard/Chess_blt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_blt45.svg similarity index 99% rename from dev-utils/pieces/svg/standard/Chess_blt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_blt45.svg index 18f034d52..3a8eaa286 100644 --- a/dev-utils/pieces/svg/standard/Chess_blt45.svg +++ b/dev-utils/pieces/svg/standard/uncompressed/Chess_blt45.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/dev-utils/pieces/svg/standard/Chess_kdt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_kdt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_kdt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_kdt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_klt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_klt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_klt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_klt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_ndt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_ndt45.svg similarity index 98% rename from dev-utils/pieces/svg/standard/Chess_ndt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_ndt45.svg index 203c6fea8..04541a865 100644 --- a/dev-utils/pieces/svg/standard/Chess_ndt45.svg +++ b/dev-utils/pieces/svg/standard/uncompressed/Chess_ndt45.svg @@ -1,22 +1,22 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/dev-utils/pieces/svg/standard/Chess_nlt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_nlt45.svg similarity index 98% rename from dev-utils/pieces/svg/standard/Chess_nlt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_nlt45.svg index 92feeee00..a5f31c6a8 100644 --- a/dev-utils/pieces/svg/standard/Chess_nlt45.svg +++ b/dev-utils/pieces/svg/standard/uncompressed/Chess_nlt45.svg @@ -1,19 +1,19 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/dev-utils/pieces/svg/standard/Chess_pdt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_pdt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_pdt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_pdt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_plt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_plt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_plt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_plt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_qdt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_qdt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_qdt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_qdt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_qlt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_qlt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_qlt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_qlt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_rdt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_rdt45.svg similarity index 100% rename from dev-utils/pieces/svg/standard/Chess_rdt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_rdt45.svg diff --git a/dev-utils/pieces/svg/standard/Chess_rlt45.svg b/dev-utils/pieces/svg/standard/uncompressed/Chess_rlt45.svg similarity index 97% rename from dev-utils/pieces/svg/standard/Chess_rlt45.svg rename to dev-utils/pieces/svg/standard/uncompressed/Chess_rlt45.svg index ac03578ec..0574ca673 100644 --- a/dev-utils/pieces/svg/standard/Chess_rlt45.svg +++ b/dev-utils/pieces/svg/standard/uncompressed/Chess_rlt45.svg @@ -1,25 +1,25 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/docs/NAVIGATING.md b/docs/NAVIGATING.md index d2fbcd852..98d51e38c 100644 --- a/docs/NAVIGATING.md +++ b/docs/NAVIGATING.md @@ -13,7 +13,7 @@ Everything starts running from [server.js](../src/server/server.js)! This configures and starts our http, https, and websocket servers, and it cleans up on closing. -[src/server/game/](../src/server/game/) contains the server-side code for running online play, including the [invites-manager](../src/server/game/invitesmanager.js) and the [game-manager](../src/server/game/gamemanager.js). +[src/server/game](../src/server/game/) contains the server-side code for running online play, including the [invites-manager](../src/server/game/invitesmanager.js) and the [game-manager](../src/server/game/gamemanager.js). Both of these managers use websockets to broadcast changes out to the clients in real-time. @@ -24,13 +24,28 @@ The websocket server code is located [here](../src/server/wsserver.js). [src/client](../src/client/) contains all clientside files of the website. -It has subfolders for all the HTML, CSS, JavaScript, sound and image files of the website. +It has subfolders for all the EJS, CSS, JavaScript, sound and image files of the website. -[src/client/views](../src/client/views) contains all our HTML documents. +[src/client/views](../src/client/views) contains all our EJS documents. -The routers that actually send these htmls to the client are located in [src/server/routes/root.js](../src/server/routes/root.js). +The routers that actually send these as htmls to the client are located in [src/server/routes/root.js](../src/server/routes/root.js). -[src/client/scripts/game/](../src/client/scripts/game/) contains all our javascipt code for running the game in the `/play` page in the user's browser. +[src/client/scripts/game](../src/client/scripts/game/) contains all our javascipt code for running the game in the `/play` page in the user's browser. + +The main script is [main.js](../src/client/scripts/game/main.js), which initiates the WebGL context and input listeners, and runs the main game loop. + + +## Translations ## + +This repository uses [i18next](https://www.npmjs.com/package/i18next) to provide translations of the website into different languages. + +[translation](../translation) contains a [TOML](https://toml.io/) file with translated text for each supported language - read more in the [translation guide](./TRANSLATIONS.md). + +The EJS files in [src/client/views](../src/client/views) get converted into html files for each supported language during deployment to `dist`. + +The translated text in each EJS file is directly inserted into the corresponding html file during deployment. + +The translated text in each clientside javacript file is stored in the `translations` object, which is inserted directly below the head tag of each EJS file. ## Accounts ## @@ -72,4 +87,4 @@ Connecting more devices to the web server, other than the machine that is hostin ## Conclusion ## -Those are the basics! [Feel free to ask](https://discord.com/channels/1114425729569017918/1115358966642393190) in the discord for more pointers on where you can find certain implementations, or what the purpose of a script does! +Those are the basics! [Feel free to ask](https://discord.com/channels/1114425729569017918/1115358966642393190) in the discord for more pointers on where you can find certain implementations, or what the purpose of a script is! diff --git a/docs/TRANSLATIONS.md b/docs/TRANSLATIONS.md new file mode 100644 index 000000000..b729510e3 --- /dev/null +++ b/docs/TRANSLATIONS.md @@ -0,0 +1,59 @@ +# Translation guide # + +This guide will walk you through the process of creating translations for [InfiniteChess.org](https://www.infinitechess.org). What it won't tell you is how to setup your workspace, please refer to [SETUP.md](./SETUP.md) for that. It is assumed that you have gone through it (you can ignore step 5). + +## Navigaton ## + +Anything that matters to you as a translator should be located in the [translation](../translation/) directory. Translation files are stored in TOML format (you can read more about its syntax [here](https://toml.io/)). Generally, it is a very aproachable format, and you only need to understand the absolute basics of it, which are explained below. + +## Translation files ## + +### Name ### + +Each file is named after its language [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag). BCP 47 tags are composed of this format (notice the capitalization): + +`lng-(script)-REGION-(extensions)` + +For example, `en-US` for American English, `sv` for Swedish, `zh-Hant-HK` for Chinese spoken in Hong Kong written in traditional script. + +You should name your file this way and only this way, otherwise it won't be correctly detected. + +### Content ### + +Translation files in TOML format consist of keys and values, table headers and comments, like this: + +```toml +[table-header] +# Comment +key1 = "value1" +key2 = "value2" +``` + +> [!IMPORTANT] +> **You should only change values and comments. Please, leave everything else unmodified when translating!**. + +## Translation process ## + +In case you are translating a language that is currently not present in the project, you can start the process by copying [en-US.toml](../translation/en-US.toml) and renaming it as described above. If you are updating an existing language, the only thing you need to do is to update the `version` variable on top of your TOML document to the value of the `version` variable in [en-US.toml](../translation/en-US.toml). + +> [!IMPORTANT] +> You should always use [en-US.toml](../translation/en-US.toml) as a reference. It is the only file that is up to date and comes straight from the developers. Do not use any other files! + +Then you can start a test server with `npx nodemon` and start translating. If you head to your browser at address `https://localhost:3443` the website should be there and it should automatically update as you make your changes (after reloading the page). Make sure you have selected the language that you are editing in the website's UI. There is a footer with a language selection dropdown at the bottom of almost every page. + +In case you are updating an existing language and you aren't sure what has changed since the last update, you can view changes of `en-US.toml` [here](https://github.com/Infinite-Chess/infinitechess.org/commits/main/translation/en-US.toml). + +> [!IMPORTANT] +> If there is an HTML tag in the value you want to translate, do not modify it! +> +> Example of an HTML tag: +> ```html +> Hello World +> ``` +> In this example you should only change the words *Hello World*. + +When you are finished, you should open a pull request as described in [SETUP.md](./SETUP.md). + +## Conclusion ## + +Thank you for your contribution! In case you have any troubles or questions, you can join [the discord](https://discord.gg/NFWFGZeNh5). diff --git a/nodemon.json b/nodemon.json index 2ed117dd7..ffe978e51 100644 --- a/nodemon.json +++ b/nodemon.json @@ -4,5 +4,5 @@ "./dist/**/*" ], "exec": "npm run build && npm run start", - "ext": "js,html,css,jpg,png,gif,mp3,wav,webp" + "ext": "js,html,css,jpg,png,gif,mp3,wav,webp,toml,ejs,json" } diff --git a/package-lock.json b/package-lock.json index 9991e773a..a2dffae33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,20 @@ "cors": "^2.8.5", "date-fns": "^2.23.0", "dotenv": "^16.0.3", + "ejs": "^3.1.10", "express": "^4.18.2", "glob": "^11.0.0", + "i18next": "^23.12.1", + "i18next-http-middleware": "^3.6.0", "jsonwebtoken": "^9.0.2", "node-forge": "^1.3.1", "nodemailer": "^6.8.0", "nodemon": "^3.1.4", "proper-lockfile": "^4.1.2", + "smol-toml": "^1.2.2", "uuid": "^8.3.2", - "ws": "^8.16.0" + "ws": "^8.16.0", + "xss": "^1.0.15" }, "devDependencies": { "@types/node": "^20.14.10", @@ -347,6 +352,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -456,6 +467,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -514,8 +577,7 @@ "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -596,6 +658,12 @@ "node": ">= 8" } }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", + "license": "MIT" + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -694,6 +762,21 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -788,6 +871,36 @@ "node": ">= 0.6" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1128,6 +1241,35 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/i18next": { + "version": "23.12.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.1.tgz", + "integrity": "sha512-l4y291ZGRgUhKuqVSiqyuU2DDzxKStlIWSaoNBR4grYmh0X+pRYbFpTMs3CnJ5ECKbOI8sQcJ3PbTUfLgPRaMA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-middleware": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.6.0.tgz", + "integrity": "sha512-pLyTOC8Dzj83byN0s4hd/i/Ewg6T36YjMrc+Zfnqz2Ca0G5ab9IPvPR8xZqr6TS0s/ZtPs2MZucDkWgqoRmNXA==", + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1235,6 +1377,24 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1942,6 +2102,15 @@ "node": ">=10" } }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2318,6 +2487,22 @@ } } }, + "node_modules/xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "license": "MIT", + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 0290600b3..333539f9a 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,20 @@ "cors": "^2.8.5", "date-fns": "^2.23.0", "dotenv": "^16.0.3", + "ejs": "^3.1.10", "express": "^4.18.2", "glob": "^11.0.0", + "i18next": "^23.12.1", + "i18next-http-middleware": "^3.6.0", "jsonwebtoken": "^9.0.2", "node-forge": "^1.3.1", "nodemailer": "^6.8.0", "nodemon": "^3.1.4", "proper-lockfile": "^4.1.2", + "smol-toml": "^1.2.2", "uuid": "^8.3.2", - "ws": "^8.16.0" + "ws": "^8.16.0", + "xss": "^1.0.15" }, "devDependencies": { "@types/node": "^20.14.10", diff --git a/readme.md b/readme.md index da03c59a8..9642ede85 100644 --- a/readme.md +++ b/readme.md @@ -2,12 +2,14 @@ [InfiniteChess.org](https://www.infinitechess.org) is a free and ad-less website for playing all kinds of chess variants on an infinite, boundless board. -What began as an indie project by [Naviary](https://www.youtube.com/@Naviary) in 2022 has been growing ever since. Inspired by the concepts of chess and infinity, he set out to craft a space where anyone can experience the thrills of freedom and exploration, reimagined within the familiar world of chess. No more limits, tear down the edges of the board, here we come! +What began as an indie project by [Naviary](https://www.youtube.com/@Naviary) in 2022 has been growing ever since. Inspired by the concepts of chess and infinity, he set out to craft a space where anyone can experience the thrills of freedom and exploration, reimagined within the familiar world of chess. No more limits, tear down the edges of the board - here we come! ## Contributing This project is open source! If you have a feature idea you want to try implementing, or you have skills in html, css, javascript, or Node, we welcome contributions! To get started collaborating, please read the [Setup Guide](./docs/SETUP.md)! +In case you want to help us with translating the website to other languages, please read the [Translation Guide](./docs/TRANSLATIONS.md). + We are still far off from our vision. We refuse to stop until many crucial features of this universe are built: - Truly infinite move distance - Board Editor diff --git a/src/client/css/createaccount.css b/src/client/css/createaccount.css index 6502f8e16..ac17de8f8 100644 --- a/src/client/css/createaccount.css +++ b/src/client/css/createaccount.css @@ -164,21 +164,6 @@ input[type='submit'].ready:focus { } - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - .currPage { background-color: rgb(236, 236, 236); } diff --git a/src/client/css/credits.css b/src/client/css/credits.css index be3404a6d..d352c980c 100644 --- a/src/client/css/credits.css +++ b/src/client/css/credits.css @@ -94,21 +94,6 @@ h3 { /* Thank you! */ } - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - a { color: black; } diff --git a/src/client/css/footer.css b/src/client/css/footer.css new file mode 100644 index 000000000..2d8155615 --- /dev/null +++ b/src/client/css/footer.css @@ -0,0 +1,26 @@ +footer { + text-align: center; + padding: 10px 0; +} + +footer a { + display: inline-block; + color: rgb(207, 207, 207); + margin: 10px 10px; + text-decoration: underline; +} + +footer label { + font-size: 16px; + color: rgb(207, 207, 207); + margin: 10px 2px 10px 10px; +} + +footer select { + width: min-content; + color: rgb(207, 207, 207); + font-size: 1em; + background-color: rgba(0,0,0,0); + margin: 10px 10px 10px 0px; + cursor: pointer; +} \ No newline at end of file diff --git a/src/client/css/index.css b/src/client/css/index.css index b22c2aa40..9bf28e43b 100644 --- a/src/client/css/index.css +++ b/src/client/css/index.css @@ -145,22 +145,6 @@ iframe { text-shadow: -2px 0px 0.6em rgba(0, 38, 255, 0.2); } - - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - .currPage { background-color: rgb(236, 236, 236); } diff --git a/src/client/css/login.css b/src/client/css/login.css index be3e76d67..b1ade5696 100644 --- a/src/client/css/login.css +++ b/src/client/css/login.css @@ -160,21 +160,6 @@ input[type='submit'].ready:focus { } - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - .currPage { background-color: rgb(236, 236, 236); } diff --git a/src/client/css/member.css b/src/client/css/member.css index dd07f4158..77a289add 100644 --- a/src/client/css/member.css +++ b/src/client/css/member.css @@ -196,21 +196,6 @@ main { } - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - .hidden { display: none; } diff --git a/src/client/css/news.css b/src/client/css/news.css index fab5808c7..21f5b478d 100644 --- a/src/client/css/news.css +++ b/src/client/css/news.css @@ -96,22 +96,6 @@ main { } - - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - .currPage { background-color: rgb(236, 236, 236); } diff --git a/src/client/css/termsofservice.css b/src/client/css/termsofservice.css index 75a63b510..caf06ab45 100644 --- a/src/client/css/termsofservice.css +++ b/src/client/css/termsofservice.css @@ -94,21 +94,6 @@ h3 { } - -footer { - text-align: center; - padding: 10px 0; -} - -footer a { - display: inline-block; - color: rgb(207, 207, 207); - margin: 10px 10px; - text-decoration: underline; -} - - - a { color: black; -webkit-tap-highlight-color: rgba(0, 0, 0, 0.099); diff --git a/src/client/scripts/createaccount.js b/src/client/scripts/createaccount.js index 9bc27b773..ed1e4987d 100644 --- a/src/client/scripts/createaccount.js +++ b/src/client/scripts/createaccount.js @@ -37,11 +37,11 @@ element_usernameInput.addEventListener('input', (event) => { // When username fi } if (lengthError && formatError) { // Change error message - usernameError.textContent = 'Username must be atleast 3 characters long, and only contain letters A-Z and numbers 0-9'; + usernameError.textContent = translations["js-username_specs"]; } else if (lengthError) { - usernameError.textContent = 'Username must be atleast 3 characters long'; + usernameError.textContent = translations["js-username_tooshort"]; } else if (formatError) { - usernameError.textContent = 'Username must only contain letters A-Z and numbers 0-9'; + usernameError.textContent = translations["js-username_wrongenc"]; } updateSubmitButton(); @@ -64,7 +64,10 @@ element_usernameInput.addEventListener('focusout', (event) => { // Check usernam // Reset variable because it now exists. usernameError = document.getElementById("usernameerror"); - usernameError.textContent = result.reason; + // 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(); }); }) @@ -95,7 +98,7 @@ element_emailInput.addEventListener('input', (event) => { // When email field ch } if (error) { - emailError.textContent = 'This is not a valid email'; + emailError.textContent = translations["js-email_invalid"]; } updateSubmitButton(); @@ -116,7 +119,7 @@ element_emailInput.addEventListener('focusout', (event) => { // Check email avai // Reset variable because it now exists. const emailError = document.getElementById("emailerror"); - emailError.textContent = 'This email is already in use'; + emailError.textContent = translations["js-email_inuse"]; updateSubmitButton(); } }); @@ -150,13 +153,13 @@ element_passwordInput.addEventListener('input', (event) => { // When password fi } if (formatError) { - passwordError.textContent = 'Password is in an incorrect format'; + passwordError.textContent = translations["js-pwd_incorrect_format"]; } else if (shortError) { - passwordError.textContent = 'Password must be 6+ characters long'; + passwordError.textContent = translations["js-pwd_too_short"]; } else if (longError) { - passwordError.textContent = "Password can't be over 72 characters long"; + passwordError.textContent = translations["js-pwd_too_long"]; } else if (containsPasswordError) { - passwordError.textContent = "Password must not be 'password'"; + passwordError.textContent = translations["js-pwd_not_pwd"]; } updateSubmitButton(); diff --git a/src/client/scripts/game/chess/checkdetection.js b/src/client/scripts/game/chess/checkdetection.js index 8a2412027..ad66dafc7 100644 --- a/src/client/scripts/game/chess/checkdetection.js +++ b/src/client/scripts/game/chess/checkdetection.js @@ -169,7 +169,7 @@ const checkdetection = (function(){ const thisPieceLegalSlide = legalmoves.slide_CalcLegalLimit(line, direction, moveset, thisPiece.coords, thisPieceColor) if (!thisPieceLegalSlide) continue; // This piece can't move in the direction of this line, NEXT piece! - if (!legalmoves.doesSlidingNetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords)) continue; // This piece can't slide so far as to reach us, NEXT piece! + if (!legalmoves.doesSlidingMovesetContainSquare(thisPieceLegalSlide, direction, thisPiece.coords, coords)) continue; // This piece can't slide so far as to reach us, NEXT piece! // This piece is attacking this square! diff --git a/src/client/scripts/game/chess/copypastegame.js b/src/client/scripts/game/chess/copypastegame.js index 36984a589..8caf0c624 100644 --- a/src/client/scripts/game/chess/copypastegame.js +++ b/src/client/scripts/game/chess/copypastegame.js @@ -30,7 +30,7 @@ const copypastegame = (function(){ const shortformat = formatconverter.LongToShort_Format(primedGamefile, { compact_moves: 1, make_new_lines: false, specifyPosition }); main.copyToClipboard(shortformat) - statustext.showStatus('Copied game to clipboard!') + statustext.showStatus(translations["copypaste"]["copied_game"]) } /** @@ -83,17 +83,17 @@ const copypastegame = (function(){ event = event || window.event; // Make sure we're not in a public match - if (onlinegame.areInOnlineGame() && !onlinegame.getIsPrivate()) return statustext.showStatus('Cannot paste game in a public match!') + if (onlinegame.areInOnlineGame() && !onlinegame.getIsPrivate()) return statustext.showStatus(translations["copypaste"]["cannot_paste_in_public"]) // Make sure it's legal in a private match - if (onlinegame.areInOnlineGame() && onlinegame.getIsPrivate() && game.getGamefile().moves.length > 0) return statustext.showStatus('Cannot paste game after moves are made!') + if (onlinegame.areInOnlineGame() && onlinegame.getIsPrivate() && game.getGamefile().moves.length > 0) return statustext.showStatus(translations["copypaste"]["cannot_paste_after_moves"]) // Do we have clipboard permission? let clipboard; try { clipboard = await navigator.clipboard.readText() } catch (error) { - const message = "Clipboard permission denied. This might be your browser." + const message = translations["copypaste"]["clipboard_denied"] return statustext.showStatus((message + "\n" + error), true) } @@ -106,7 +106,7 @@ const copypastegame = (function(){ longformat = formatconverter.ShortToLong_Format(clipboard, true, true) } catch(e) { console.error(e); - statustext.showStatus("Clipboard is not in valid ICN notation.", true) + statustext.showStatus(translations["copypaste"]["clipboard_invalid"], true) return; } } @@ -140,7 +140,7 @@ const copypastegame = (function(){ if (!longformat.metadata) longformat.metadata = {}; if (!longformat.turn) longformat.turn = 'white'; if (!longformat.fullMove) longformat.fullMove = 1; - if (!longformat.startingPosition && !longformat.metadata.Variant) { statustext.showStatus("Game needs to specify either the 'Variant' metadata, or 'startingPosition' property.", true); return false; } + if (!longformat.startingPosition && !longformat.metadata.Variant) { statustext.showStatus(translations["copypaste"]["game_needs_to_specify"], true); return false; } if (longformat.startingPosition && !longformat.specialRights) longformat.specialRights = {}; if (!longformat.gameRules) longformat.gameRules = variant.getBareMinimumGameRules(); longformat.gameRules.winConditions = longformat.gameRules.winConditions || variant.getDefaultWinConditions(); @@ -157,7 +157,7 @@ const copypastegame = (function(){ const winCondition = winConditions.white[i]; if (wincondition.validWinConditions.includes(winCondition)) continue; // Not valid - statustext.showStatus(`White has an invalid win condition "${winCondition}".`, true) + statustext.showStatus(`${translations["copypaste"]["invalid_wincon_white"]} "${winCondition}".`, true) return false; } @@ -165,7 +165,7 @@ const copypastegame = (function(){ const winCondition = winConditions.black[i]; if (wincondition.validWinConditions.includes(winCondition)) continue; // Not valid - statustext.showStatus(`Black has an invalid win condition "${winCondition}".`, true) + statustext.showStatus(`${translations["copypaste"]["invalid_wincon_black"]} "${winCondition}".`, true) return false; } @@ -177,7 +177,7 @@ const copypastegame = (function(){ * @param {Object} longformat - The game in longformat, or primed for copying. This is NOT the gamefile, we'll need to use the gamefile constructor. */ function pasteGame(longformat) { // game: { startingPosition (key-list), patterns, promotionRanks, moves, gameRules } - console.log("Pasting game...") + console.log(translations["copypaste"]["pasting_game"]) /** longformat properties: * metadata @@ -233,13 +233,13 @@ const copypastegame = (function(){ const newGamefile = new gamefile(longformat.metadata, { moves: longformat.moves, variantOptions }) // What is the warning message if pasting in a private match? - const privateMatchWarning = onlinegame.getIsPrivate() ? ` Pasting a game in a private match will cause a desync if your opponent doesn't do the same!` : ""; + const privateMatchWarning = onlinegame.getIsPrivate() ? ` ${translations["copypaste"]["pasting_in_private"]}` : ""; // Change win condition of there's too many pieces! let tooManyPieces = false; if (newGamefile.startSnapshot.pieceCount >= gamefileutility.pieceCountToDisableCheckmate) { // TOO MANY pieces! tooManyPieces = true; - statustext.showStatus(`Piece count ${newGamefile.startSnapshot.pieceCount} exceeded ${gamefileutility.pieceCountToDisableCheckmate}! Changed checkmate win conditions to royalcapture, and toggled off icon rendering. Hit 'P' to re-enable (not recommended).${privateMatchWarning}`, false, 1.5) + statustext.showStatus(`${translations["copypaste"]["piece_count"]} ${newGamefile.startSnapshot.pieceCount} ${translations["copypaste"]["exceeded"]} ${gamefileutility.pieceCountToDisableCheckmate}! ${translations["copypaste"]["changed_wincon"]}${privateMatchWarning}`, false, 1.5) // Make win condition from checkmate to royal capture const whiteHasCheckmate = newGamefile.gameRules.winConditions.white.includes('checkmate'); @@ -256,14 +256,14 @@ const copypastegame = (function(){ // Only print "Loaded game!" if we haven't already shown a different status message cause of too many pieces if (!tooManyPieces) { - const message = `Loaded game from clipboard!${privateMatchWarning}` + const message = `${translations["copypaste"]["loaded_from_clipboard"]}${privateMatchWarning}` statustext.showStatus(message) } game.unloadGame(); game.loadGamefile(newGamefile); - console.log("Loaded game!") + console.log(translations["copypaste"]["loaded"]) } /** @@ -273,7 +273,7 @@ const copypastegame = (function(){ */ function verifyGamerules(gameRules) { if (gameRules.slideLimit !== undefined && typeof gameRules.slideLimit !== 'number') { - statustext.showStatus(`slideLimit gamerule must be a number. Received "${gameRules.slideLimit}"`, true) + statustext.showStatus(`${translations["copypaste"]["slidelimit_not_number"]} "${gameRules.slideLimit}"`, true) return false; } return true; diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index d37924f18..4792bd814 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -163,24 +163,24 @@ const legalmoves = (function(){ * Shortens the moveset by pieces that block it's path. * @param {Piece[]} line - The list of pieces on this line * @param {number[]} direction - The direction of the line: `[dx,dy]` - * @param {number[]} slidinget - How far this piece can slide in this direction: `[left,right]`. If the line is vertical, this is `[bottom,top]` - * @param {number[]} coords - The coordinates of the piece with the specified slidinget. + * @param {number[]} slideMoveset - How far this piece can slide in this direction: `[left,right]`. If the line is vertical, this is `[bottom,top]` + * @param {number[]} coords - The coordinates of the piece with the specified slideMoveset. * @param {string} color - The color of friendlies */ - function slide_CalcLegalLimit (line, direction, slidinget, coords, color) { + function slide_CalcLegalLimit (line, direction, slideMoveset, coords, color) { - if (!slidinget) return; // Return undefined if there is no slide moveset + if (!slideMoveset) return; // Return undefined if there is no slide moveset // The default slide is [-Infinity, Infinity], change that if there are any pieces blocking our path! // For most we'll be comparing the x values, only exception is the vertical lines. const axis = direction[0] == 0 ? 1 : 0 - const limit = math.copyCoords(slidinget); + const limit = math.copyCoords(slideMoveset); // Iterate through all pieces on same line for (let i = 0; i < line.length; i++) { // What are the coords of this piece? const thisPiece = line[i] // { type, coords } - const thisPieceSteps = Math.floor((thisPiece.coords[axis]-coords[axis])/direction[axis]) + const thisPieceSteps = math.getLineSteps(direction, coords, thisPiece.coords) const thisPieceColor = math.getPieceColorFromType(thisPiece.type) const isFriendlyPiece = color === thisPieceColor const isVoid = thisPiece.type === 'voidsN'; @@ -241,7 +241,7 @@ const legalmoves = (function(){ let clickedCoordsLine = organizedlines.getKeyFromLine(line,endCoords); if (!limits || selectedPieceLine !== clickedCoordsLine) continue; - if (!doesSlidingNetContainSquare(limits, line, startCoords, endCoords)) continue; + if (!doesSlidingMovesetContainSquare(limits, line, startCoords, endCoords)) continue; return true; } return false; @@ -349,21 +349,18 @@ const legalmoves = (function(){ // This requires coords be on the same line as the sliding moveset. /** - * Tests if the piece's precalculated slidinget is able to reach the provided coords. + * Tests if the piece's precalculated slideMoveset is able to reach the provided coords. * ASSUMES the coords are on the direction of travel!!! - * @param {number[]} slidinget - The distance the piece can move along this line: `[left,right]`. If the line is vertical, this will be `[bottom,top]`. + * @param {number[]} slideMoveset - The distance the piece can move along this line: `[left,right]`. If the line is vertical, this will be `[bottom,top]`. * @param {number[]} direction - The direction of the line: `[dx,dy]` * @param {number[]} pieceCoords - The coordinates of the piece with the provided sliding net * @param {number[]} coords - The coordinates we want to know if they can reach. * @returns {boolean} true if the piece is able to slide to the coordinates */ - function doesSlidingNetContainSquare(slidinget, direction, pieceCoords, coords) { - const axis = direction[0] === 0 ? 1 : 0 - const coordMag = coords[axis]; - const min = slidinget[0] * direction[axis] + pieceCoords[axis] - const max = slidinget[1] * direction[axis] + pieceCoords[axis] + function doesSlidingMovesetContainSquare(slideMoveset, direction, pieceCoords, coords) { + const step = math.getLineSteps(direction, pieceCoords, coords) - return coordMag >= min && coordMag <= max; + return step >= slideMoveset[0] && step <= slideMoveset[1]; } /** @@ -390,7 +387,7 @@ const legalmoves = (function(){ getPieceMoveset, calculate, checkIfMoveLegal, - doesSlidingNetContainSquare, + doesSlidingMovesetContainSquare, hasAtleast1Move, slide_CalcLegalLimit, isOpponentsMoveLegal diff --git a/src/client/scripts/game/gui/gui.js b/src/client/scripts/game/gui/gui.js index 9635587bd..d28367b37 100644 --- a/src/client/scripts/game/gui/gui.js +++ b/src/client/scripts/game/gui/gui.js @@ -40,7 +40,7 @@ const gui = (function(){ function callback_featurePlanned(event) { event = event || window.event; - statustext.showStatus("This feature is planned!") + statustext.showStatus(translations["planned_feature"]) } function makeOverlayUnselectable() { diff --git a/src/client/scripts/game/gui/guigameinfo.js b/src/client/scripts/game/gui/guigameinfo.js index f6eb0acb2..7e6eec1a9 100644 --- a/src/client/scripts/game/gui/guigameinfo.js +++ b/src/client/scripts/game/gui/guigameinfo.js @@ -32,8 +32,8 @@ const guigameinfo = (function(){ const white = gameOptions.metadata.White; const black = gameOptions.metadata.Black; // If you are a guest, then we want your name to be "(You)" instead of "(Guest)" - element_playerWhite.textContent = onlinegame.areWeColor('white') && white === "(Guest)" ? "(You)" : white; - element_playerBlack.textContent = onlinegame.areWeColor('black') && black === "(Guest)" ? "(You)" : black; + element_playerWhite.textContent = onlinegame.areWeColor('white') && white === translations["guest_indicator"] ? translations["you_indicator"] : white; + element_playerBlack.textContent = onlinegame.areWeColor('black') && black === translations["guest_indicator"] ? translations["you_indicator"] : black; style.revealElement(element_playerWhite) style.revealElement(element_playerBlack) } @@ -52,8 +52,8 @@ const guigameinfo = (function(){ let textContent = ""; if (onlinegame.areInOnlineGame()) { const ourTurn = onlinegame.isItOurTurn(gamefile) - textContent = ourTurn ? "Your move" : "Their move"; - } else textContent = color === "white" ? "White to move" : "Black to move" + textContent = ourTurn ? translations["your_move"] : translations["their_move"]; + } else textContent = color === "white" ? translations["white_to_move"] : translations["black_to_move"] element_whosturn.textContent = textContent; @@ -77,60 +77,60 @@ const guigameinfo = (function(){ if (onlinegame.areInOnlineGame()) { - if (onlinegame.areWeColor(victor)) element_whosturn.textContent = condition === 'checkmate' ? "You win by checkmate!" - : condition === 'time' ? "You win on time!" - : condition === 'resignation' ? "You win by resignation!" - : condition === 'disconnect' ? "You win by abandonment!" - : condition === 'royalcapture' ? "You win by royal capture!" - : condition === 'allroyalscaptured' ? "You win by all royals captured!" - : condition === 'allpiecescaptured' ? "You win by all pieces captured!" - : condition === 'threecheck' ? "You win by three-check!" - : condition === 'koth' ? "You win by king of the hill!" - : "You win!" - else if (victor === 'draw') element_whosturn.textContent = condition === 'stalemate' ? "Draw by stalemate!" - : condition === 'repetition' ? "Draw by repetition!" - : condition === 'moverule' ? `Draw by the ${game.getGamefile().gameRules.moveRule / 2}-move-rule!` - : condition === 'insuffmat' ? "Draw by insufficient material!" - : "Draw!" - else if (condition === 'aborted') element_whosturn.textContent = "Game aborted." - else /* loss */ element_whosturn.textContent = condition === 'checkmate' ? "You lose by checkmate!" - : condition === 'time' ? "You lose on time!" - : condition === 'resignation' ? "You lose by resignation!" - : condition === 'disconnect' ? "You lose by abandonment!" - : condition === 'royalcapture' ? "You lose by royal capture!" - : condition === 'allroyalscaptured' ? "You lose by all royals captured!" - : condition === 'allpiecescaptured' ? "You lose by all pieces captured!" - : condition === 'threecheck' ? "You lose by three-check!" - : condition === 'koth' ? "You lose by king of the hill!" - : "You lose!" + if (onlinegame.areWeColor(victor)) element_whosturn.textContent = condition === 'checkmate' ? translations["results"]["you_checkmate"] + : condition === 'time' ? translations["results"]["you_time"] + : condition === 'resignation' ? translations["results"]["you_resignation"] + : condition === 'disconnect' ? translations["results"]["you_disconnect"] + : condition === 'royalcapture' ? translations["results"]["you_royalcapture"] + : condition === 'allroyalscaptured' ? translations["results"]["you_allroyalscaptured"] + : condition === 'allpiecescaptured' ? translations["results"]["you_allpiecescaptured"] + : condition === 'threecheck' ? translations["results"]["you_threecheck"] + : condition === 'koth' ? translations["results"]["you_koth"] + : translations["results"]["you_generic"] + else if (victor === 'draw') element_whosturn.textContent = condition === 'stalemate' ? translations["results"]["draw_stalemate"] + : condition === 'repetition' ? translations["results"]["draw_repetition"] + : condition === 'moverule' ? `${translations["results"]["draw_moverule"][0]}${(game.getGamefile().gameRules.moveRule / 2)}${translations["results"]["draw_moverule"][1]}` + : condition === 'insuffmat' ? translations["results"]["draw_insuffmat"] + : translations["results"]["draw_generic"] + else if (condition === 'aborted') element_whosturn.textContent = translations["results"]["aborted"] + else /* loss */ element_whosturn.textContent = condition === 'checkmate' ? translations["results"]["opponent_checkmate"] + : condition === 'time' ? translations["results"]["opponent_time"] + : condition === 'resignation' ? translations["results"]["opponen_resignation"] + : condition === 'disconnect' ? translations["results"]["opponent_disconnect"] + : condition === 'royalcapture' ? translations["results"]["opponent_royalcapture"] + : condition === 'allroyalscaptured' ? translations["results"]["opponent_allroyalscaptured"] + : condition === 'allpiecescaptured' ? translations["results"]["opponent_allpiecescaptured"] + : condition === 'threecheck' ? translations["results"]["opponent_threecheck"] + : condition === 'koth' ? translations["results"]["opponent_koth"] + : translations["results"]["opponent_generic"] } else { // Local game - if (condition === 'checkmate') element_whosturn.textContent = victor === 'white' ? "White wins by checkmate!" - : victor === 'black' ? "Black wins by checkmate!" - : 'This is a bug, please report. Game ended by checkmate.' - else if (condition === 'time') element_whosturn.textContent = victor === 'white' ? "White wins on time!" - : victor === 'black' ? "Black wins on time!" - : 'This is a bug, please report. Game ended on time.' - else if (condition === 'royalcapture') element_whosturn.textContent = victor === 'white' ? "White wins by royal capture!" - : victor === 'black' ? "Black wins by royal capture!" - : 'This is a bug, please report. Game ended by royal capture.' - else if (condition === 'allroyalscaptured') element_whosturn.textContent = victor === 'white' ? "White wins by all royals captured!" - : victor === 'black' ? "Black wins by all royals captured!" - : 'This is a bug, please report. Game ended by all royals captured.' - else if (condition === 'allpiecescaptured') element_whosturn.textContent = victor === 'white' ? "White wins by all pieces captured!" - : victor === 'black' ? "Black wins by all pieces captured!" - : 'This is a bug, please report. Game ended by all pieces captured.' - else if (condition === 'threecheck') element_whosturn.textContent = victor === 'white' ? "White wins by three-check!" - : victor === 'black' ? "Black wins by three-check!" - : 'This is a bug, please report. Game ended by three-check.' - else if (condition === 'koth') element_whosturn.textContent = victor === 'white' ? "White wins by king of the hill!" - : victor === 'black' ? "Black wins by king of the hill!" - : 'This is a bug, please report. Game ended by king of the hill.' - else if (condition === 'stalemate') element_whosturn.textContent = "Draw by stalemate!" - else if (condition === 'repetition') element_whosturn.textContent = "Draw by repetition!" - else if (condition === 'moverule') element_whosturn.textContent = `Draw by the ${game.getGamefile().gameRules.moveRule / 2}-move-rule!` - else if (condition === 'insuffmat') element_whosturn.textContent = "Draw by insufficient material!" + if (condition === 'checkmate') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_checkmate"] + : victor === 'black' ? translations["results"]["black_checkmate"] + : translations["results"]["bug_checkmate"] + else if (condition === 'time') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_time"] + : victor === 'black' ? translations["results"]["black_time"] + : translations["results"]["bug_time"] + else if (condition === 'royalcapture') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_royalcapture"] + : victor === 'black' ? translations["results"]["black_royalcapture"] + : translations["results"]["bug_royalcapture"] + else if (condition === 'allroyalscaptured') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_allroyalscaptured"] + : victor === 'black' ? translations["results"]["black_allroyalscaptured"] + : translations["results"]["bug_allroyalscaptured"] + else if (condition === 'allpiecescaptured') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_allpiecescaptured"] + : victor === 'black' ? translations["results"]["black_allpiecescaptured"] + : translations["results"]["bug_allpiecescaptured"] + else if (condition === 'threecheck') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_threecheck"] + : victor === 'black' ? translations["results"]["black_threecheck"] + : translations["results"]["bug_threecheck"] + else if (condition === 'koth') element_whosturn.textContent = victor === 'white' ? translations["results"]["white_koth"] + : victor === 'black' ? translations["results"]["black_koth"] + : translations["results"]["bug_koth"] + else if (condition === 'stalemate') element_whosturn.textContent = translations["results"]["draw_stalemate"] + else if (condition === 'repetition') element_whosturn.textContent = translations["results"]["draw_repetition"] + else if (condition === 'moverule') element_whosturn.textContent = `${translations["results"]["draw_moverule"][0]}${(game.getGamefile().gameRules.moveRule / 2)}${translations["results"]["draw_moverule"][1]}` + else if (condition === 'insuffmat') element_whosturn.textContent = translations["results"]["draw_insuffmat"] else { - element_whosturn.textContent = "This is a bug, please report!" + element_whosturn.textContent = translations["results"]["bug_generic"] console.error(`Game conclusion: "${conclusion}"\nVictor: ${victor}\nCondition: ${condition}`) } } diff --git a/src/client/scripts/game/gui/guipause.js b/src/client/scripts/game/gui/guipause.js index 03b803ed3..cc36e1d15 100644 --- a/src/client/scripts/game/gui/guipause.js +++ b/src/client/scripts/game/gui/guipause.js @@ -56,11 +56,11 @@ const guipause = (function(){ function changeTextOfMainMenuButton() { if (!isPaused) return; - if (!onlinegame.areInOnlineGame() || game.getGamefile().gameConclusion) return element_mainmenu.textContent = "Main Menu"; + if (!onlinegame.areInOnlineGame() || game.getGamefile().gameConclusion) return element_mainmenu.textContent = translations["main_menu"]; - if (movesscript.isGameResignable(game.getGamefile())) return element_mainmenu.textContent = "Resign Game"; + if (movesscript.isGameResignable(game.getGamefile())) return element_mainmenu.textContent = translations["resign_game"]; - return element_mainmenu.textContent = "Abort Game"; + return element_mainmenu.textContent = translations["abort_game"]; } function initListeners() { @@ -108,11 +108,11 @@ const guipause = (function(){ mode++; if (mode > 2) mode = 0; arrows.setMode(mode); - const text = mode === 0 ? "Arrows: Off" - : mode === 1 ? "Arrows: Defense" - : "Arrows: All"; + const text = mode === 0 ? translations["arrows_off"] + : mode === 1 ? translations["arrows_defense"] + : translations["arrows_all"]; element_pointers.textContent = text; - if (!isPaused) statustext.showStatus('Toggled ' + text) + if (!isPaused) statustext.showStatus(translations["toggled"] + " " + text) } function callback_Perspective(event) { diff --git a/src/client/scripts/game/gui/guiplay.js b/src/client/scripts/game/gui/guiplay.js index 9d7bd531f..2043d15fe 100644 --- a/src/client/scripts/game/gui/guiplay.js +++ b/src/client/scripts/game/gui/guiplay.js @@ -101,7 +101,7 @@ const guiplay = (function(){ function changePlayMode(mode) { // online / local / computer modeSelected = mode if (mode === 'online') { - element_playName.textContent = "Play - Online" + element_playName.textContent = translations["menu_online"] element_online.classList.add('selected') element_local.classList.remove('selected') element_online.classList.remove('not-selected') @@ -117,7 +117,7 @@ const guiplay = (function(){ } else if (mode === 'local') { guiplay.setElement_CreateInviteEnabled(true); invites.cancel() - element_playName.textContent = "Play - Local" + element_playName.textContent = translations["menu_local"] element_online.classList.remove('selected') element_local.classList.add('selected') element_online.classList.add('not-selected') @@ -195,7 +195,7 @@ const guiplay = (function(){ const code = element_textboxPrivate.value.toLowerCase() - if (code.length !== 5) return statustext.showStatus("Invite code needs to be 5 digits.") + if (code.length !== 5) return statustext.showStatus(translations["invite_error_digits"]) element_joinPrivateMatch.disabled = true; // Re-enable when the code is changed @@ -223,7 +223,7 @@ const guiplay = (function(){ const code = invites.gelement_iCodeCode().textContent; main.copyToClipboard(code) - statustext.showStatus('Copied invite code to clipboard.') + statustext.showStatus(translations["invite_copied"]) } function initListeners_Invites() { diff --git a/src/client/scripts/game/gui/stats.js b/src/client/scripts/game/gui/stats.js index 1d962917d..6e2720895 100644 --- a/src/client/scripts/game/gui/stats.js +++ b/src/client/scripts/game/gui/stats.js @@ -48,7 +48,7 @@ const stats = { const currentPly = game.getGamefile().moveIndex + 1 const totalPlyCount = movesscript.getPlyCount(game.getGamefile().moves) - stats.elementStatusMoves.textContent = `Move: ${currentPly}/${totalPlyCount}`; + stats.elementStatusMoves.textContent = `${translations["move_counter"]} ${currentPly}/${totalPlyCount}`; }, updateStatsCSS() { @@ -62,7 +62,7 @@ const stats = { updatePiecesMesh(percent) { const percentString = math.decimalToPercent(percent); - stats.elementStatusPiecesMesh.textContent = `Constructing mesh ${percentString}`; + stats.elementStatusPiecesMesh.textContent = `${translations["constructing_mesh"]} ${percentString}`; }, hidePiecesMesh() { @@ -91,7 +91,7 @@ const stats = { updateRotateMesh(percent) { const percentString = math.decimalToPercent(percent); - stats.elementStatusRotateMesh.textContent = `Rotating mesh ${percentString}`; + stats.elementStatusRotateMesh.textContent = `${translations["rotating_mesh"]} ${percentString}`; }, hideRotateMesh() { diff --git a/src/client/scripts/game/gui/statustext.js b/src/client/scripts/game/gui/statustext.js index a7a6ec066..09927fffd 100644 --- a/src/client/scripts/game/gui/statustext.js +++ b/src/client/scripts/game/gui/statustext.js @@ -71,12 +71,12 @@ const statustext = (function(){ } function lostConnection() { - showStatus(`Lost connection.`); + showStatus(translations["lost_connection"]); } /** Shows a status message stating to please wait to perform this task. */ function pleaseWaitForTask() { - showStatus(`Please wait a moment to perform this task.`, false, 0.5) + showStatus(translations["please_wait"], false, 0.5) } // Dev purposes diff --git a/src/client/scripts/game/htmlscript.js b/src/client/scripts/game/htmlscript.js index 7f7e6354b..8061a79f0 100644 --- a/src/client/scripts/game/htmlscript.js +++ b/src/client/scripts/game/htmlscript.js @@ -102,7 +102,7 @@ const htmlscript = (function() { const element_loadingError = document.getElementById('loading-error'); const element_loadingErrorText = document.getElementById('loading-error-text'); element_loadingError.classList.remove('hidden'); - element_loadingErrorText.textContent = lostNetwork ? "Lost network." : "One or more resources failed to load. Please refresh."; + element_loadingErrorText.textContent = lostNetwork ? translations["lost_network"] : translations["failed_to_load"]; // Remove the glowing in the background animation const element_loadingGlow = document.getElementById('loading-glow'); diff --git a/src/client/scripts/game/misc/browsersupport.js b/src/client/scripts/game/misc/browsersupport.js index 044f7a5b7..a4b3c7c48 100644 --- a/src/client/scripts/game/misc/browsersupport.js +++ b/src/client/scripts/game/misc/browsersupport.js @@ -17,7 +17,7 @@ const browsersupport = (function() { BigInt(123); // Try to initialize a BigInt } catch (e) { console.error('BigInts are not supported.') - alert("BigInts are not supported. Please upgrade your browser.\nBigInts are needed to make the board infinite."); + alert(translations["bigints_unsupported"]); throw new Error('Browser not supported.') } } diff --git a/src/client/scripts/game/misc/invites.js b/src/client/scripts/game/misc/invites.js index c406e2664..ed101ac6d 100644 --- a/src/client/scripts/game/misc/invites.js +++ b/src/client/scripts/game/misc/invites.js @@ -25,7 +25,7 @@ const invites = (function(){ function update() { if (!guiplay.onPlayPage()) return; // Not on the play screen - if (loadbalancer.gisHibernating()) statustext.showStatus("Move the mouse to reconnect.", false, 0.1) + if (loadbalancer.gisHibernating()) statustext.showStatus(translations["invites"]["move_mouse"], false, 0.1) } function unsubIfWeNotHave() { @@ -48,7 +48,7 @@ const invites = (function(){ updateActiveGameCount(data.value) break; default: - statustext.showStatus(`Unknown action ${data.action} received from the server in the invites subscription!`, true) + statustext.showStatus(`${translations["invites"]["unknown_action_received_1"]} ${data.action} ${translations["invites"]["unknown_action_received_2"]}`, true) break; } } @@ -84,7 +84,7 @@ const invites = (function(){ function cancel(id = ourInviteID, isUserAction = false) { if (!weHaveInvite) return; - if (!id) return statustext.showStatus("Cannot cancel invite of undefined ID.", true) + if (!id) return statustext.showStatus(translations["invites"]["cannot_cancel"], true) deleteInviteTagInLocalStorage(); @@ -149,7 +149,7 @@ const invites = (function(){ //
Unrated
//
Accept
- const n = ours ? "(You)" : invite.name + const n = ours ? translations["invites"]["you_indicator"] : invite.name const name = createDiv(['invite-child'], n) newInvite.appendChild(name) @@ -160,15 +160,15 @@ const invites = (function(){ const cloc = createDiv(['invite-child'], time) newInvite.appendChild(cloc) - const uColor = ours ? invite.color === 'White' ? "You're: White" : invite.color === 'Black' ? "You're: Black" : 'Random' - : invite.color === 'White' ? "You're: Black" : invite.color === 'Black' ? "You're: White" : 'Random' + const uColor = ours ? invite.color === 'White' ? translations["invites"]["you_are_white"] : invite.color === 'Black' ? translations["invites"]["you_are_black"] : translations["invites"]["random"] + : invite.color === 'White' ? translations["invites"]["you_are_black"] : invite.color === 'Black' ? translations["invites"]["you_are_white"] : translations["invites"]["random"] const color = createDiv(['invite-child'], uColor) newInvite.appendChild(color) const rated = createDiv(['invite-child'], invite.rated) newInvite.appendChild(rated) - const a = ours ? 'Cancel' : "Accept" + const a = ours ? translations["invites"]["cancel"] : translations["invites"]["accept"] const accept = createDiv(['invite-child', 'accept'], a) newInvite.appendChild(accept) @@ -296,8 +296,8 @@ const invites = (function(){ function updateCreateInviteButton() { if (guiplay.getModeSelected() !== 'online') return; - if (weHaveInvite) guiplay.setElement_CreateInviteTextContent("Cancel Invite") - else guiplay.setElement_CreateInviteTextContent("Create Invite") + if (weHaveInvite) guiplay.setElement_CreateInviteTextContent(translations["invites"]["cancel_invite"]) + else guiplay.setElement_CreateInviteTextContent(translations["invites"]["create_invite"]) } function updatePrivateInviteCode(privateInviteID) { // If undefined, we know we don't have a "private" invite @@ -329,7 +329,7 @@ const invites = (function(){ function updateActiveGameCount(newCount) { if (newCount == null) return; - element_joinExisting.textContent = `Join Existing - Active Games: ${newCount}` + element_joinExisting.textContent = `${translations["invites"]["join_existing_active_games"]} ${newCount}` } function doWeHave() { diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 0a0113702..83eb108af 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -234,10 +234,9 @@ const math = (function() { function areLinesCollinear(lines) { let gradient for (const line of lines) { - console.log(line) const lgradient = line[1]/line[0] if (!gradient) gradient = lgradient - if (!Number.isFinite(gradient)&&!Number.isFinite(lgradient)) {console.log(lgradient,gradient);continue}; + if (!Number.isFinite(gradient)&&!Number.isFinite(lgradient)) {continue}; if (!isAproxEqual(lgradient, gradient)) return false; } return true @@ -451,7 +450,13 @@ const math = (function() { return corner; } - // Top left as failsafe + /** + * Get the corner coordinate of the bounding box. + * Will revert to top left if the corners sides aren't provided. + * @param {BoundingBox} boundingBox + * @param {String} corner + * @returns {Number[]} + */ function getCornerOfBoundingBox(boundingBox, corner) { const { left, right, top, bottom } = boundingBox; let yval = corner.startsWith('bottom') ? bottom : top; @@ -499,35 +504,6 @@ const math = (function() { // Doesn't intersect any tile in the box. } - // Returns point, if there is one, of a line with specified slope "b" intersection screen edge on desired corner - function getIntersectionEntryTile (slope, b, boundingBox, corner) { // corner: "topright"/"bottomright"... - const { left, right, top, bottom } = boundingBox; - - // Check for intersection with left side of rectangle - if (corner.endsWith('left')) { - const yIntersectLeft = left * slope + b; - if (yIntersectLeft >= bottom && yIntersectLeft <= top) return [left, yIntersectLeft] - } - - // Check for intersection with bottom side of rectangle - if (corner.startsWith('bottom')) { - const xIntersectBottom = (bottom - b) * slope; - if (xIntersectBottom >= left && xIntersectBottom <= right) return [xIntersectBottom, bottom] - } - - // Check for intersection with right side of rectangle - if (corner.endsWith('right')) { - const yIntersectRight = right * slope + b; - if (yIntersectRight >= bottom && yIntersectRight <= top) return [right, yIntersectRight]; - } - - // Check for intersection with top side of rectangle - if (corner.startsWith('top')) { - const xIntersectTop = (top - b) * slope; - if (xIntersectTop >= left && xIntersectTop <= right) return [xIntersectTop, top]; - } - } - function convertWorldSpaceToGrid(value) { return value / movement.getBoardScale() } @@ -1033,7 +1009,6 @@ const math = (function() { convertWorldSpaceToPixels_Virtual, getAABBCornerOfLine, getCornerOfBoundingBox, - getLineIntersectionEntryTile, getIntersectionEntryTile, convertWorldSpaceToGrid, euclideanDistance, diff --git a/src/client/scripts/game/misc/onlinegame.js b/src/client/scripts/game/misc/onlinegame.js index 3abcc4e37..c1be20b89 100644 --- a/src/client/scripts/game/misc/onlinegame.js +++ b/src/client/scripts/game/misc/onlinegame.js @@ -129,8 +129,8 @@ const onlinegame = (function(){ } function displayWeAFK(secsRemaining) { - const resigningOrAborting = movesscript.isGameResignable(game.getGamefile()) ? 'resigning' : 'aborting'; - statustext.showStatusForDuration(`You are AFK. Auto-${resigningOrAborting} in ${secsRemaining}...`, 1000); + const resigningOrAborting = movesscript.isGameResignable(game.getGamefile()) ? translations["onlinegame"]["auto_resigning_in"] : translations["onlinegame"]["auto_aborting_in"]; + statustext.showStatusForDuration(`${translations["onlinegame"]["afk_warning"]} ${resigningOrAborting} ${secsRemaining}...`, 1000); const nextSecsRemaining = secsRemaining - 1; if (nextSecsRemaining === 0) return; // Stop const timeRemainUntilAFKLoss = afk.timeWeLoseFromAFK - Date.now(); @@ -190,7 +190,7 @@ const onlinegame = (function(){ inSync = false; break; case "login": // Not logged in error - statustext.showStatus("You are not logged in. Please login to reconnect to this game.", true, 100); + statustext.showStatus(translations["onlinegame"]["not_logged_in"], true, 100); websocket.getSubs().game = false; inSync = false; clock.stop(); @@ -199,13 +199,13 @@ const onlinegame = (function(){ board.darkenColor(); break; case "nogame": // Game is deleted / no longer exists - statustext.showStatus('Game no longer exists.', false, 1.5) + statustext.showStatus(translations["onlinegame"]["game_no_longer_exists"], false, 1.5) websocket.getSubs().game = false; inSync = false; gamefileutility.concludeGame(game.getGamefile(), 'aborted', { requestRemovalFromActiveGames: false }); break; case "leavegame": // Another window connected - statustext.showStatus('Another window has connected.') + statustext.showStatus(translations["onlinegame"]["another_window_connected"]) websocket.getSubs().game = false; inSync = false; closeOnlineGame() @@ -230,7 +230,7 @@ const onlinegame = (function(){ initServerRestart(data.value); break; default: - statustext.showStatus(`Unknown action ${message.action} received from the server in the invites subscription!`, true) + statustext.showStatus(`${translations["invites"]["unknown_action_received_1"]} ${message.action} ${translations["invites"]["unknown_action_received_2"]}`, true) break; } } @@ -252,8 +252,8 @@ const onlinegame = (function(){ } function displayOpponentAFK(secsRemaining) { - const resigningOrAborting = movesscript.isGameResignable(game.getGamefile()) ? 'resigning' : 'aborting'; - statustext.showStatusForDuration(`Opponent is AFK. Auto-${resigningOrAborting} in ${secsRemaining}...`, 1000); + const resigningOrAborting = movesscript.isGameResignable(game.getGamefile()) ? translations["onlinegame"]["auto_resigning_in"] : translations["onlinegame"]["auto_aborting_in"]; + statustext.showStatusForDuration(`${translations["onlinegame"]["opponent_afk"]} ${resigningOrAborting} ${secsRemaining}...`, 1000); const nextSecsRemaining = secsRemaining - 1; if (nextSecsRemaining === 0) return; // Stop const timeRemainUntilAFKLoss = afk.timeOpponentLoseFromAFK - Date.now(); @@ -281,11 +281,11 @@ const onlinegame = (function(){ } function displayOpponentDisconnect(secsRemaining, wasByChoice) { - const disconnectedOrLostConnection = wasByChoice ? 'disconnected' : 'lost connection' - const resigningOrAborting = movesscript.isGameResignable(game.getGamefile()) ? 'resigning' : 'aborting'; + const opponent_disconnectedOrLostConnection = wasByChoice ? translations["onlinegame"]["opponent_disconnected"] : translations["onlinegame"]["opponent_lost_connection"] + const resigningOrAborting = movesscript.isGameResignable(game.getGamefile()) ? translations["onlinegame"]["auto_resigning_in"] : translations["onlinegame"]["auto_aborting_in"]; // The "You are AFK" message should overwrite, be on top of, this message, // so if that is running, don't display this 1-second disconnect message, but don't cancel it either! - if (!afk.timeWeLoseFromAFK) statustext.showStatusForDuration(`Opponent has ${disconnectedOrLostConnection}. Auto-${resigningOrAborting} in ${secsRemaining}...`, 1000); + if (!afk.timeWeLoseFromAFK) statustext.showStatusForDuration(`${opponent_disconnectedOrLostConnection} ${resigningOrAborting} ${secsRemaining}...`, 1000); const nextSecsRemaining = secsRemaining - 1; if (nextSecsRemaining === 0) return; // Stop const timeRemainUntilDisconnectLoss = disconnect.timeOpponentLoseFromDisconnect - Date.now(); @@ -702,12 +702,12 @@ const onlinegame = (function(){ /** Displays the next "Server restaring..." message, and schedules the next one. */ function displayServerRestarting(minutesLeft) { if (minutesLeft === 0) { - statustext.showStatus(`Server restarting shortly...`, false, 2) + statustext.showStatus(translations["onlinegame"]["server_restarting"], false, 2) serverRestart.time = false; return; // Print no more server restarting messages } - const plurality = minutesLeft > 1 ? 's' : ''; - statustext.showStatus(`Server restarting in ${minutesLeft} minute${plurality}...`, false, 2) + const minutes_plurality = minutesLeft > 1 ? translations["onlinegame"]["minutes"] : translations["onlinegame"]["minute"]; + statustext.showStatus(`${translations["onlinegame"]["server_restarting_in"]} ${minutesLeft} ${minutes_plurality}...`, false, 2) let nextKeyMinute; for (const keyMinute of serverRestart.keyMinutes) { if (keyMinute < minutesLeft) { diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index f26e31689..1f58182ac 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -97,7 +97,6 @@ const arrows = (function() { const slides = gamefile.startSnapshot.slidingPossible for (const line of slides) { - const axis = line[0] == 0 ? 1 : 0; const perpendicular = [-line[1], line[0]]; const linestr = math.getKeyFromCoords(line); @@ -112,19 +111,16 @@ const arrows = (function() { const boardSlidesStart = Math.min(boardSlidesLeft, boardSlidesRight); const boardSlidesEnd = Math.max(boardSlidesLeft, boardSlidesRight); + for (const key in gamefile.piecesOrganizedByLines[linestr]) { + const intsects = key.split("|").map(Number) + if (boardSlidesStart > intsects || boardSlidesEnd < intsects) continue; + const pieces = calcPiecesOffScreen(line, gamefile.piecesOrganizedByLines[linestr][key]) - for (let i=Math.ceil(boardSlidesStart); i<=Math.floor(boardSlidesEnd); i++) { - for (let x=0; x { + updateCookie("i18next", language_picker.value, 365); + location.reload(); + }); +} diff --git a/src/client/scripts/login.js b/src/client/scripts/login.js index facb0e5a3..31972e90f 100644 --- a/src/client/scripts/login.js +++ b/src/client/scripts/login.js @@ -67,7 +67,18 @@ const sendLogin = (username, password) => { element_forgot.className = 'forgotvisible'; } updateSubmitButton(); - loginErrorElement.textContent = result['message']; + + // translate the message from the server if a translation is available + let result_message = result['message']; + if (translations[result_message]) result_message = translations[result_message]; + + // append the login cooldown if it exists + let login_cooldown = ("login_cooldown" in result ? result["login_cooldown"] : undefined); + if (login_cooldown !== undefined){ + const seconds_plurality = login_cooldown == 1 ? translations["ws-second"] : translations["ws-seconds"]; + result_message += ` ${login_cooldown} ${seconds_plurality}.` + } + loginErrorElement.textContent = result_message; element_submitButton.disabled = false; } }); diff --git a/src/client/scripts/member.js b/src/client/scripts/member.js index 6d126711b..c4ce6c6fa 100644 --- a/src/client/scripts/member.js +++ b/src/client/scripts/member.js @@ -53,10 +53,10 @@ function refreshAndUpdateNav () { // Change navigation links... element_loginLink.setAttribute('href', `/member/${result.member.toLowerCase()}`); // element_loginText.textContent = result.member; - element_loginText.textContent = 'Profile'; + element_loginText.textContent = translations["js-profile"]; element_createaccountLink.setAttribute('href', '/logout'); - element_createaccountText.textContent = 'Log Out'; + element_createaccountText.textContent = translations["js-logout"]; } else { // Unauthorized, don't change any navigation links console.log(result['message']); @@ -127,8 +127,8 @@ function showAccountInfo() { } async function removeAccount(confirmation) { - if (!confirmation || confirm("Are you sure you want to delete your account? This CANNOT be undone! Click OK to enter your password.")) { - const password = prompt("Enter your password to PERMANENTLY delete your account:"); + if (!confirmation || confirm(translations["js-confirm_delete"])) { + const password = prompt(translations["js-enter_password"]); const cancelWasPressed = password === null; if (cancelWasPressed) return; // Don't delete account @@ -144,8 +144,18 @@ async function removeAccount(confirmation) { const response = await fetch(`/member/${member}/delete`, config) if (!response.ok) { + // translate the message from the server if a translation is available const result = await response.json(); - alert(result.message); + let message = result.message; + if (translations[message]) message = translations[message]; + + // append the login cooldown if it exists + let login_cooldown = ("login_cooldown" in result ? result["login_cooldown"] : undefined); + if (login_cooldown !== undefined){ + const seconds_plurality = login_cooldown == 1 ? translations["ws-second"] : translations["ws-seconds"]; + message += ` ${login_cooldown} ${seconds_plurality}.` + } + alert(message); removeAccount(false); } else { window.location.href = '/'; diff --git a/src/client/scripts/memberHeader.js b/src/client/scripts/memberHeader.js index bfd530974..b22fde862 100644 --- a/src/client/scripts/memberHeader.js +++ b/src/client/scripts/memberHeader.js @@ -107,10 +107,10 @@ const validation = (function(){ function changeNavigationLinks() { loginLink.setAttribute('href', `/member/${member.toLowerCase()}`); - loginText.textContent = 'Profile'; + loginText.textContent = translations["js-profile"]; createaccountLink.setAttribute('href', '/logout'); - createaccountText.textContent = 'Log Out'; + createaccountText.textContent = translations["js-logout"]; } /** diff --git a/src/client/scripts/validation.js b/src/client/scripts/validation.js index e7ecfd2de..cab41b486 100644 --- a/src/client/scripts/validation.js +++ b/src/client/scripts/validation.js @@ -130,16 +130,16 @@ const validation = (function(){ function updateNavigationLinks() { if (areLoggedIn) { loginLink.setAttribute('href', `/member/${member.toLowerCase()}`); - loginText.textContent = 'Profile'; + loginText.textContent = translations["js-profile"]; createaccountLink.setAttribute('href', '/logout'); - createaccountText.textContent = 'Log Out'; + createaccountText.textContent = translations["js-logout"]; } else { loginLink.setAttribute('href', `/login`); - loginText.textContent = 'Log In'; + loginText.textContent = translations["js-login"]; createaccountLink.setAttribute('href', '/createaccount'); - createaccountText.textContent = 'Create Account'; + createaccountText.textContent = translations["js-createaccount"]; } } diff --git a/src/client/views/components/footer.ejs b/src/client/views/components/footer.ejs new file mode 100644 index 000000000..aebf93549 --- /dev/null +++ b/src/client/views/components/footer.ejs @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/src/client/views/createaccount.html b/src/client/views/createaccount.ejs similarity index 58% rename from src/client/views/createaccount.html rename to src/client/views/createaccount.ejs index eb40fc98f..1c04eef21 100644 --- a/src/client/views/createaccount.html +++ b/src/client/views/createaccount.ejs @@ -1,11 +1,19 @@ - + + Create Account + @@ -13,55 +21,51 @@
-

Create Account

+

<%=t('create-account.title')%>

- +
- +
- +
- -

I agree to the Terms of Service

+ +

<%=t('create-account.argreement.0')%><%=t('create-account.argreement.1')%>

- + <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> \ No newline at end of file diff --git a/src/client/views/credits.ejs b/src/client/views/credits.ejs new file mode 100644 index 000000000..f2624b2f9 --- /dev/null +++ b/src/client/views/credits.ejs @@ -0,0 +1,75 @@ + + + + + + + + Credits + + + + + +
+ +
+
+
+

<%=t('credits.title')%>

+

<%=t('credits.copyright')%>

+ +

<%=t('credits.variants_heading')%>

+

<%=t('credits.variants_credits.0')%>

+

<%=t('credits.variants_credits.1')%>

+

<%=t('credits.variants_credits.2')%>

+

<%=t('credits.variants_credits.3')%>

+

<%=t('credits.variants_credits.4')%>

+

<%=t('credits.variants_credits.5')%>

+

<%=t('credits.variants_credits.6')%>

+

<%=t('credits.variants_credits.7')%>

+

<%=t('credits.variants_credits.8')%>

+

<%=t('credits.variants_credits.9')%>

+

Omega <%=t('credits.variants_credits.10')%>

+

Omega^2 <%=t('credits.variants_credits.11')%>

+

Omega^3 <%=t('credits.variants_credits.12')%>

+

Omega^4 <%=t('credits.variants_credits.13')%>

+

<%=t('credits.textures_heading')%>

+

Cburnett <%=t('credits.textures_licensed_under')%> Creative Commons Attribution-Share Alike 3.0 Unported License.

+

Green Chess <%=t('credits.textures_licensed_under')%> Creative Commons Attribution-Share Alike 3.0

+

Pychess <%=t('credits.textures_licensed_under')%> GNU General Public License

+

<%=t('credits.textures_credits.0')%>

+ +

<%=t('credits.sounds_heading')%>

+

<%=t('credits.sounds_credits.0.0')%> Lichess-org Lila <%=t('credits.sounds_credits.0.1')%> GNU Affero General Public License

+

<%=t('credits.sounds_credits.1')%>

+ +

<%=t('credits.code_heading')%>

+

High Performance Matrix & Vector Operations <%=t('credits.code_credits.0')%>

+

Infinite Chess Notation Converter <%=t('credits.code_credits.1')%>

+
+
+ <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> + + \ No newline at end of file diff --git a/src/client/views/credits.html b/src/client/views/credits.html deleted file mode 100644 index bca2d2de8..000000000 --- a/src/client/views/credits.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - Credits - - - - -
- -
-
-
-

Credits

-

Anything on the website that is not listed below is copyright of www.InfiniteChess.org

- -

Variants

-

Core designed by Andreas Tsevas.

-

Space designed by Andreas Tsevas.

-

Space Classic designed by Andreas Tsevas.

-

Coaip (Chess on an Infinite Plane) designed by Vickalan.

-

Pawn Horde designed by Inaccessible Cardinal.

-

Abundance designed by Clicktuck Suskriberz.

-

Pawndard by SexiLexi.

-

Classical+ by SexiLexi.

-

Knightline by Inaccessible Cardinal.

-

Knighted Chess by cycy98.

-

Omega designed by Cory Evans and Joel Hamkins.

-

Omega^2 designed by Andreas Tsevas.

-

Omega^3 designed by Cory Evans and Joel Hamkins.

-

Omega^4 designed by Cory Evans, Joel Hamkins, and Norman Lewis Perlmutter.

- -

Textures

-

Cburnett textures licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License.

-

Green Chess textures licensed under the Creative Commons Attribution-Share Alike 3.0

-

Pychess textures licensed under the GNU General Public License

-

Gold coin by Quolte.

- -

Sounds

-

Some sounds are provided by the Lichess-org Lila project under the GNU Affero General Public License.

-

Other sounds created by Naviary.

- -

Code

-

High Performance Matrix & Vector Operations by Brandon Jones and Colin MacKenzie IV.

-

Infinite Chess Notation Converter by Andreas Tsevas and Naviary.

-
-
- - - \ No newline at end of file diff --git a/src/client/views/errors/400.html b/src/client/views/errors/400.ejs similarity index 76% rename from src/client/views/errors/400.html rename to src/client/views/errors/400.ejs index fe658da6c..a6d124562 100644 --- a/src/client/views/errors/400.html +++ b/src/client/views/errors/400.ejs @@ -1,5 +1,5 @@ - + @@ -11,7 +11,7 @@

400 Bad Request

-

Invalid parameters were received.

+

<%=t('error-pages.400_message')%>

\ No newline at end of file diff --git a/src/client/views/errors/401.html b/src/client/views/errors/401.ejs similarity index 85% rename from src/client/views/errors/401.html rename to src/client/views/errors/401.ejs index ab688bfc6..4d4186ed2 100644 --- a/src/client/views/errors/401.html +++ b/src/client/views/errors/401.ejs @@ -1,5 +1,5 @@ - + diff --git a/src/client/views/errors/404.html b/src/client/views/errors/404.ejs similarity index 85% rename from src/client/views/errors/404.html rename to src/client/views/errors/404.ejs index ee56da6b4..a5438999b 100644 --- a/src/client/views/errors/404.html +++ b/src/client/views/errors/404.ejs @@ -1,5 +1,5 @@ - + diff --git a/src/client/views/errors/409.html b/src/client/views/errors/409.ejs similarity index 61% rename from src/client/views/errors/409.html rename to src/client/views/errors/409.ejs index 5ab1a7aff..633d390c6 100644 --- a/src/client/views/errors/409.html +++ b/src/client/views/errors/409.ejs @@ -1,5 +1,5 @@ - + @@ -11,7 +11,7 @@

409 Conflict

-

There may have been a clashing username or email. Please reload the page.

+

<%=t('error-pages.409_message.0')%><%=t('error-pages.409_message.1')%><%=t('error-pages.409_message.2')%>

\ No newline at end of file diff --git a/src/client/views/errors/500.html b/src/client/views/errors/500.ejs similarity index 76% rename from src/client/views/errors/500.html rename to src/client/views/errors/500.ejs index 1337e806b..f5b2e65f5 100644 --- a/src/client/views/errors/500.html +++ b/src/client/views/errors/500.ejs @@ -1,5 +1,5 @@ - + @@ -11,7 +11,7 @@

500 Server Error

-

This isn't supposed to happen. There is some debugging to be done!

+

<%=t('error-pages.500_message')%>

\ No newline at end of file diff --git a/src/client/views/index.ejs b/src/client/views/index.ejs new file mode 100644 index 000000000..5111a6f1c --- /dev/null +++ b/src/client/views/index.ejs @@ -0,0 +1,81 @@ + + + + + + + + Infinite Chess | Home - The Official Website + + + + + +
+ +
+
+
+ +
+ +
+

<%=t('index.what_is_it_title')%>

+

<%-t('index.what_is_it_pargaraphs', { joinArrays: '

' })%>

+

<%=t('index.how_to_title')%>

+

<%=t('index.how_to_paragraph.0')%><%=t('index.how_to_paragraph.1')%><%=t('index.how_to_paragraph.2')%>

+ +
+

<%=t('index.about_title')%>

+ +

<%=t('index.about_paragraphs.0')%>

+

<%=t('index.about_paragraphs.1.0')%> <%=t('index.about_paragraphs.1.1')%>.

+ +
+

<%=t('index.patreon_title')%>

+
+

Andreas

+

Lebarb

+

Mauer01

+

Levon

+

IM Luke Harmon-Vellotti

+ +

KnightBeforeLast

+

SuperGamersGames

+
+
+

EmmaBellHelium

+

Meni

+

Settemio

+
+
+
+ <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> + + + \ No newline at end of file diff --git a/src/client/views/index.html b/src/client/views/index.html deleted file mode 100644 index 231f2c03c..000000000 --- a/src/client/views/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - Infinite Chess | Home - The Official Website - - - - -
- -

Home

-
- -

Play

-
- -

News

-
- -

Log In

-
- -

Create Account

-
- -
-
-
- -
- -
-

What is it?

-

Infinite Chess is a variant of chess in which there are no borders, much larger - than your familiar 8x8 board. The queen, rooks, and - bishops have no limit to how far they can move per turn. Pick any natural number up to infinity!

-

With no limit to how far you can move, there are positions possible where the doomsday clock, or checkmate-in-blank, number is represented by the first infinite ordinal, omega ω. In fact, researches have discovered that any countable ordinal is achievable for the checkmate clock!

-

As you can imagine, there are infinite possibilites for starting configurations, many of which you can play competitively! Your end goal is still checkmate, which requires new tactics seeing as there are no walls to trap the enemy king against. Games don't typically last much longer than normal chess games. Pawns also still promote at ranks 1 & 8!

-

How can I play?

-

The current version release is 1.3.2 on the Play page!

- -
-

About the Project

- -

I am Naviary. Since I first discovered Infinite Chess (the concept existed long before this website), I have been very intrigued by it and its possibilities! Up to just recently, playing has been quite difficult, requiring chess.com members to create images of the current board and send them back and forth for every move played. Due to this, not many people know about or have been able to play this.

-

It is my goal to build a way to make this easily playable for everyone, and grow a community surrounding it. I have spent countless hours of my own time on this website, up-keeping, and developing the game. I have many more ideas that will keep me occupied for some time. While I wish to keep this free-to-play, life has requirements, to help support me financially please consider joining my Patreon.

- -
-

Patreon Supporters

-
-

Andreas

-

Lebarb

-

Mauer01

-

Levon

-

IM Luke Harmon-Vellotti

- -

KnightBeforeLast

-

SuperGamersGames

-
-
-

EmmaBellHelium

-

Meni

-

Settemio

-
-
-
- - - \ No newline at end of file diff --git a/src/client/views/login.html b/src/client/views/login.ejs similarity index 55% rename from src/client/views/login.html rename to src/client/views/login.ejs index 37346237b..d6679dadc 100644 --- a/src/client/views/login.html +++ b/src/client/views/login.ejs @@ -1,11 +1,18 @@ - + + Log In + @@ -13,46 +20,42 @@
-

Log In

+

<%=t('login.title')%>

- +
- +
-

Forgot? Email us.

- +

<%=t('login.forgot_password.0')%><%=t('login.forgot_password.1')%>

+
- + <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> \ No newline at end of file diff --git a/src/client/views/member.ejs b/src/client/views/member.ejs new file mode 100644 index 000000000..d0b858e3b --- /dev/null +++ b/src/client/views/member.ejs @@ -0,0 +1,73 @@ + + + + + + + + Member + + + + + +
+ +
+
+
+ + +
+ Blank profile image +

+
+
+

<%=t('member.rating')%>

+

<%=t('member.joined')%>

+

<%=t('member.seen.0')%> <%=t('member.seen.1')%>

+
+
+
+ + +
+
+ +
+
+ +
+ +
+ <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> + + diff --git a/src/client/views/member.html b/src/client/views/member.html deleted file mode 100644 index aee237039..000000000 --- a/src/client/views/member.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - Member - - - - -
- -
-
-
- - -
- Blank profile image -

-
-
-

Elo rating:

-

Joined:

-

Seen: ago

-
-
-
- - -
-
- -
-
- -
- -
- - - diff --git a/src/client/views/news.html b/src/client/views/news.ejs similarity index 50% rename from src/client/views/news.html rename to src/client/views/news.ejs index 0a6601a31..27bfb3567 100644 --- a/src/client/views/news.html +++ b/src/client/views/news.ejs @@ -1,82 +1,91 @@ - + + News +
-

News

+

<%=t('news.title')%>

-

July 13, 2024:

+

<%=t('news.july13-2024.date')%>


-

The Terms of Service have been updated. Changes made: All games you play on the website may become public information, including the approximate time your account was last active. The terms may be updated at any time, and it is your responsibility to make sure you're up-to-date on them.

+

<%=t('news.july13-2024.tos_update.0')%><%=t('news.july13-2024.tos_update.1')%><%=t('news.july13-2024.tos_update.2')%>

+
+

<%=t('news.july13-2024.game_history_warning')%>
-

Your game history may become available on your profile at a future time.

-

July 9, 2024:

+

<%=t('news.july9-2024.date')%>


-

Infinite Chess is Now Open Source! See, and contribute, to the project on GitHub!

+

<%=t('news.july9-2024.text.0')%><%=t('news.july9-2024.text.1')%>


-

May 27, 2024:

+

<%=t('news.may27-2024.date')%>


-

1.3.2: Added showcase variants for Omega^3 and Omega^4 that were shown in my latest video. Also, the checkmate algorithm is now compatible with multiple kings per side.

+

<%=t('news.may27-2024.text')%>

-

May 24, 2024:

+

<%=t('news.may24-2024.date')%>


-

Update 1.3.1 released! This includes the guide, pop-up tooltips when hovering over the navigation buttons, and links to the discord and game credits on the title page!

+

<%=t('news.may24-2024.text')%>

-

May 14, 2024:

-

Update 1.3 released today! This includes MANY new speed and user experience improvements. Just a few are:

+

<%=t('news.may14-2024.date')%>

+

<%=t('news.may14-2024.text_top')%>


-

The transition to websockets, decreasing the delay when your opponent moves.

-

No longer getting disconnected when you switch tabs.

-

Audible cues when you or someone else creates an invite, or makes a move.

-

Added the 50-move rule.

-

A drum countdown effect is now played at 10 seconds left on the clock.

-

An auto-resignation timer will start if you're opponent goes AFK (with an audible warning).

+

<%=t('news.may14-2024.update_list.0')%>

+

<%=t('news.may14-2024.update_list.1')%>

+

<%=t('news.may14-2024.update_list.2')%>

+

<%=t('news.may14-2024.update_list.3')%>

+

<%=t('news.may14-2024.update_list.4')%>

+

<%=t('news.may14-2024.update_list.5')%>


-

And many others! For the full list, check out the discord!

+

<%=t('news.may14-2024.text_box.0')%><%=t('news.may14-2024.text_box.1')%>

-

Jan 29, 2024:

-

New video released today!

+

<%=t('news.jan29-2024.date')%>

+

<%=t('news.jan29-2024.text')%>

-

Aug 26, 2023:

-

Infinite Chess v1.2 has been released! Introducing Move History. Use the left & right arrow keys to rewind and fast forward the game!

+

<%=t('news.aug26-2023.date')%>

+

<%=t('news.aug26-2023.text_top')%>


-

The system for verifying accounts has been updated to be more reliable! If your account is not verified and you had trouble with the verification link before, please try verifying again!

+

<%=t('news.aug26-2023.text_box')%>

+ +


-

More dev logs are posted on the official discord, and on the chess.com forums!

+

<%=t('news.more_dev_logs.0')%><%=t('news.more_dev_logs.1')%><%=t('news.more_dev_logs.2')%><%=t('news.more_dev_logs.3')%>

+ +
- + <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> diff --git a/src/client/views/play.html b/src/client/views/play.ejs similarity index 75% rename from src/client/views/play.html rename to src/client/views/play.ejs index 728e736b3..2378c5549 100644 --- a/src/client/views/play.html +++ b/src/client/views/play.ejs @@ -1,8 +1,15 @@ - + - - + + + @@ -11,7 +18,6 @@ Infinite Chess - Play - @@ -19,28 +25,29 @@ - - + + +
@@ -50,9 +57,9 @@
- LOADING + <%=t('play.main-menu.loading')%>
@@ -74,65 +81,65 @@

ERROR

- Credits + <%=t('play.main-menu.credits')%>
- + @@ -382,12 +389,12 @@

Board Editing

10:00
-
Player white
+
<%=t('play.footer.player_white')%>
-
White to move
+
<%=t('play.footer.white_to_move')%>
-
Player black
+
<%=t('play.footer.player_black')%>
10:00
@@ -435,13 +442,13 @@

Board Editing

diff --git a/src/client/views/termsofservice.ejs b/src/client/views/termsofservice.ejs new file mode 100644 index 000000000..a701fa263 --- /dev/null +++ b/src/client/views/termsofservice.ejs @@ -0,0 +1,90 @@ + + + + + + + + Terms of Service + + + + + +
+ +
+
+
+

<%=t('terms.title')%>

+ <% if (language !== "en-US") { %> +

<%=t('terms.warning.0')%><%=t('terms.warning.1')%><%=t('terms.warning.2')%>

+ <% } %> +

<%=t('terms.consent')%>

+

<%=t('terms.guardian_consent')%>

+

<%=t('terms.parents_header')%>

+

<%=t('terms.parents_paragraphs.0')%>

+

<%=t('terms.parents_paragraphs.1')%>

+ +

<%=t('terms.fair_play_header')%>

+

<%=t('terms.fair_play_paragraph1.0')%><%=t('terms.fair_play_paragraph1.1')%>

+

<%=t('terms.fair_play_paragraph2')%>

+
    +
  • <%=t('terms.fair_play_rules.0')%>

  • +
  • <%=t('terms.fair_play_rules.1')%>

  • +
  • <%=t('terms.fair_play_rules.2')%>

  • +
+ +

<%=t('terms.cleanliness_header')%>

+

<%=t('terms.cleanliness_rules.0')%>

+

<%=t('terms.cleanliness_rules.1')%>

+ +

<%=t('terms.privacy_header')%>

+

<%=t('terms.privacy_rules.0')%>

+

<%=t('terms.privacy_rules.1')%>

+

<%=t('terms.privacy_rules.2')%>

+

<%=t('terms.privacy_rules.3')%>

+

<%=t('terms.privacy_rules.4.0')%> <%=t('terms.privacy_rules.4.1')%> <%=t('terms.privacy_rules.4.2')%>

+

<%=t('terms.privacy_rules.5')%>

+

<%=t('terms.privacy_rules.6')%>

+ +

<%=t('terms.cookie_header')%>

+

<%=t('terms.cookie_paragraphs.0')%>

+

<%=t('terms.cookie_paragraphs.1')%>

+ +

<%=t('terms.conclusion_header')%>

+

<%=t('terms.conclusion_paragraphs.0')%>

+

<%=t('terms.conclusion_paragraphs.1.0')%> <%=t('terms.conclusion_paragraphs.1.1')%> <%=t('terms.conclusion_paragraphs.1.2')%>

+

<%=t('terms.conclusion_paragraphs.2.0')%> <%=t('terms.conclusion_paragraphs.2.1')%><%=t('terms.conclusion_paragraphs.2.2')%>

+

<%=t('terms.conclusion_paragraphs.3')%>

+

<%=t('terms.conclusion_paragraphs.4')%>

+

<%=t('terms.conclusion_paragraphs.5.0')%> <%=t('terms.conclusion_paragraphs.5.1')%>

+ +

<%=t('terms.update')%>

+

<%=t('terms.thanks')%>

+
+
+ <%- include(`${viewsfolder}/components/footer`, {t:t, languages:languages, language:language}) %> + + diff --git a/src/client/views/termsofservice.html b/src/client/views/termsofservice.html deleted file mode 100644 index de60752dc..000000000 --- a/src/client/views/termsofservice.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - Terms of Service - - - - -
- -
-
-
-

Terms of Service

-

By using this site, you agree to abide by the following terms. If you do not agree, you must immediately stop using the site.

-
    -
  • -

    If you are under 18, you must receive consent from a parent or legal guardian to use this site and to create an account.

    -
  • -
-

Parents:

-

There is an algorithm in place for prohibiting users setting their name to common cuss words. At this time there is no method of communication between members on the site.

-

Currently, members cannot set their own profile picture. There is a plan to allow this feature. At that time we will do our best to prevent innapropriate profile pictures.

- -

Fair Play

-

You cannot create more than 1 account. If you would like to change the email address associated with your account, contact us.

-

To keep play fun and fair for everyone, you must NOT:

-
    -
  • Modify or manipulate the code in any way, including but not limited to: Using console commands, local overrides, custom scripts, modifying http requests, etc. This can be done to intentionally break the game, or to give you an advantage.

  • -
  • In rated games, receive help/advice from another person or program as to what you should play. (Creating an engine is ok and encouraged, but you must limit its use to unrated games)

  • -
  • Trade elo points with other people by purposefully losing with intent to boost the elo of your opponent, or by receiving elo points from an opponent that intends to lose to boost your own rating. This abuses the system and creates unaccurate ratings according to your level of skill.

  • -
- -

Cleanliness

-

In all your language on the site, you must remain clean, no vulgarity or cursing. You cannot bully, harass, or threaten anyone, or do anything that is illegal. You cannot spam other users or forums.

-

You cannot not upload imagery to your profile that is innapropriate, suggestive, or gory. Doing so may result in a ban or termination of your account.

- -

Privacy

-

Currently, the only personal information we collect is email. This is with intent to verify users' accounts, and provide a means of proving who they are when they request a password reset or account termination. We do not send any promotional emails or offers. We do not share any user's email address with anyone else.

-

InfiniteChess.org may collect data about your usage on the site, including your ip address. This is intended to help prevent attacks from bots and other unwanted entities, and to keep accurate statistics in the database. This is NOT your home address.

-

All games you play on this website become public information. If you wish to remain anonymous, do not share your username with friends or family. If this is your desire, it is your responsibility to make sure no one finds out your username is associated with your human identity.

-

Your account online status, and the approximate last time you were active on the website, is also public information.

-

While InfiniteChess.org will strive to keep everyone's account and personal information safe to the best of our ability, in the event of a hack or data leak, you may not press charges on us. If a data leak ever happens, users will be notified on the News page.

-

There is no content available on the site for purchase. Any other personal information is not collected.

-

To have your private information deleted from our servers, you may delete your account through your profile page. The only thing with ties to your username that we will NOT delete, is your game history, because all games are public information.

- -

Cookie Policy

-

This site uses cookies, which are small text files that are stored in your browser, and sent to the server when connections are made. The purpose of these cookies are to: Validate your login session, validate your browser belongs to the chess game it says it's in, and to store user game preferences so they can keep their preferences when they re-visit the site. The site does not use 3rd-party cookies, cookies are not shared with external parties.

-

Cookies are required for this site and game to function correctly. If you do not want the site to store cookies, you must stop using the site. You can navigate to your browsers preferences to delete existing cookies. By continuing to use this site, you are consenting to the use of cookies.

- -

Conclusion

-

Any violations of these terms may result in a ban or termination of your account. InfiniteChess.org wants to be able to give everyone the opportunity to play and have fun! But, we reserve the right to, at any time, ban or terminate the accounts of any users, for reasons that need not to be disclosed. Charges may not be pressed against us.

-

These terms of service may be modified at any point. It is YOUR responsibility to make sure you stay updated on the latest changes! When these terms of service receive an update, that information will be posted on the News page. If, at the time of a terms-of-service update, you do not agree with the new terms, you must immediately stop using the website. You may delete your account from your profile page. If you delete your account, all your private information and account data will be deleted, EXCEPT we do not delete your game history associated with your username, that is public information. -

This site is open source. You may copy or distribute anything on this website as long as you follow the conditions outlined in the license terms! If this link is broken, it is your responsibility to find the terms.

-

We cannot guarantee the site will be running 100% of the time. We also cannot guarantee that data will never be corrupted.

-

You may not perform any illegal activity on the site.

-

If you have any questions regarding these terms, or any other question about the site, email us!

-

(Last updated: 7/13/24. Added warning that all games played becomes public information, including your account approximate last time active. Also, these terms may be updated at any time, and it is your responsibility to make sure you stay updated.)

-

Thank you!

-
-
- - - diff --git a/src/server/config/setupTranslations.js b/src/server/config/setupTranslations.js new file mode 100644 index 000000000..0df8e15b1 --- /dev/null +++ b/src/server/config/setupTranslations.js @@ -0,0 +1,292 @@ +const i18next = require("i18next"); +const { parse } = require("smol-toml"); +const fs = require("fs"); +const path = require("path"); +const ejs = require("ejs"); +const middleware = require("i18next-http-middleware"); +const xss = require("xss"); + +const translationsFolder = "./translation"; + +/** + * Templates without any external data other than translations. + * Don't insert names with file extensions. + */ +const staticTranslatedTemplates = [ + "createaccount", + "credits", + "index", + "login", + "member", + "news", + "play", + "termsofservice", + "errors/400", + "errors/401", + "errors/404", + "errors/409", + "errors/500", +]; + +// Removed because tags are no longer in whitelist +/* +const link_white_list = [ + "/", + "/login", + "/news", + "/play", + "/credits", + "/termsofservice", + "/createaccount", + "https://github.com/pychess/pychess/blob/master/LICENSE", + "mailto:infinitechess.org@gmail.com", + "https://www.patreon.com/Naviary", + "https://math.colgate.edu/~integers/og2/og2.pdf", + "https://chess.stackexchange.com/questions/42480/checkmate-in-%cf%89%c2%b2-moves-with-finitely-many-pieces", + "https://math.colgate.edu/~integers/og2/og2.pdf", + "https://math.colgate.edu/~integers/rg4/rg4.pdf", + "https://commons.wikimedia.org/wiki/Category:SVG_chess_pieces", + "https://creativecommons.org/licenses/by-sa/3.0/deed.en", + "https://www.gnu.org/licenses/gpl-3.0.en.html", + "https://greenchess.net/info.php?item=downloads", + "https://github.com/lichess-org/lila/blob/master/COPYING.md", + "https://www.gnu.org/licenses/agpl-3.0.en.html", + "https://www.lcg.ufrj.br/WebGL/hws.edu-examples/doc-bump/gl-matrix.js.html", + "https://github.com/tsevasa/infinite-chess-notation", + "https://github.com/Infinite-Chess/infinitechess.org/blob/main/docs/COPYING.md", + "https://discord.gg/NFWFGZeNh5", + "https://www.chess.com/forum/view/chess-variants/infinite-chess-app-devlogs-and-more", + "https://github.com/Infinite-Chess/infinitechess.org", + "https://discord.com/channels/1114425729569017918/1114427288776364132/1240014519061712997" +]; +*/ + +const xss_options = { + whiteList: { + // a: ["href", "target"], + b: [], + strong: [], + i: [], + em: [], + mark: [], + small: [], + del: [], + ins: [], + sub: [], + sup: [], + }, + onTagAttr: function (tag, name, value, isWhiteAttr) { + /*if (!isWhiteAttr && !(value === 'href' && name === 'a')) { + console.warn( + `Atribute "${name}" of "${tag}" tag with value "${value.trim()}" failed to pass XSS filter. `, + ); + }*/ + }, + safeAttrValue: function (tag, name, value) { + /*if ( + tag === "a" && + name === "href" && + link_white_list.includes(value.trim()) + ) { + return value; + } else if (name === "href") { + console.warn( + `Atribute "${name}" of "${tag}" tag with value "${value.trim()}" failed to pass XSS filter. `, + ); + }*/ + }, +}; +const custom_xss = new xss.FilterXSS(xss_options); + +function html_escape_array(array) { + let escaped = []; + for (const member of array) { + escaped.push(html_escape(member)); + } + return escaped; +} + +function html_escape_object(object) { + let escaped = {}; + for (const key of Object.keys(object)) { + escaped[key] = html_escape(object[key]); + } + return escaped; +} + +/** + Function to iterate over arrays and objects and html escape strings + */ +function html_escape(value) { + switch (typeof value) { + case "object": + if (value.constructor.name == `Object`) { + return html_escape_object(value); + } else if (value.constructor.name == `Array`) { + return html_escape_array(value); + } else { + throw "Unhandled object type while escaping"; + } + break; + case "string": + return custom_xss.process(value); // Html escape strings + break; + case "number": + return value; + break; + default: + throw "Unhandled type while escaping"; + break; + } +} + +/** + * Removes keys from `object` based on string of format 'foo.bar'. + * @param {key_string} String representing key that has to be deleted in format 'foo.bar'.s + * @param {object} Object that is target of the removal. + * @returns Copy of `object` with deleted values + */ +function remove_key(key_string, object) { + const keys = key_string.split("."); + + let currentObj = object; + for (let i = 0; i < keys.length - 1; i++) { + if (currentObj[keys[i]]) { + currentObj = currentObj[keys[i]]; + } + } + + if (currentObj[keys.at(-1)]) { + delete currentObj[keys.at(-1)]; + } + return object; +} + +/** + * Removes outdated translations. + * @param {object} Object of translations. + * @param {changelog} `changes.json` file. + * @returns + */ +function removeOutdated(object, changelog) { + const version = object.version; + // Filter out versions that are older than version of current language + const filtered_keys = Object.keys(changelog).filter(function x(y) { + return version < parseInt(y); + }); + + let key_strings = []; + for (key of filtered_keys) { + key_strings = key_strings.concat(changelog[key]); + } + // Remove duplicate + key_strings = Array.from(new Set(key_strings)); + + let object_copy = object; + for (let key_string of key_strings) { + object_copy = remove_key(key_string, object_copy); + } + + return object_copy; +} + +function loadTranslationsFolder(folder) { + const resources = {}; + const files = fs.readdirSync(folder); + const changelog = JSON.parse( + fs.readFileSync(path.join(folder, "changes.json")).toString(), + ); + files + .filter(function y(x) { + return x.endsWith(".toml"); + }) + .forEach((file) => { + resources[file.replace(".toml", "")] = { + default: html_escape( + removeOutdated( + parse(fs.readFileSync(path.join(folder, file)).toString()), + changelog, + ), + ), + }; + }); + + return resources; +} + +/** + * Creates file or directory if it doesn't exist + * @param {filePath} Path to create. + */ +function createFileOrDir(filePath) { + if (!fs.existsSync(filePath)) { + if (path.extname(filePath) === "") { + fs.mkdirSync(filePath, { recursive: true }); + } else { + const dirPath = path.dirname(filePath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + fs.writeFileSync(filePath, ""); + } + } +} + +/** + * Generates translated versions of templates in staticTranslatedTemplates + */ +function translateStaticTemplates(translations) { + const languages = Object.keys(translations); + + const languages_list = []; + for (let language of languages) { + languages_list.push({ code: language, name: translations[language]['default'].name }); + } + + const templatesPath = path.join(__dirname, "..", "..", "..", "dist", "views"); + for (let language of languages) { + for (let template of staticTranslatedTemplates) { + createFileOrDir(path.join(templatesPath, language, template + ".html")); // Make sure it exists + fs.writeFileSync( + path.join(templatesPath, language, template + ".html"), + ejs.render( + // Read EJS template + fs + .readFileSync(path.join(templatesPath, template + ".ejs")) + .toString(), + { + // Function for translations + t: function (key, options = {}) { + options.lng = language; // Make sure language is correct + return i18next.t(key, options); + }, + languages: languages_list, + language: language, + viewsfolder: path.join(__dirname, '..', '..', '..', 'dist', 'views'), + }, + ), + ); + } + } +} + +/** + * Initializes i18next, loads languages from .toml files, saves translated versions of templates. + * **Should be ran only once**. + */ +function initTranslations() { + const translations = loadTranslationsFolder(translationsFolder); + + i18next.use(middleware.LanguageDetector).init({ + // debug: true, + preload: Object.keys(translations), // List of languages to preload to make sure they are loaded before rendering views + resources: translations, + defaultNS: "default", + fallbackLng: "en-US", + }); + translateStaticTemplates(translations); // Compiles static files +} + +module.exports = { + initTranslations, +}; diff --git a/src/server/controllers/authController.js b/src/server/controllers/authController.js index da57974f6..40cf75751 100644 --- a/src/server/controllers/authController.js +++ b/src/server/controllers/authController.js @@ -95,7 +95,7 @@ async function testPasswordForRequest(req, res) { const hashedPassword = getHashedPassword(usernameLowercase); if (!usernameCaseSensitive || !hashedPassword) { - res.status(401).json({ 'message': 'Username is invalid'}); // Unauthorized, username not found + res.status(401).json({ 'message': "ws-invalid_username"}); // Unauthorized, username not found return false; } @@ -108,7 +108,7 @@ async function testPasswordForRequest(req, res) { const match = await bcrypt.compare(password, hashedPassword); if (!match) { logEvents(`Incorrect password for user ${usernameCaseSensitive}!`, "loginAttempts.txt", { print: true }); - res.status(401).json({ 'message': 'Password is incorrect'}); // Unauthorized, password not found + res.status(401).json({ 'message': "ws-incorrect_password"}); // Unauthorized, password not found onIncorrectPassword(browserAgent, usernameCaseSensitive); return false; } @@ -128,7 +128,7 @@ async function testPasswordForRequest(req, res) { function verifyBodyHasLoginFormData(req, res) { if (!req.body) { // Missing body console.log(`User sent a bad login request missing the body!`) - res.status(400).send('Bad Request'); // 400 Bad request + res.status(400).send("ws-bad_request"); // 400 Bad request return false; } @@ -136,13 +136,13 @@ function verifyBodyHasLoginFormData(req, res) { if (!username || !password) { console.log(`User ${username} sent a bad login request missing either username or password!`) - res.status(400).json({ 'message': 'Username and password are required.'}); // 400 Bad request + res.status(400).json({ 'message': "ws-username_and_password_required"}); // 400 Bad request return false; } if (typeof username !== "string" || typeof password !== "string") { console.log(`User ${username} sent a bad login request with either username or password not a string!`) - res.status(400).json({ 'message': 'Username and password must be a string.'}); // 400 Bad request + res.status(400).json({ 'message': "ws-username_and_password_string"}); // 400 Bad request return false; } @@ -217,7 +217,12 @@ function rateLimitLogin(res, browserAgent) { // Too many attempts! if (timeSinceLastAttemptsSecs <= loginAttemptData[browserAgent].cooldownTimeSecs) { // Still on cooldown - res.status(401).json({ 'message': `Failed to login, try again in ${Math.floor(loginAttemptData[browserAgent].cooldownTimeSecs - timeSinceLastAttemptsSecs)} seconds.`}); + res.status(401).json({ + 'message': "ws-login_failure_retry_in", + 'login_cooldown': Math.floor(loginAttemptData[browserAgent].cooldownTimeSecs - timeSinceLastAttemptsSecs) + }); + // res.status(401).json({ 'message': `Failed to login, try again in ${Math.floor(loginAttemptData[browserAgent].cooldownTimeSecs - timeSinceLastAttemptsSecs)} seconds.`}); + // Reset the timer to auto-delete them from the login attempt data // if they haven't tried in a while. // This is so it doesn't get cluttered over time diff --git a/src/server/controllers/createaccountController.js b/src/server/controllers/createaccountController.js index 4f836bf72..4966d380b 100644 --- a/src/server/controllers/createaccountController.js +++ b/src/server/controllers/createaccountController.js @@ -76,7 +76,7 @@ const profainWords = [ const createNewMember = async (req, res) => { if (!req.body) { console.log(`User sent a bad create account request missing the whole body!`) - return res.status(400).send('Bad Request'); // 400 Bad request + return res.status(400).send("ws-bad_request"); // 400 Bad request } // First make sure we have all 3 variables. let { username, email, password } = req.body; @@ -202,20 +202,20 @@ function checkUsernameAvailable(req, res) { const doUsernameFormatChecks = function (username, res) { // First we check the username's length - if (username.length < 3 || username.length > 20) return res.status(400).json({ 'message': 'Username must be between 3-20 characters'}); + if (username.length < 3 || username.length > 20) return res.status(400).json({ 'message': "ws-username_length"}); // Then the format - if (!onlyLettersAndNumbers(username)) return res.status(400).json({ 'message': 'Username must only contain letters A-Z and numbers 0-9'}); + if (!onlyLettersAndNumbers(username)) return res.status(400).json({ 'message': "ws-username_letters"}); // Then check if the name's taken const usernameLowercase = username.toLowerCase(); // Make sure the username isn't taken!! - if (doesMemberExist(usernameLowercase)) return res.status(409).json({ 'conflict': 'That username is taken'}); + if (doesMemberExist(usernameLowercase)) return res.status(409).json({ 'conflict': "ws-username_taken"}); // Then check if the name's reserved - if (reservedUsernames.includes(usernameLowercase)) return res.status(409).json({ 'conflict': 'That username is taken'}); // Code for reserved (but the users don't know that!) + if (reservedUsernames.includes(usernameLowercase)) return res.status(409).json({ 'conflict': "ws-username_taken"}); // Code for reserved (but the users don't know that!) // Lastly check for profain words - if (checkProfanity(usernameLowercase)) return res.status(409).json({ 'conflict': 'That username contains a word that is not allowed'}); + if (checkProfanity(usernameLowercase)) return res.status(409).json({ 'conflict': "ws-username_bad_word"}); return true; // Everything's good, no conflicts! } @@ -234,13 +234,13 @@ const checkProfanity = function (string) { } const doEmailFormatChecks = function (string, res) { - if (string.length > 320) return res.status(400).json({ 'message': 'Your email is too looooooong.'}); // Max email length - if (!isValidEmail(string)) return res.status(400).json({ 'message': 'This is not a valid email'}); - if(!isEmailAvailable(string.toLowerCase())) return res.status(409).json({ 'conflict': 'This email is already in use'}); + if (string.length > 320) return res.status(400).json({ 'message': "ws-email_too_long"}); // Max email length + if (!isValidEmail(string)) return res.status(400).json({ 'message': "ws-email_invalid"}); + if(!isEmailAvailable(string.toLowerCase())) return res.status(409).json({ 'conflict': "ws-email_in_use"}); if (isEmailBanned(string)) { const errMessage = `Banned user with email ${string.toLowerCase()} tried to recreate their account!`; logEvents(errMessage, 'bannedIPLog.txt', { print: true }) - return res.status(409).json({ 'conflict': 'You are banned.'}); + return res.status(409).json({ 'conflict': "ws-you_are_banned"}); } return true; } @@ -253,9 +253,9 @@ const isValidEmail = function (string) { const doPasswordFormatChecks = function (password, res) { // First we check password length - if (password.length < 6 || password.length > 72) return res.status(400).json({ 'message': 'Password must be 6-72 characters long'}); - if (!isValidPassword(password)) return res.status(400).json({ 'message': 'Password is in an incorrect format'}); - if (password.toLowerCase() === 'password') return res.status(400).json({ 'message': "Password must not be 'password'"}); + if (password.length < 6 || password.length > 72) return res.status(400).json({ 'message': "ws-password_length"}); + if (!isValidPassword(password)) return res.status(400).json({ 'message': "ws-password_format"}); + if (password.toLowerCase() === 'password') return res.status(400).json({ 'message': "ws-password_password"}); return true; } diff --git a/src/server/controllers/logoutController.js b/src/server/controllers/logoutController.js index 51ebada9d..d40ffa7ed 100644 --- a/src/server/controllers/logoutController.js +++ b/src/server/controllers/logoutController.js @@ -13,7 +13,7 @@ const handleLogout = async (req, res) => { // Is refreshToken in db? const foundMemberKey = findMemberFromRefreshToken(refreshToken) - if (!foundMemberKey) return res.status(409).json({'message':'No member has that refresh token (already logged out)'}); // Forbidden + if (!foundMemberKey) return res.status(409).json({'message':"ws-refresh_token_not_found"}); // Forbidden // Delete refreshToken in db. // This also saves the members file. diff --git a/src/server/controllers/memberController.js b/src/server/controllers/memberController.js index 0ca579c42..4b9ca8a6f 100644 --- a/src/server/controllers/memberController.js +++ b/src/server/controllers/memberController.js @@ -25,7 +25,7 @@ const getMemberData = async (req, res) => { // Load their case sensitive username const username = getUsernameCaseSensitive(usernameLowercase); - if (!username) return res.status(404).json({message: 'Member not found'}) + if (!username) return res.status(404).json({message: "ws-member_not_found"}) // Load their data const joinDate = getJoinDate(usernameLowercase); diff --git a/src/server/controllers/refreshTokenController.js b/src/server/controllers/refreshTokenController.js index b14d16883..6cc5fda3e 100644 --- a/src/server/controllers/refreshTokenController.js +++ b/src/server/controllers/refreshTokenController.js @@ -22,7 +22,7 @@ const handleRefreshToken = (req, res) => { // If we have cookies AND there's a jwt property.. if (!cookies?.jwt) { assignOrRenewBrowserID(req, res); - return res.status(401).json({'message' : 'No refresh token found (expired session)'}); + return res.status(401).json({'message' : "ws-refresh_token_expired"}); } const refreshToken = cookies.jwt; const foundMemberKey = findMemberFromRefreshToken(refreshToken); @@ -30,7 +30,7 @@ const handleRefreshToken = (req, res) => { // As soon as they log out, we will have removed the token from the database. if (!foundMemberKey) { assignOrRenewBrowserID(req, res); - return res.status(403).json({'message':'No member has that refresh token'}); // Forbidden + return res.status(403).json({'message': "ws-refresh_token_not_found_logged_out"}); // Forbidden } // Evaluate jwt @@ -41,7 +41,7 @@ const handleRefreshToken = (req, res) => { // If the token is expired/wrong, or the payload is different if (err || foundMemberKey !== decoded.username) { assignOrRenewBrowserID(req, res); - return res.status(403).json({'message' : 'Refresh token expired or tampered'}); + return res.status(403).json({'message' : "ws-refresh_token_invalid"}); } // Else verified. Send them new access token! const accessToken = jwt.sign( diff --git a/src/server/controllers/removeAccountController.js b/src/server/controllers/removeAccountController.js index 80d54fda9..72bd903de 100644 --- a/src/server/controllers/removeAccountController.js +++ b/src/server/controllers/removeAccountController.js @@ -27,7 +27,7 @@ async function removeAccount(req, res) { // Check to make sure they're logged in if (req.user !== usernameLowercase) { logEvents(`User ${req.user} tried to delete account of ${usernameLowercase}!!`, 'hackLog.txt', { print: true }) - return res.status(403).json({'message' : 'Forbidden. This is not your account.'}); + return res.status(403).json({'message' : "ws-forbidden_wrong_account"}); } // The delete account request doesn't come with the username @@ -44,7 +44,7 @@ async function removeAccount(req, res) { return res.send('OK'); // 200 is default code } else { logEvents(`Can't delete ${usernameLowercase}'s account. They do not exist.`, 'hackLog.txt', { print: true }); - return res.status(404).json({'message' : 'Failed to delete account. Account not found.'}); + return res.status(404).json({'message' : "ws-deleting_account_not_found"}); } } diff --git a/src/server/controllers/verifyAccountController.js b/src/server/controllers/verifyAccountController.js index 954c3cfc9..13c971985 100644 --- a/src/server/controllers/verifyAccountController.js +++ b/src/server/controllers/verifyAccountController.js @@ -29,7 +29,7 @@ const verifyAccount = async function (req, res) { if (req.user !== usernameLowercase) { // Forbid them if they are logged in and NOT who they're wanting to verify! const errText = `User ${req.user} attempted to verify ${usernameLowercase}!` logEvents(errText, 'hackLog.txt', { print: true }); - res.status(403).send("Forbidden. This is not your account."); + res.status(403).send("ws-forbidden_wrong_account"); return; } diff --git a/src/server/middleware/errorHandler.js b/src/server/middleware/errorHandler.js index e36b9ce27..8b0ef4c2c 100644 --- a/src/server/middleware/errorHandler.js +++ b/src/server/middleware/errorHandler.js @@ -6,7 +6,7 @@ function errorHandler(err, req, res, next) { logEvents(errMessage, 'errLog.txt', { print: true }); // This sends back to the browser the error, instead of the ENTIRE stack which is PRIVATE. - const messageForClient = "Sorry, there was a server error! Please go back." + const messageForClient = "ws-server_error" res.status(500).send(messageForClient); // 500: Server error } diff --git a/src/server/middleware/middleware.js b/src/server/middleware/middleware.js index 01547e0a4..1807a1750 100644 --- a/src/server/middleware/middleware.js +++ b/src/server/middleware/middleware.js @@ -17,6 +17,10 @@ const { verifyJWT } = require('./verifyJWT'); const { rateLimit } = require('./rateLimit') const { protectedStatic } = require('./protectedStatic') +// External translation middleware +const i18next = require('i18next'); +const middleware = require('i18next-http-middleware'); + // Other imports const { useOriginWhitelist } = require('../config/config'); @@ -44,6 +48,12 @@ function configureMiddleware(app) { app.use(credentials); // Handle credentials check. Must be before CORS. + app.use( + middleware.handle(i18next, { + removeLngFromUrl: false + }) + ) + /** * Cross Origin Resource Sharing * diff --git a/src/server/middleware/rateLimit.js b/src/server/middleware/rateLimit.js index 6dde47b54..aeaa193a1 100644 --- a/src/server/middleware/rateLimit.js +++ b/src/server/middleware/rateLimit.js @@ -82,18 +82,18 @@ function rateLimit(req, res, next) { const clientIP = getClientIP(req); if (!clientIP) { console.log('Unable to identify client IP address') - return res.status(500).json({ message: 'Unable to identify client IP address' }); + return res.status(500).json({ message: "ws-unable_to_identify_client_ip" }); } if (isIPBanned(clientIP)) { const logThis = `Banned IP ${clientIP} tried to connect! ${req.headers.origin} ${clientIP} ${req.method} ${req.url} ${req.headers['user-agent']}`; logEvents(logThis, 'bannedIPLog.txt', { print: true }); - return res.status(403).json({ message: 'You are banned' }); + return res.status(403).json({ message: "ws-you_are_banned_by_server" }); } if (rateLimitHash[clientIP] > maxRequestsPerMinute) { // Rate limit them (too many requests sent) console.log(`IP ${clientIP} has too many requests! Count: ${rateLimitHash[clientIP]}`); - return res.status(429).json({ message: 'Too Many Requests. Try again soon.' }); + return res.status(429).json({ message: "ws-too_many_requests_to_server" }); } // Increment their recent connection count, diff --git a/src/server/middleware/send404.js b/src/server/middleware/send404.js index a5befad25..c76759294 100644 --- a/src/server/middleware/send404.js +++ b/src/server/middleware/send404.js @@ -3,11 +3,11 @@ const path = require('path'); function send404(req, res) { res.status(404); if (req.accepts('html')) { - res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'views', 'errors', '404.html')); + res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'views', req.i18n.resolvedLanguage, 'errors', '404.html'), {t: req.t}); } else if (req.accepts('json')) { - res.json({ error: "404 Not Found" }); + res.json({ error: "ws-not_found" }); } else { - res.type('txt').send("404 Not Found"); + res.type('txt').send("ws-not_found"); } } diff --git a/src/server/middleware/verifyRoles.js b/src/server/middleware/verifyRoles.js index 2240912c2..0645e4991 100644 --- a/src/server/middleware/verifyRoles.js +++ b/src/server/middleware/verifyRoles.js @@ -18,7 +18,7 @@ function ensureOwner(req, res, next) { if (isOwner(req)) return next(); // Valid, you may pass if (req.user) { // Logged in, but don't have the right permissions console.log(`Forbid user ${req.user} from accessing an owner-protected resource!`) - return res.status(403).send("Forbidden."); + return res.status(403).send("ws-forbidden"); } // NOT logged in... Redirect them to the login page, // BUT add a query parameter that will bring them back here after logging in! @@ -38,7 +38,7 @@ function ensurePatron(req, res, next) { if (isPatron(req)) return next(); // Pass if (req.user) { // Logged in, but don't have the right permissions console.log(`Stopped user ${req.user} from accessing a patron-protected resource.`) - return res.status(403).send("Unauthorized. This page is patron-exclusive."); + return res.status(403).send("ws-unauthorized_patron_page"); } // NOT logged in... Redirect them to the login page, // BUT add a query parameter that will bring them back here after logging in! diff --git a/src/server/routes/createaccount.js b/src/server/routes/createaccount.js index a22d49aec..758b3927f 100644 --- a/src/server/routes/createaccount.js +++ b/src/server/routes/createaccount.js @@ -6,12 +6,10 @@ const path = require('path'); const createaccountController = require('../controllers/createaccountController') const {getRegisterData, checkEmailAssociated, checkUsernameAvailable} = require('../controllers/createaccountController'); -const createAccountHTMLPath = path.join(__dirname, '..', '..', '..', 'dist', 'views', 'createaccount.html'); - router.get('/', (req, res) => { - res.sendFile(createAccountHTMLPath); + res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'views', req.i18n.resolvedLanguage, 'createaccount.html')); }) router.post('/', createaccountController.createNewMember); diff --git a/src/server/routes/member.js b/src/server/routes/member.js index c52f61b67..0323ab28b 100644 --- a/src/server/routes/member.js +++ b/src/server/routes/member.js @@ -8,7 +8,7 @@ const {removeAccount} = require('../controllers/removeAccountController'); router.get('/:member', (req, res) => { - res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'views', 'member.html')); + res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'views', req.i18n.resolvedLanguage, 'member.html'), {t: req.t}); }); router.get('/:member/data', getMemberData); diff --git a/src/server/routes/root.js b/src/server/routes/root.js index 69ee85905..e747602f1 100644 --- a/src/server/routes/root.js +++ b/src/server/routes/root.js @@ -1,74 +1,98 @@ - -const express = require('express'); +const express = require("express"); const router = express.Router(); // Here we define router instead of an app -const path = require('path'); -const fs = require('fs'); +const path = require("path"); +const fs = require("fs"); +const i18next = require("i18next"); -const { handleLogin } = require('../controllers/authController'); -const { handleRefreshToken } = require('../controllers/refreshTokenController'); -const { handleLogout } = require('../controllers/logoutController'); -const { verifyAccount } = require('../controllers/verifyAccountController'); -const { ensureOwner, ensurePatron } = require('../middleware/verifyRoles'); -const { getCachedHTML, sendCachedHTML } = require('../utility/HTMLScriptInjector'); +const { handleLogin } = require("../controllers/authController"); +const { handleRefreshToken } = require("../controllers/refreshTokenController"); +const { handleLogout } = require("../controllers/logoutController"); +const { verifyAccount } = require("../controllers/verifyAccountController"); +const { ensureOwner, ensurePatron } = require("../middleware/verifyRoles"); -const htmlDirectory = path.join(__dirname, '..', '..', '..', 'dist', 'views'); +const htmlDirectory = path.join(__dirname, "..", "..", "..", "dist", "views"); // router.get('/skeleton(.html)?', (req, res) => { // If it starts & ends with '/', OR it's '/index.html' OR '/index' -// res.sendFile(path.join(__dirname, '../views', 'skeleton.html')); +// res.render(path.join(__dirname, '../views', 'skeleton.ejs')); // }); // Send the index/root / home page -router.get('^/$|/index(.html)?', (req, res) => { // If it starts & ends with '/', OR it's '/index.html' OR '/index' - res.sendFile(path.join(htmlDirectory, 'index.html')); +router.get("^/$|/index(.html)?", (req, res) => { + // If it starts & ends with '/', OR it's '/index.html' OR '/index' + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "index.html"), + ); }); -router.get('/credits(.html)?', (req, res) => { - res.sendFile(path.join(htmlDirectory, 'credits.html')); -}) +router.get("/credits(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "credits.html"), + ); +}); -router.get('/play(.html)?', (req, res) => { - // res.sendFile(path.join(__dirname, '../views', 'play.html')); - const htmlFilePath = path.join(htmlDirectory, 'play.html'); - sendCachedHTML(req, res, htmlFilePath) -}) +router.get("/play(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "play.html"), + ); +}); -router.get('/news(.html)?', (req, res) => { - res.sendFile(path.join(htmlDirectory, 'news.html')); -}) +router.get("/news(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "news.html"), + ); +}); -router.get('/login(.html)?', (req, res) => { - res.sendFile(path.join(htmlDirectory, 'login.html')); -}) +router.get("/login(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "login.html"), + ); +}); -router.post('/auth', handleLogin); +router.post("/auth", handleLogin); -router.get('/refresh', handleRefreshToken); +router.get("/refresh", handleRefreshToken); -router.get('/logout', handleLogout); +router.get("/logout", handleLogout); -router.get('/termsofservice(.html)?', (req, res) => { - res.sendFile(path.join(htmlDirectory, 'termsofservice.html')); -}) +router.get("/termsofservice(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "termsofservice.html"), + ); +}); -router.get('/verify/:member/:id', verifyAccount); +router.get("/verify/:member/:id", verifyAccount); -const errorDirectory = path.join(htmlDirectory, 'errors'); +const errorDirectory = path.join(htmlDirectory, "errors"); -router.get('/400(.html)?', (req, res) => { - res.sendFile(path.join(errorDirectory, '400.html')); +router.get("/400(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "errors", "400.html"), + ); }); -router.get('/401(.html)?', (req, res) => { - res.sendFile(path.join(errorDirectory, '401.html')); +router.get("/401(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "errors", "401.html"), + ); }); -router.get('/404(.html)?', (req, res) => { - res.sendFile(path.join(errorDirectory, '404.html')); +router.get("/404(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "errors", "404.html"), + ); }); -router.get('/409(.html)?', (req, res) => { - res.sendFile(path.join(errorDirectory, '409.html')); +router.get("/409(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "errors", "409.html"), + ); }); -router.get('/500(.html)?', (req, res) => { - res.sendFile(path.join(errorDirectory, '500.html')); +router.get("/500(.html)?", (req, res) => { + res.sendFile( + path.join(htmlDirectory, req.i18n.resolvedLanguage, "errors", "500.html"), + ); }); +router.post("/setlanguage", (req, res) => { + res.cookie("i18next", req.i18n.resolvedLanguage); + res.send(""); // Doesn't work without this for some reason +}); module.exports = router; diff --git a/src/server/server.js b/src/server/server.js index 51b66ee75..f62c4c34c 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -7,6 +7,7 @@ initDevEnvironment(); const express = require('express'); const app = express(); const https = require('https'); +const ejs = require('ejs'); // Other imports const configureMiddleware = require('./middleware/middleware') const wsserver = require("./wsserver"); @@ -15,7 +16,14 @@ const getCertOptions = require('./config/certOptions'); const { DEV_BUILD } = require('./config/config'); const { saveMembersIfChangesMade } = require('./controllers/members'); const { saveRolesIfChangesMade } = require('./controllers/roles'); +const { initTranslations } = require('./config/setupTranslations'); +// Initiate translations +initTranslations(); + +// Set EJS as the view engine +app.engine("html", ejs.renderFile); +app.set("view engine", "html"); const httpsServer = https.createServer(getCertOptions(DEV_BUILD), app); app.disable('x-powered-by'); // This removes the 'x-powered-by' header from all responses. diff --git a/src/server/utility/HTMLScriptInjector.js b/src/server/utility/HTMLScriptInjector.js index c8ddc874f..6ab16d3ed 100644 --- a/src/server/utility/HTMLScriptInjector.js +++ b/src/server/utility/HTMLScriptInjector.js @@ -1,151 +1,72 @@ - /** - * This module, at runtime, creates a list of a few - * of our htmls into which we want to manually inject - * some javascript before sharing to the client. - * (Currently, htmlscript.js is injected in full into play.html. - * Also, calls to the game scripts in /src/client/scripts/game are injected into play.html) - * + * This module is called by build.mjs to inject javascript code into play.ejs. + * Currently, htmlscript.js is injected in full into play.ejs. + * Also, calls to the game scripts in /src/client/scripts/game are injected into play.ejs. + * * We keep the javascript separate in development, so as * to not break Intellisense's sense of the javascript project. * (We wouldn't get useful JSDoc dropdown info otherwise) */ -const fs = require('fs'); -const path = require('path'); -const glob = require('glob'); -const { getReservedUsernames, getProfainWords } = require('../controllers/createaccountController'); - -/** - * A cache object that has file paths for the keys, and for the values- - * html documents with our desired injected javascript. - */ -let htmlCache = {}; - -/** - * Injects a JavaScript file's content into an HTML file after - * a specified tag, then cache's that content into {@link htmlCache} - * @param {string} htmlFilePath - The path of the html document in the project - * @param {string} jsFilePath - The path of the javascript file containing the desired javascript code to inject. - * @param {Object} [stringInjection] - Optional argument: An object of the form {string: "htmlstring", injectafter: "tags"}. - * The string will be insterted after the specified tags into the html doc - */ -function prepareAndCacheHTML(htmlFilePath, jsFilePath, stringInjection = {}) { - injectScriptIntoHeadFromPaths(htmlFilePath, jsFilePath, stringInjection) - .then(modifiedHTML => { - addHTMLToCache(htmlFilePath, modifiedHTML) - }) - .catch(error => console.error("Failed to inject script: ", error)); -} +const fs = require("fs"); +const path = require("path"); +const glob = require("glob"); /** - * Injects a JavaScript file's content into an HTML file + * Injects a JavaScript file's content into an HTML or EJS file * after a specified tag, returning the new content. - * RECEIVES file paths, not raw data. - * @param {string} htmlFilePath - The path of the html document in the project + * @param {string} htmlFilePath - The path of the html/ejs document in the project * @param {string} jsFilePath - The path of the javascript file containing the desired javascript code to inject. + * @param {string} injectAfterTag - The HTML tag after which the JavaScript code will be injected (typically the ``). * @param {Object} [stringInjection] - Optional argument: An object of the form {string: "htmlstring", injectafter: "tags"}. * The string will be insterted after the specified tags into the html doc - * @returns {Promise} - A promise that resolves with the modified HTML content, or rejects with an error message. - */ -function injectScriptIntoHeadFromPaths(htmlFilePath, jsFilePath, stringInjection = {}) { - return new Promise((resolve, reject) => { - // Read the JavaScript file - fs.readFile(jsFilePath, 'utf8', (jsErr, jsData) => { - if (jsErr) { - reject("Error reading the JavaScript file: " + jsErr); - return; - } - // Read the HTML file and inject the script tag - fs.readFile(htmlFilePath, 'utf8', (htmlErr, htmlData) => { - if (htmlErr) { - reject("Error reading the HTML file: " + htmlErr); - return; - } - - let modifiedHTML = insertScriptInHead(htmlData, jsData) - - // Inject the string of the optional argument "stringInjection" into the HTML file, if applicable - if (Object.keys(stringInjection).length != 0){ - modifiedHTML = modifiedHTML.replace(stringInjection.injectafter, `${stringInjection.injectafter}${stringInjection.string}`); - } - resolve(modifiedHTML); - }); - }); - }); -} - -/** - * Inserts the given javascript code into a script tag in the html header. - * Receives RAW, pre-read data. - * @param {string} html - The preloaded html file - * @param {string} js - The javascript code - */ -function insertScriptInHead(html, js) { - // Create a script tag with the JavaScript content - const scriptTag = ``; - // Inject the script tag before the specified closing tag - return html.replace('', `${scriptTag}`); -} - -/** - * Adds the modified html to the cache. - * @param {string} path - The path of the html (typically inside /dist) - * @param {string} contents - The modified contents of the html. - */ -function addHTMLToCache(path, contents) { - htmlCache[path] = contents; -} - -/** - * Sends our cached HTML file with injected code at the specified path, to the client. - * If the HTML content is not ready or doesn't exist, an error message will be sent instead. - * @param {Object} req - The HTTP request object. - * @param {Object} res - The HTTP response object. - * @param {string} htmlFilePath - The path to the HTML file, relative to this module. - */ -function sendCachedHTML(req, res, htmlFilePath) { - const cachedHTML = getCachedHTML(htmlFilePath); - if (cachedHTML === false) res.status(503).send('Content is still being prepared, please refresh!'); - else res.send(cachedHTML); -} - -/** - * Returns our cached html file with injected code at the specified path. - * If it's not ready, or we don't have it, we'll return false. - * @param {string} htmlFilePath - The path to the html file, relative to this module. - * @returns {string | false} - The injected html, or *false* if it's not ready or doesn't exist. + * @returns {string} modifiedHTML - Modified html. */ -function getCachedHTML(htmlFilePath) { - // console.log(Object.keys(htmlCache)) - return htmlCache[htmlFilePath] || false; +function injectScript(htmlFilePath, jsFilePath, injectAfterTag, stringInjection = {}) { + // Read the JavaScript file + const jsData = fs.readFileSync(jsFilePath, "utf8"); + // Create a script tag with the JavaScript content + const scriptTag = ``; + + // Read the HTML file and inject the script tag + const htmlData = fs.readFileSync(htmlFilePath, "utf8"); + // Inject the script tag before the specified closing tag + let modifiedHTML = htmlData.replace(injectAfterTag, `${injectAfterTag}${scriptTag}`); + + // Inject the string of the optional argument "stringInjection" into the HTML file, if applicable + if (Object.keys(stringInjection).length != 0) { + modifiedHTML = modifiedHTML.replace(stringInjection.injectafter, `${stringInjection.injectafter}${stringInjection.string}`); + } + return modifiedHTML; } -// Inject the scripts we want... -{ - // Prepare the injection of our (potentially minified) htmlscript.js script into play.html - const htmlFilePath = path.join(__dirname, '..', '..', '..', 'dist', 'views', 'play.html'); - const jsFilePath = path.join(__dirname, '..', '..', '..', 'dist', 'scripts', 'game', 'htmlscript.js'); - - // Prepare the injection of references to all other game scripts into play.html - const HMTL_scriptcall_p1 = `` - const injectafter_string = `${HMTL_scriptcall_p1}validation.js${HMTL_scriptcall_p2}` // we will insert the other game scripts after this exact place in the HTML code - - // Automatically build the list of scripts to be injected into play.html by including everything in scripts/game except for htmlscripts.js - let HTML_callGame_JS_string = ""; - const game_JSscripts = glob.sync(`./dist/scripts/game/**/*.js`).filter(file => {return !/htmlscript\.js/.test(file)}); - // Convert the list of scripts into an explicit HTML string that imports them all - for (file of game_JSscripts){ - const js_filename = file.split(/(\\|\/)+/).slice(4).join(""); // discard "dist/scripts/" - HTML_callGame_JS_string += `\n\t\t${HMTL_scriptcall_p1}${js_filename}${HMTL_scriptcall_p2}`; - } - - // Finally, perform the injection into play.html - prepareAndCacheHTML(htmlFilePath, jsFilePath, {string: HTML_callGame_JS_string, injectafter: injectafter_string}); +// Inject the scripts we want into play.ejs +function injectScriptsIntoPlayEjs() { + // Prepare the injection of our (potentially minified) htmlscript.js script into play.ejs + const htmlFilePath = path.join(__dirname, "..", "..", "..", "dist", "views", "play.ejs"); + const jsFilePath = path.join(__dirname, "..", "..", "..", "dist", "scripts", "game", "htmlscript.js"); + + // Prepare the injection of references to all other game scripts into play.ejs + const HMTL_scriptcall_p1 = ``; + const injectafter_string = ""; // we will insert the other game scripts after this exact place in the HTML code + + // Automatically build the list of scripts to be injected into play.ejs by including everything in scripts/game except for htmlscripts.js + let HTML_callGame_JS_string = ""; + const game_JSscripts = glob.sync(`./dist/scripts/game/**/*.js`).filter((file) => { return !/htmlscript\.js/.test(file); }); + // Convert the list of scripts into an explicit HTML string that imports them all + for (file of game_JSscripts) { + const js_filename = file.split(/(\\|\/)+/).slice(4).join(""); // discard "dist/scripts/" + HTML_callGame_JS_string += `\n\t\t${HMTL_scriptcall_p1}${js_filename}${HMTL_scriptcall_p2}`; + } + + // Return html with injected javascript + return injectScript(htmlFilePath, jsFilePath, "", { + string: HTML_callGame_JS_string, + injectafter: injectafter_string, + }); } module.exports = { - getCachedHTML, - sendCachedHTML + injectScriptsIntoPlayEjs, }; diff --git a/translation/changes.json b/translation/changes.json new file mode 100644 index 000000000..1c146027e --- /dev/null +++ b/translation/changes.json @@ -0,0 +1,4 @@ +{ + "1": ["inital commit of en-US-toml"], + "1.0.1": ["added login.login_button on line 448"] +} \ No newline at end of file diff --git a/translation/en-US.toml b/translation/en-US.toml new file mode 100644 index 000000000..64a967503 --- /dev/null +++ b/translation/en-US.toml @@ -0,0 +1,533 @@ +name = "English" # Name of language +direction = "ltr" # Change to "rtl" for right to left languages +version = "1.0.1" + +[header] +home = "Home" +play = "Play" +news = "News" +login = "Log In" +createaccount = "Create Account" + +[footer] +contact = "Contact us" +terms_of_service = "Terms of Service" +source_code = "Source Code" +language = "Language" + +[header.javascript] +js-profile = "Profile" +js-logout = "Log Out" +js-login = "Log In" +js-createaccount = "Create Account" + +[member.javascript] +js-confirm_delete = "Are you sure you want to delete your account? This CANNOT be undone! Click OK to enter your password." +js-enter_password = "Enter your password to PERMANENTLY delete your account:" + +[index] +secondary_title = "The official website for playing live!" +what_is_it_title = "What is it?" +what_is_it_pargaraphs = [ +"Infinite Chess is a variant of chess in which there are no borders, much larger than your familiar 8x8 board. The queen, rooks, and bishops have no limit to how far they can move per turn. Pick any natural number up to infinity!", +"With no limit to how far you can move, there are positions possible where the doomsday clock, or checkmate-in-blank, number is represented by the first infinite ordinal, omega ω. In fact, researches have discovered that any countable ordinal is achievable for the checkmate clock!", +"As you can imagine, there are infinite possibilites for starting configurations, many of which you can play competitively! Your end goal is still checkmate, which requires new tactics seeing as there are no walls to trap the enemy king against. Games don't typically last much longer than normal chess games. Pawns also still promote at ranks 1 & 8!", +] +how_to_title = "How can I play?" +how_to_paragraph = ["The current version release is 1.3.2 on the ","Play"," page!"] +about_title = "About the Project" +about_paragraphs = [ +"I am Naviary. Since I first discovered Infinite Chess (the concept existed long before this website), I have been very intrigued by it and its possibilities! Up to just recently, playing has been quite difficult, requiring chess.com members to create images of the current board and send them back and forth for every move played. Due to this, not many people know about or have been able to play this.", +["It is my goal to build a way to make this easily playable for everyone, and grow a community surrounding it. I have spent countless hours of my own time on this website, up-keeping, and developing the game. I have many more ideas that will keep me occupied for some time. While I wish to keep this free-to-play, life has requirements, to help support me financially please consider joining my", "Patreon"] +] +patreon_title = "Patreon Supporters" + +[credits] +title = "Credits" +copyright = "Anything on the website that is not listed below is copyright of www.InfiniteChess.org" +variants_heading = "Variants" +variants_credits = [ +"Core designed by Andreas Tsevas.", +"Space designed by Andreas Tsevas.", +"Space Classic designed by Andreas Tsevas.", +"Coaip (Chess on an Infinite Plane) designed by Vickalan.", +"Pawn Horde designed by Inaccessible Cardinal.", +"Abundance designed by Clicktuck Suskriberz.", +"Pawndard by SexiLexi.", +"Classical+ by SexiLexi.", +"Knightline by Inaccessible Cardinal.", +"Knighted Chess by cycy98.", +"designed by Cory Evans and Joel Hamkins.", +"designed by Andreas Tsevas.", +"designed by Cory Evans and Joel Hamkins.", +"designed by Cory Evans, Joel Hamkins, and Norman Lewis Perlmutter.", +] +textures_heading = "Textures" +textures_licensed_under = "textures licensed under the" +textures_credits = [ +"Gold coin by Quolte.", +] +sounds_heading = "Sounds" +sounds_credits = [ +["Some sounds are provided by the", "project under the"], +"Other sounds created by Naviary.", +] +code_heading = "Code" +code_credits = [ +"by Brandon Jones and Colin MacKenzie IV.", +"by Andreas Tsevas and Naviary.", +] + +[member] +verify_message = "Please check your email to verify your account." +resend_message = ["Didn't get one? Check your spam folder. Also, ", "send it again.", " If you still can't find it, ", "message me."] +verify_confirm = "Thank you! Your account has been verified." +rating = "Elo rating:" +joined = "Joined:" +seen = ["Seen:", " ago"] +reveal_info = "Show Account Info" +account_info_heading = "Account Info" +email = "Email:" +delete_account = "Delete account" +password_reset_message = ["To change your username, email or password, ", "contact us."] + +[create-account] +title = "Create Account" +username = "Username:" +email = "Email:" +password = "Password:" +create_button = "Create Account" +argreement = ["I agree to the ", "Terms of Service"] + +[create-account.javascript] +js-username_specs = "Username must be atleast 3 characters long, and only contain letters A-Z and numbers 0-9" +js-username_tooshort = "Username must be at least 3 characters long" +js-username_wrongenc = "Username must only contain letters A-Z and numbers 0-9" +js-email_invalid = "This is not a valid email" +js-email_inuse = "This email is already in use" +js-pwd_incorrect_format = "Password is in an incorrect format" +js-pwd_too_short = "Password must be 6+ characters long" +js-pwd_too_long = "Password can't be over 72 characters long" +js-pwd_not_pwd = "Password must not be 'password'" + +[play.main-menu] +credits = "Credits" +play = "Play" +guide = "Guide" +editor = "Board Editor" +loading = "LOADING" +error = "ERROR" + +[play.guide] +title = "Guide" +rules = "Rules" +rules_paragraphs = [ +"The rules to Infinite Chess are almost identical to classical chess, except the board is infinite in all directions! These are the only notes and changes you need to be aware of:", +"Pieces with sliding moves, such as the rooks, bishops, and queen, have no limit to how far they can move in one turn! As long as their path is unobstructed, you can move millions!", +["In the \"Classical\" default variant, white pawns promote at rank 8, and black pawns at rank 1. In this image, this is indicated by the thin black lines, they are faint, see if you can spot them! Pawns only need to reach the opposite line to promote, ", "not", " cross it."], +"Square notation is no longer described by the letter and rank number (i.e., a1), rather, each square is defined by a pair of x and y coordinates. The a1 square has become (1,1), and the h8 square has become (8,8). On desktop devices, the coordinate your mouse is over is displayed at the top of the screen.", +"All other rules are the same as in classical chess, such as checkmate, stalemate, 3-fold repetition, the 50-move rule, castling, en passant, etc.!" +] +careful_heading = "Be Careful!" +careful_paragraphs = [ +"The openness of the infinite board means it is very easy to exploit forks, pins, and skewers. Your backside is often very vulnerable. Watch out for tactics like this! Be creative about forming protection for your king and rooks! Opening strategy is very different from classical chess.", +"Many other variants have been created with the aim of strengthening your backside." +] +controls_heading = "Controls" +controls_paragraph = "A lot of controls are intuitive, such as clicking and dragging the board to move around, and scrolling to zoom in and out, but let's take a look at the other controls you have at your disposal!" +keybinds = [ +" to move around.", +["Space", " and ", "Shift", " to zoom in and out."], +["Escape", " to pause the game."], +["Tab", " toggles the arrow indicators on the edges of the screen pointing to pieces off-screen. By default, this mode is set to \"Defense\", which shows an arrow for pieces that can move to your location from where they're at. But ", "tab", " can switch this to mode \"All\", or \"Off\", of which \"All\" reveals all pieces on those orthogonals and diagonals, Whether they can move orthogonally or diagonally. This setting can also be toggled in the pause menu."], +" will toggle \"Edit Mode\" in local games. This allows you to move any piece anywhere else on the board! Very useful for analyzing." +] +controls_paragraph2 = "Those are the major controls you need to know. But here are some extras if you ever find yourself needing them!" +keybinds_extra = [ +" will reset the rendering of the pieces. This is useful if they turn invisible. This glitch can happen if you move extreme distances (like 1e21).", +" will toggle the rendering of the navigation and game info bars, which can be useful for recording. Streaming and making videos on the game is welcome!", +" will toggle your FPS meter. This displays the number of times the game is updating per second, not always the number of frames rendered, as the game skips rendering when nothing visible has changed, to save compute.", +" will toggle icon-rendering. These are the clickable mini-pictures of the pieces when you zoom out far enough. In imported games with over 50,000 pieces this is automatically toggled off, as it is a performance throddler, but they can be toggled back on with ", +[" (backtick, or the same key as ", ") will toggle Debug mode."], +] +fairy_heading = "Fairy Pieces" +fairy_paragraph = "You already know what you need to know to play the default \"Classical\" variant. Fairy chess pieces are not used in conventional chess, but are incorporated into other variants! If you find yourself in a variant with some pieces you haven't seen before, let's learn how they work here!" +editing_heading = "Board Editing" +editing_paragraphs = [ +["There is an external ", "board editor", " currently available on a public google sheet! It includes instructions for how to use it. This requires some basic google sheets knowledge. After setup, you will be able to create and import custom positions into the game via the \"Paste Game\" button in the options menu!"], +"To play a custom position with a friend, have them join a private invite, then both of you paste the game code before you start playing!", +"An in-game board editor is still planned.", +] +back = "Back" + +[play.guide.pieces] +chancellor = {name="Chancellor", description="Moves like a rook and a knight combined."}, +archbishop = {name="Archbishop", description="Moves like a bishop and a knight combined."}, +amazon = {name="Amazon", description="Moves like a queen and a knight combined. This is the strongest piece in the game!"}, +guard = {name="Guard", description="Moves like a king, except it is not susceptible to check or checkmate."}, +hawk = {name="Hawk", description="Leaps exactly 2 or 3 squares in any direction."}, +centaur = {name="Centaur", description="Moves like a knight and a guard combined."}, +obstacle = {name="Obstacle", description="A neutral piece (not controlled by either player) that blocks movement, but can be captured."}, +void = {name="Void", description="A neutral piece (not controlled by either player) that represents the absence of board. Pieces may not move through or on top of it."}, + + +[play.play-menu] +title = "Play - Online" +colors = "Colors" +online = "Online" +local = "Local" +computer = "Computer" +variant = "Variant" +variants = [ +"Classical", +"Classical+", +"Chess on an Infinite Plane", +"Pawndard", +"Knighted Chess", +"Knightline", +"Core", +"Standarch", +"Pawn Horde", +"Space Classic", +"Space", +"Obstocean", +"Abundance", +"Amazon Chandelier", +"Containment", +"Classical - Limit 7", +"Coaip - Limit 7", +"Chess", +"Experimental: Classical - KOTH", +"Experimental: Coaip - KOTH", +"Showcase: Omega", +"Showcase: Omega^2", +"Showcase: Omega^3", +"Showcase: Omega^4", +] +clock = "Clock" +minutes = "m" +seconds = "s" +infinite_time = "Infinite Time" +color = "Color" +piece_colors = ["Random", "White", "Black"] +private = "Private" +no = "No" +yes = "Yes" +rated = "Rated" +join_games = "Join Existing - Active Games:" +private_invite = "Private Invite:" +your_invite = "Your Invite Code:" +create_invite = "Create Invite" +join = "Join" +copy = "Copy" +back = "Back" +code = "Code" + +[play.footer] +white_to_move = "White to move" +player_white = "Player white" +player_black = "Player black" + +[play.pause] +title = "Paused" +resume = "Resume" +arrows = "Arrows: Defense" +perspective = "Perspective: Off" +copy = "Copy Game" +paste = "Paste Game" +main_menu = "Main Menu" + +[play.javascript] +guest_indicator = "(Guest)" +you_indicator = "(You)" +white_to_move = "White to move" +black_to_move = "Black to move" +your_move = "Your move" +their_move = "Their move" +lost_network = "Lost network." +failed_to_load = "One or more resources failed to load. Please refresh." +planned_feature = "This feature is planned!" +main_menu = "Main Menu" +resign_game = "Resign Game" +abort_game = "Abort Game" +arrows_off = "Arrows: Off" +arrows_defense = "Arrows: Defense" +arrows_all = "Arrows: All" +toggled = "Toggled" +menu_online = "Play - Online" +menu_local = "Play - Local" +invite_error_digits = "Invite code needs to be 5 digits." +invite_copied = "Copied invite code to clipboard." +move_counter = "Move:" +constructing_mesh = "Constructing mesh" +rotating_mesh = "Rotating mesh" +lost_connection = "Lost connection." +please_wait = "Please wait a moment to perform this task." +webgl_unsupported = "Your browser does not support WebGL. This game requires that to function. Please update your browser." +bigints_unsupported = "BigInts are not supported. Please upgrade your browser.\nBigInts are needed to make the board infinite." +shaders_failed = "Unable to initialize the shader program:" +failed_compiling_shaders = "An error occurred compiling the shaders:" + +[play.javascript.copypaste] +copied_game = "Copied game to clipboard!" +cannot_paste_in_public = "Cannot paste game in a public match!" +cannot_paste_after_moves = "Cannot paste game after moves are made!" +clipboard_denied = "Clipboard permission denied. This might be your browser." +clipboard_invalid = "Clipboard is not in valid ICN notation." +game_needs_to_specify = "Game needs to specify either the 'Variant' metadata, or 'startingPosition' property." +invalid_wincon_white = "White has an invalid win condition" +invalid_wincon_black = "Black has an invalid win condition" +pasting_game = "Pasting game..." +pasting_in_private = "Pasting a game in a private match will cause a desync if your opponent doesn't do the same!" +piece_count = "Piece count" +exceeded = "exceeded" +changed_wincon = "Changed checkmate win conditions to royalcapture, and toggled off icon rendering. Hit 'P' to re-enable (not recommended)." +loaded_from_clipboard = "Loaded game from clipboard!" +loaded = "Loaded game!" +slidelimit_not_number = "slideLimit gamerule must be a number. Received" + +[play.javascript.rendering] +on = "On" +off = "Off" +icon_rendering_off = "Toggled off icon rendering." +icon_rendering_on = "Toggled on icon rendering." +toggled_debug = "Toggled Debug Mode:" +toggled_edit = "Toggled Edit Mode:" +perspective = "Perspective" +perspective_mode_on_desktop = "Perspective mode is available on desktop!" +movement_tutorial = "WASD to move. Space & shift to zoom." +regenerated_pieces = "Regenerated pieces." + +[play.javascript.invites] +move_mouse = "Move the mouse to reconnect." +unknown_action_received_1 = "Unknown action" +unknown_action_received_2 = "received from the server in the invites subscription!" +cannot_cancel = "Cannot cancel invite of undefined ID." +you_indicator = "(You)" +you_are_white = "You're: White" +you_are_black = "You're: Black" +random = "Random" +accept = "Accept" +cancel = "Cancel" +create_invite = "Create Invite" +cancel_invite = "Cancel Invite" +join_existing_active_games = "Join Existing - Active Games:" + +[play.javascript.onlinegame] +afk_warning = "You are AFK." +opponent_afk = "Opponent is AFK." +opponent_disconnected = "Opponent has disconnected." +opponent_lost_connection = "Opponent has lost connection." +auto_resigning_in = "Auto-resigning in" +auto_aborting_in = "Auto-aborting in" +not_logged_in = "You are not logged in. Please login to reconnect to this game." +game_no_longer_exists = "Game no longer exists." +another_window_connected = "Another window has connected." +server_restarting = "Server restarting shortly..." +server_restarting_in = "Server restarting in" +minute = "minute" +minutes = "minutes" + +[play.javascript.websocket] +no_connection = "No connection." +reconnected = "Reconnected." +unable_to_identify_ip = "Unable to identify IP." +online_play_disabled = "Online play disabled. Cookies not supported. Try a different browser." +too_many_requests = "Too many requests. Try again soon." +message_too_big = "Message too big." +too_many_sockets = "Too many sockets" +origin_error = "Origin error." +connection_closed = "Connection closed unexpectedly. Server message:" +please_report_bug = "This should never happen, please report this bug!" + +[play.javascript.results] +you_checkmate = "You win by checkmate!" +you_time = "You win on time!" +you_resignation = "You win by resignation!" +you_disconnect = "You win by abandonment!" +you_royalcapture = "You win by royal capture!" +you_allroyalscaptured = "You win by all royals captured!" +you_allpiecescaptured = "You win by all pieces captured!" +you_threecheck = "You win by three-check!" +you_koth = "You win by king of the hill!" +you_generic = "You win!" +draw_stalemate = "Draw by stalemate!" +draw_repetition = "Draw by repetition!" +draw_moverule = ["Draw by the ", "-move-rule!"] +draw_insuffmat = "Draw by insufficient material!" +draw_generic = "Draw!" +aborted = "Game aborted." +opponent_checkmate = "You lose by checkmate!" +opponent_time = "You lose on time!" +opponen_resignation = "You lose by resignation!" +opponent_disconnect = "You lose by abandonment!" +opponent_royalcapture = "You lose by royal capture!" +opponent_allroyalscaptured = "You lose by all royals captured!" +opponent_allpiecescaptured = "You lose by all pieces captured!" +opponent_threecheck = "You lose by three-check!" +opponent_koth = "You lose by king of the hill!" +opponent_generic = "You lose!" +white_checkmate = "White wins by checkmate!" +black_checkmate = "Black wins by checkmate!" +bug_checkmate = "This is a bug, please report. Game ended by checkmate." +white_time = "White wins on time!" +black_time = "Black wins on time!" +bug_time = "This is a bug, please report. Game ended on time." +white_royalcapture = "White wins by royal capture!" +black_royalcapture = "Black wins by royal capture!" +bug_royalcapture = "This is a bug, please report. Game ended by royal capture." +white_allroyalscaptured = "White wins by all royals captured!" +black_allroyalscaptured = "Black wins by all royals captured!" +bug_allroyalscaptured = "This is a bug, please report. Game ended by all royals captured." +white_allpiecescaptured = "White wins by all pieces captured!" +black_allpiecescaptured = "Black wins by all pieces captured!" +bug_allpiecescaptured = "This is a bug, please report. Game ended by all pieces captured." +white_threecheck = "White wins by three-check!" +black_threecheck = "Black wins by three-check!" +bug_threecheck = "This is a bug, please report. Game ended by three-check." +white_koth = "White wins by king of the hill!" +black_koth = "Black wins by king of the hill!" +bug_koth = "This is a bug, please report. Game ended by king of the hill." +bug_generic = "This is a bug, please report!" + +[terms] # Translations are disabled for now, only language is en-US +title = "Terms of Service" +warning = ["THIS DOCUMENT IS NOT LEGALLY BINDING. We are only accountable for the English version of this document. This translation is provided solely for general informational purposes. You can access the official English version ", "here", "."] +consent = "By using this site, you agree to abide by the following terms. If you do not agree, you must immediately stop using the site." +guardian_consent = "If you are under 18, you must receive consent from a parent or legal guardian to use this site and to create an account." +parents_header = "Parents" +parents_paragraphs = [ +"There is an algorithm in place for prohibiting users setting their name to common cuss words. At this time there is no method of communication between members on the site.", +"Currently, members cannot set their own profile picture. There is a plan to allow this feature. At that time we will do our best to prevent innapropriate profile pictures.", +] +fair_play_header = "Fair Play" +fair_play_paragraph1 = ["You cannot create more than 1 account. If you would like to change the email address associated with your account, ", "contact us."] +fair_play_paragraph2 = "To keep play fun and fair for everyone, you must NOT:" +fair_play_rules = [ +"Modify or manipulate the code in any way, including but not limited to: Using console commands, local overrides, custom scripts, modifying http requests, etc. This can be done to intentionally break the game, or to give you an advantage.", +"In rated games, receive help/advice from another person or program as to what you should play. (Creating an engine is ok and encouraged, but you must limit its use to unrated games)", +"Trade elo points with other people by purposefully losing with intent to boost the elo of your opponent, or by receiving elo points from an opponent that intends to lose to boost your own rating. This abuses the system and creates unaccurate ratings according to your level of skill." +] +cleanliness_header = "Cleanliness" +cleanliness_rules = [ +"In all your language on the site, you must remain clean, no vulgarity or cursing. You cannot bully, harass, or threaten anyone, or do anything that is illegal. You cannot spam other users or forums.", +"You cannot not upload imagery to your profile that is innapropriate, suggestive, or gory. Doing so may result in a ban or termination of your account." +] +privacy_header = "Privacy" +privacy_rules = [ +"Currently, the only personal information we collect is email. This is with intent to verify users' accounts, and provide a means of proving who they are when they request a password reset. We do not send any promotional emails or offers. We do not share any user's email address with anyone else.", +"InfiniteChess.org may collect data about your usage on the site, including your ip address. This is intended to help prevent attacks from bots and other unwanted entities, and to keep accurate statistics in the database. This is NOT your home address.", +"All games you play on this website become public information. If you wish to remain anonymous, do not share your username with friends or family. If this is your desire, it is your responsibility to make sure no one finds out your username is associated with your human identity.", +"Your account online status, and the approximate last time you were active on the website, is also public information.", +["While InfiniteChess.org will strive to keep everyone's account and personal information safe to the best of our ability, in the event of a hack or data leak, you may not press charges on us. If a data leak ever happens, users will be notified on the ", "News", "page."], +"There is no content available on the site for purchase. Any other personal information is not collected.", +"To have your private information deleted from our servers, you may delete your account through your profile page. The only thing with ties to your username that we will NOT delete, is your game history, because all games are public information.", +] +cookie_header = "Cookie Policy" +cookie_paragraphs = [ +"This site uses cookies, which are small text files that are stored in your browser, and sent to the server when connections are made. The purpose of these cookies are to: Validate your login session, validate your browser belongs to the chess game it says it's in, and to store user game preferences so they can keep their preferences when they re-visit the site. The site does not use 3rd-party cookies, cookies are not shared with external parties.", +"Cookies are required for this site and game to function correctly. If you do not want the site to store cookies, you must stop using the site. You can navigate to your browsers preferences to delete existing cookies. By continuing to use this site, you are consenting to the use of cookies." +] +conclusion_header = "Conclusion" +conclusion_paragraphs = [ +"Any violations of these terms may result in a ban or termination of your account. InfiniteChess.org wants to be able to give everyone the opportunity to play and have fun! But, we reserve the right to, at any time, ban or terminate the accounts of any users, for reasons that need not to be disclosed. Charges may not be pressed against us.", +["These terms of service may be modified at any point. It is YOUR responsibility to make sure you stay updated on the latest changes! When these terms of service receive an update, that information will be posted on the", "News", "page. If, at the time of a terms-of-service update, you do not agree with the new terms, you must immediately stop using the website. You may delete your account from your profile page. If you delete your account, all your private information and account data will be deleted, EXCEPT we do not delete your game history associated with your username, that is public information."], +["This site is open source. You may copy or distribute anything on this website as long as you follow the conditions outlined in", "the license terms", "! If this link is broken, it is your responsibility to find the terms."], +"We cannot guarantee the site will be running 100% of the time. We also cannot guarantee that data will never be corrupted.", +"You may not perform any illegal activity on the site.", +["If you have any questions regarding these terms, or any other question about the site,", "email us!"] +] +update = "(Last updated: 7/13/24. Added warning that all games played may become public information, including your account's approximate last time active. Also, these terms may be updated at any time, and it is your responsibility to make sure you stay updated.)" +thanks = "Thank you!" + +[login] +title = "Log In" +username = "Username:" +password = "Password:" +forgot_password = ["Forgot? ", "Email us."] +login_button = "Log In" + +[error-pages] # Messages shown on some error pages explaining what went wrong +400_message = "Invalid parameters were received." +409_message = ["There may have been a clashing username or email. Please ", "reload", ", the page."] +500_message = "This isn't supposed to happen. There is some debugging to be done!" + +########### NEWS ########### + +[news] +title = "News" +more_dev_logs = ["More dev logs are posted on the ", "official discord", ", and on the ", "chess.com forums!"] + +[news.july13-2024] +date = "July 13, 2024:" +tos_update = ["The ", "Terms of Service", " have been updated. Changes made: All games you play on the website may become public information, including the approximate time your account was last active. The terms may be updated at any time, and it is your responsibility to make sure you're up-to-date on them."] +game_history_warning = "Your game history may become available on your profile at a future time." + +[news.july9-2024] +date = "July 9, 2024:" +text = ["Infinite Chess is Now Open Source! See, and contribute, to the project ", "on GitHub!"] + +[news.may27-2024] +date = "May 27, 2024:" +text = "1.3.2: Added showcase variants for Omega^3 and Omega^4 that were shown in my latest video. Also, the checkmate algorithm is now compatible with multiple kings per side." + +[news.may24-2024] +date = "May 24, 2024:" +text = "Update 1.3.1 released! This includes the guide, pop-up tooltips when hovering over the navigation buttons, and links to the discord and game credits on the title page!" + +[news.may14-2024] +date = "May 14, 2024:" +text_top = "Update 1.3 released today! This includes MANY new speed and user experience improvements. Just a few are:" +update_list = [ +"The transition to websockets, decreasing the delay when your opponent moves.", +"No longer getting disconnected when you switch tabs.", +"Audible cues when you or someone else creates an invite, or makes a move.", +"Added the 50-move rule.", +"A drum countdown effect is now played at 10 seconds left on the clock.", +"An auto-resignation timer will start if you're opponent goes AFK (with an audible warning)." +] +text_box = ["And many others! For the full list, check out ", "the discord!"] + +[news.jan29-2024] +date = "Jan 29, 2024:" +text = "New video released today!" + +[news.aug26-2023] +date = "Aug 26, 2023:" +text_top = "Infinite Chess v1.2 has been released! Introducing Move History. Use the left & right arrow keys to rewind and fast forward the game!" +text_box = "The system for verifying accounts has been updated to be more reliable! If your account is not verified and you had trouble with the verification link before, please try verifying again!" + +[server.javascript] +ws-invalid_username = "Username is invalid" +ws-incorrect_password = "Password is incorrect" +ws-username_and_password_required = "Username and password are required." +ws-username_and_password_string = "Username and password must be a string." +ws-login_failure_retry_in = "Failed to login, try again in" +ws-seconds = "seconds" # unit of time +ws-second = "second" # unit of time +ws-username_length = "Username must be between 3-20 characters" +ws-username_letters = "Username must only contain letters A-Z and numbers 0-9" +ws-username_taken = "That username is taken" +ws-username_bad_word = "That username contains a word that is not allowed" +ws-email_too_long = "Your email is too looooooong." +ws-email_invalid = "This is not a valid email" +ws-email_in_use = "This email is already in use" +ws-you_are_banned = "You are banned." +ws-password_length = "Password must be 6-72 characters long" +ws-password_format = "Password is in an incorrect format" +ws-password_password = "Password must not be 'password'" +ws-refresh_token_not_found_logged_out = "No member has that refresh token (already logged out)" +ws-refresh_token_not_found = "No member has that refresh token" +ws-refresh_token_expired = "No refresh token found (expired session)" +ws-refresh_token_invalid = "Refresh token expired or tampered" +ws-member_not_found = "Member not found" +ws-forbidden_wrong_account = "Forbidden. This is not your account." +ws-deleting_account_not_found = "Failed to delete account. Account not found." +ws-server_error = "Sorry, there was a server error! Please go back." +ws-unable_to_identify_client_ip = "Unable to identify client IP address" +ws-you_are_banned_by_server = "You are banned" +ws-too_many_requests_to_server = "Too Many Requests. Try again soon." +ws-bad_request = "Bad Request" +ws-not_found = "404 Not Found" +ws-forbidden = "Forbidden." +ws-unauthorized_patron_page = "Unauthorized. This page is patron-exclusive." \ No newline at end of file diff --git a/translation/fr-FR.toml b/translation/fr-FR.toml new file mode 100644 index 000000000..b79b3c2be --- /dev/null +++ b/translation/fr-FR.toml @@ -0,0 +1,533 @@ +name = "Français" # Name of language +direction = "ltr" +version = "1.0.1" + +[header] +home = "Accueil" +play = "Jouer" +news = "Actualités" +login = "Connexion" +createaccount = "Créer un compte" + +[footer] +contact = "Contactez nous" +terms_of_service = "Conditions d'utilisation" +source_code = "Code Source" +language = "Langue" + +[header.javascript] +js-profile = "Profil" +js-logout = "Déconnexion" +js-login = "Connexion" +js-createaccount = "Créer un compte" + +[member.javascript] +js-confirm_delete = "Êtes vous sur de vouloir supprimer votre compte? Cette action est DÉFINITIVE! Cliquez sur 'OK' pour rentrer votre mot de passe." +js-enter_password = "Entrez votre mot de passe pour supprimer votre compte DÉFINITIVEMENT:" + +[index] +secondary_title = "Le site officiel pour jouer en direct!" +what_is_it_title = "Qu'est ce que c'est?" +what_is_it_pargaraphs = [ +"Infinite Chess est une variante des échecs dans laquelle il n'y pas de bordures, on est bien loin du plateau de 8x8 cases classique. La reine, les tours et les fous n'ont pas de limites à leur distance de déplacement. Vous pouvez vous déplacez sur n'importe quel case, de 0 à l'infini!", +"Sans limite à la distance que vous pouvez parcourir, il y a des positions dans lesquelles l'horloge de la mort, ou le nombre de coups avant le mat, est représenté par le premier ordinal infini: omega ω. En fait, les recherches ont prouvé que le nombre de coups avant un mat peut être représenté par tout ordinal dénombrable!", +"Comme vous pouvez l'imaginer, il y a une infinité de positions de départ possibles dont beaucoup que vous pouvez jouer de façon compétitive! Le but final reste de faire un échec et mat, il est donc nécessaire de développer des nouvelles techniques vu qu'il n'y pas de murs pour coincer le roi ennemi. Normalement, les parties ne durent pas plus longtemps que dans un jeu d'échec classique. Les pions font leur promotions sur les lignes 1 et 8 respectivement!", +] +how_to_title = "Comment Jouer ?" +how_to_paragraph = ["Le jeu est actuellement en version 1.3.2 sur la page","Jouer","!"] +about_title = "À Propos" +about_paragraphs = [ +"Je m'apelle Naviary. Dès que j'ai découvert Infinite-Chess (le concept existait bien avant ce site), J'ai été très intrigué par celui ci et les possibilités qu'il débloque! Jusqu'à aujourd'hui, y jouer était plutôt difficile, les membres de chess.com devaient créer des images du plateau eux même et se les envoyer à chaque coup. À cause de ça, peu de gens connaissent cette variante.", +["Mon objectif est de créer un site qui permettrait à tout le monde d'y jouer facilement, et de développer une communauté autour. J'ai passé un nombre incalculable d'heures de mon temps libre à faire ce site, à le maintenir et à developper le jeu. J'ai encore beaucoup d'idées qui vont m'occuper pendant pas mal de temps. Bien que je souhaite garder cette plateforme gratuite, la vie a un coût, pour aider à me supporter financiérement vous pouvez rejoindre mon", "Patreon"] +] +patreon_title = "Supporters Patreon" + +[credits] +title = "Credits" +copyright = "Tout le contenu du site qui n'est pas listé ci dessous est la propriété de www.InfiniteChess.org" +variants_heading = "Variantes" +variants_credits = [ +"Core crée par Andreas Tsevas.", +"Space crée crée par Andreas Tsevas.", +"Space Classic crée par Andreas Tsevas.", +"Coaip (Chess on an Infinite Plane) crée par Vickalan.", +"Pawn Horde crée par Inaccessible Cardinal.", +"Abundance crée par Clicktuck Suskriberz.", +"Pawndard par SexiLexi.", +"Classical+ par SexiLexi.", +"Knightline par Inaccessible Cardinal.", +"Knighted Chess par cycy98.", +"crée par Cory Evans and Joel Hamkins.", +"crée par Andreas Tsevas.", +"crée par Cory Evans and Joel Hamkins.", +"crée par Cory Evans, Joel Hamkins, and Norman Lewis Perlmutter.", +] +textures_heading = "Textures" +textures_licensed_under = "textures sous la licence" +textures_credits = [ +"Gold coin par Quolte.", +] +sounds_heading = "Sons" +sounds_credits = [ +["Certains sons viennent du", "project under the"], +"D'autres ont été crées par Naviary.", +] +code_heading = "Code" +code_credits = [ +"par Brandon Jones et Colin MacKenzie IV.", +"par Andreas Tsevas et Naviary.", +] + +[member] +verify_message = "Validez l'email pour vérifier votre compte." +resend_message = ["Vous n'avez pas reçu d'email? Vérifiez vos spams. Aussi, ", "renvoyer.", "Si vous ne recevez toujours rien, envoyez moi un message."] +verify_confirm = "Merci! Votre compte est désormais vérifié." +rating = "Classement elo:" +joined = "A rejoint le:" +seen = ["Vu il y a:", ""] +reveal_info = "Voir les informations du compte" +account_info_heading = "Informations" +email = "Email:" +delete_account = "Supprimer le compte" +password_reset_message = ["Pour changer votre nom d'utilisateur, votre mot de passe ou votre email vous pouvez, ", "nous contacter."] + +[create-account] +title = "Créer un compte" +username = "Nom d'utilisateur:" +email = "Email:" +password = "Mot de Passe:" +create_button = "Créer un compte" +argreement = ["J'accepte les ", "Conditions d'utilisation"] + +[create-account.javascript] +js-username_specs = "Le nom d'utilisateur doit être d'au moins 3 caractères et ne doit contenir que des lettres ou des chiffres" +js-username_tooshort = "Le nom d'utilisateur doit faire au moins caractères" +js-username_wrongenc = "Le nom d'utilisateur ne peut contenir que des lettres ou des chiffres" +js-email_invalid = "Cette adresse mail est invalide" +js-email_inuse = "Cette adresse mail est déjà utilisée" +js-pwd_incorrect_format = "Le mot de passe est dans un format incorrect" +js-pwd_too_short = "Le mot de passe doit contenir au moins 6 caractères" +js-pwd_too_long = "Le mot de passe doit contenir moins de 72 caractères" +js-pwd_not_pwd = "Le mot de passe ne doit pas être 'password'" + +[play.main-menu] +credits = "Credits" +play = "Jouer" +guide = "Guide" +editor = "Editeur de plateau" +loading = "CHARGEMENT" +error = "ERREUR" + +[play.guide] +title = "Guide" +rules = "Règles" +rules_paragraphs = [ +"Les règles de Infinite-Chess sont presques identiques à celles des échecs classiques, la seule différence étant que le plateau est infini dans toutes les directions ! Les seuls changements que vous devez prendre en compte sont listés ci-dessous:", +"Les pièces with avec des déplacements coulissant, comme les tours, les fous et les dames, n'ont pas de limite à la distance qu'elles peuvent parcourir en un tour tant que leur chemin n'est pas obstrué, elles peuvent traverser des millions de cases!", +["Dans la variante par défaut \"Classique\", les pions blancs font leur promotion ligne 8, et les pions noirs ligne 1. Sur cette image, ces lignes sont indiquées par les fines lignes noires, elles sont discrètes, Regardez si vous pouvez les voir! Les pions ont seulement besoin d'atteindre la ligne opposée à leur position de départ pour faire promotion, ", "pas", " de la dépasser."], +"Les cases ne sont plus décrites par leur lettre et leur numéro de ligne (ex: a1) mais plutôt comme une paire de coordonnées x et y. La case a1 devient (1,1), et la case h8 devient (8,8). Sur ordinateur, les coordonées de la case sur laquelle est votre souris sont affichées en haut au milieu de l'écran.", +"Toutes les autres règles sont les mêmes que dans le jeu d'échec classique, comme les échecs-et-mat, les pats, les répétitions de positions, la règle des 50 coups, rocs, prise au passant, etc.!" +] +careful_heading = "Faites Attention!" +careful_paragraphs = [ +"L'ouverture donnée par un plateau infini fait qu'il est très facile d'exploiter les fourchettes, les clouages et les enfilades. La partie du plateau derrière votre Roi est souvent très vulnérable. Faites attention aux tactiques qui l'exploite! Soyez créatif sur la protection que vous formez autour de votre roi et de vos tours! Les ouvertures sont drès différentes de celles des échecs classiques.", +"Beaucoup d'autres variantes ont été crées avec pour but de renforcer l'arrière du roi." +] +controls_heading = "Controles" +controls_paragraph = "Beaucoup de controles sont intuitifs, comme cliquer et faire glisser le plateau pour se déplacer, ou scroller pour zoomer, mais regardons les autres controles que vous pouvez utiliser!" +keybinds = [ +" pour se déplacer.", +["Espace", " et ", "Maj", " pour zoomer et dézoomer."], +["Échap", " pour mettre pause."], +["Tab", " active et désactive les flèches pointant vers les pièces hors champ sur les bords de l'écran. Par défaut elles sont en mode \"Défense\", elle n'affiche une flèche que pour les pièces qui peuvent bouger de leur emplacement à une case sur l'écran. Mais, appuyer sur ", "tab", " permet de passer aux modes \"Tous\", ou \"Off\". Le mode \"Tous\" révèle toutes les pièces qui passent par les lignes et les diagonales visibles à l'écran, selon si elles se déplacent en ligne ou en diagonale. Ce mode peut également être changé depuis le menu pause."], +" va activer/désativer le \"mode d'édition\" dans les parties locales. Le mode d'édition vous permet déplacer n'importe quelle pièce n'importe où sur le plateau. Très utile pour les analyses." +] +controls_paragraph2 = "Ce sont les principaux contrôles que vous devez connaître. Mais il y en a quelques uns en plus si vous en avez besoin!" +keybinds_extra = [ +" va reset l'affichage des pièces. C'est utile si elles deviennent invisibles. Ce qui peut arriver quand elles sont à des distances très éloignées (comme 10^21).", +" va activer/désactiver l'affichage des barres de navigation et d'informations, ce qui peut être utile pour enregistrer. Les streams et les vidéos sur le jeu sont les bienvenus!", +" va activer/désactiver le compte de FPS. Ce qui va afficher le nombre de mises à jour de la partie par seconde (ce qui ne correspond pas toujours au nombre de frames affichées), étant donné que l'écran n'est rafraichit que quand quelque chose de visible change, pour optimiser les performances.", +" va activer/désactiver l'affichage des icônes. Les icônes sont des petites images cliquables des pièces quand vous dézoomez suffisament. Dans les parties importées avec plus de 50 000 pièces, ce paramètre est automatiquement désactivé, étant donné qu'il prend beaucoup de performances, il peut cependant être réactivé.", +[" (backtick, ou la même touche que ", ") va activer/désactiver le mode Debug."], +] +fairy_heading = "Pièces fées" +fairy_paragraph = "Vous savez déjà ce que vous devez savoir pour jouer à la variante par défaut \"Classique\". Les pièces fées ne sont pas utilisés dans les échecs conventionels, mais elles sont incorporées dans d'autres variantes! Si vous vous trouvez dans une variante avec des pièces que vous n'avez jamais vu avant vous pouvez apprendre comment elles marchent ici!" +editing_heading = "Edition de Plateau" +editing_paragraphs = [ +["Il y a un ", "éditeur de plateau", " externe, pour l'instant disponible dans un google sheet publique! Il contient toutes les instructions sur comment l'utiliser (en anglais) et demande un peu de connaissances du fonctionnement des tableurs. Après ça, vous serez capable de créer et d'importer des positions customs dans le jeu via le bouton \"Coller la Position\" dans le menu des options !"], +"Pour jouer sur une position custom avec un ami vous devez le rejoindre via une invitation privée, puis vous devez tous les deux copier et coller le code de la partie avant de commencer à jouer !", +"Un editeur de plateau en jeu est toujours prévu.", +] +back = "Retour" + +[play.guide.pieces] +chancellor = {name="Chancellier", description="Se déplace comme une tour et un cavalier combinés."} +archbishop = {name="Archifou", description="Se déplace comme un fou et un cavalier combinés."} +amazon = {name="Amazone", description="Se déplace comme une reine et un cavalier combinés. C'est la pièce fée la plus forte!"} +guard = {name="Garde", description="Se déplace comme le roi mais ne peut pas être mis en échec ou échec et mat."} +hawk = {name="Faucon", description="Se déplace d'exactement 2 ou 3 cases dans chaque direction. Peut sauter au dessus d'autres pièces comme un cavalier."} +obstacle = {name="Obstacle", description="Une pièce neutre (controlés par aucun des joueurs) qui bloque les mouvements mais peut être capturé."}, +centaur = {name="Centaure", description="Se déplace comme un cavalier et un garde combinés."} +void = {name="Vide", description="Une pièce neutre (controlés par aucun des joueurs) qui represente l'absence de cases sur le plateau. Les pièces ne peuvent pas se déplacer par dessus ou à travers."} + + +[play.play-menu] +title = "Jouer - En Ligne" +colors = "Couleurs" +online = "En Ligne" +local = "Local" +computer = "Ordinateur" +variant = "Variante" +variants = [ +"Classique", +"Classique+", +"Chess on an Infinite Plane", +"Pawndard", +"Knighted Chess", +"Knightline", +"Core", +"Standarch", +"Pawn Horde", +"Space Classic", +"Space", +"Obstocean", +"Abundance", +"Amazon Chandelier", +"Containment", +"Classical - Limit 7", +"Coaip - Limit 7", +"Chess", +"Experimental: Classical - KOTH", +"Experimental: Coaip - KOTH", +"Showcase: Omega", +"Showcase: Omega^2", +"Showcase: Omega^3", +"Showcase: Omega^4", +] +clock = "Horloge" +minutes = "m" +seconds = "s" +infinite_time = "Temps Infini" +color = "Couleur" +piece_colors = ["Aléatoire", "Blanc", "Noir"] +private = "Privé" +no = "Non" +yes = "Oui" +rated = "Classé" +join_games = "Rejoindre des Parties:" +private_invite = "Invitation Privée:" +your_invite = "Votre code d'invitation:" +create_invite = "Créer une invitation" +join = "Rejoindre" +copy = "Copier" +back = "Retour" +code = "Code" + +[play.footer] +white_to_move = "Tour des Blancs" +player_white = "Joueur Blanc" +player_black = "Joueur Noir" + +[play.pause] +title = "En Pause" +resume = "Reprendre" +arrows = "Flèches: Defense" +perspective = "Perspective: Off" +copy = "Copier la position" +paste = "Coller une position" +main_menu = "Menu Principal" + +[play.javascript] +guest_indicator = "(Guest)" +you_indicator = "(Vous)" +white_to_move = "Au tour des blancs" +black_to_move = "Au tour des noirs" +your_move = "Votre coup" +their_move = "Son coup" +lost_network = "Connexion perdue." +failed_to_load = "Une ressource ou plus n'a pas plus charger. Rafraîchissez la page s'il vous plaît." +planned_feature = "Cette fonctionnalité est prévue!" +main_menu = "Menu Principal" +resign_game = "Abandonner la Partie" +abort_game = "Abandonner la Partie" +arrows_off = "Flèches: Off" +arrows_defense = "Flèches: Défense" +arrows_all = "Flèches: Toutes" +toggled = "Activé" +menu_online = "Jouer - En Ligne" +menu_local = "Jouer - Local" +invite_error_digits = "Les codes d'invitation doivent être composés de 5 chiffres." +invite_copied = "Le code d'invitation a été copié dans le presse papier." +move_counter = "Tours:" +constructing_mesh = "Construction du mesh" +rotating_mesh = "Rotation du mesh" +lost_connection = "Connexion perdue." +please_wait = "Attendez un moment s'il vous plaît." +webgl_unsupported = "Votre navigateur ne supporte pas WebGL. Ce jeu ne peut pas fonctionner sans. Mettez à jour votre navigateur s'il vous plaît." +bigints_unsupported = "Votre navigateur ne supporte pas les BigInts. Mettez à jour votre navigateur.\nLes BigInts sont nécessaire pour rendre le plateau infini." +shaders_failed = "Les shaders n'ont pas pu être initialisés:" +failed_compiling_shaders = "Une erreur s'est produite lors de la compilation des shaders:" + +[play.javascript.copypaste] +copied_game = "La position a été copiée dans le presse-papier!" +cannot_paste_in_public = "Vous ne pouvez pas coller une position dans une partie publique!" +cannot_paste_after_moves = "Vous ne pouvez pas coller une position après que des coups aient été joués!" +clipboard_denied = "Erreur de permission avec le presse-papier. Ceci pourrait être du à votre navigateur." +clipboard_invalid = "Le presse-papier ne contient pas de notation ICN valide." +game_needs_to_specify = "La partie doit spécifier la metadata 'Variant' ou la propriété 'startingPosition'." +invalid_wincon_white = "Les Blancs ont une condition de victoire invalide" +invalid_wincon_black = "Les Noirs ont une condition de victoire invalide" +pasting_game = "Collage de la position..." +pasting_in_private = "Coller une position dans une partie privée causera une désynchronisation si votre adversaire ne fait pas de même!" +piece_count = "Nombre de pièces" +exceeded = "dépassé" +changed_wincon = "Les condition de victoire par mat ont étés passé à 'capture royale', et l'affichage des icones a été désactivé. Appuyez sur 'P' pour les réactiver (déconseillé)." +loaded_from_clipboard = "La position a été chargée depuis le presse-papier!" +loaded = "Position chargée!" +slidelimit_not_number = "La gamerule 'slideLimit' doit être un nombre." + +[play.javascript.rendering] +on = "On" +off = "Off" +icon_rendering_off = "L'affichage des icones a été désactivé." +icon_rendering_on = "L'affichage des icones a été activé." +toggled_debug = "Le debug mode a été activé:" +toggled_edit = "Le mode d'édition a été activé:" +perspective = "Perspective" +perspective_mode_on_desktop = "Le mode perspective n'est pas disponible sur téléphone!" +movement_tutorial = "WASD pour se déplacer. Espace et maj pour zoomer." +regenerated_pieces = "Les pièces ont été régénérés." + +[play.javascript.invites] +move_mouse = "Déplacez votre souris pour vous reconnecter." +unknown_action_received_1 = "Action inconnue" +unknown_action_received_2 = "reçu par le serveur dans l'abonnement d'invitations!" +cannot_cancel = "Impossible d'annuler l'invitation ou identifiant non défini." +you_indicator = "(Vous)" +you_are_white = "Vous êtes: Les blancs" +you_are_black = "Vous êtes: Les noirs" +random = "Aléatoire" +accept = "Accepter" +cancel = "Annuler" +create_invite = "Créer une invitation" +cancel_invite = "Annuler l'invitation" +join_existing_active_games = "Rejoindre des parties existantes - actives:" + +[play.javascript.onlinegame] +afk_warning = "Vous êtes AFK." +opponent_afk = "Votre adversaire est AFK." +opponent_disconnected = "Votre adversaire s'est déconnecté." +opponent_lost_connection = "Votre adversaire est hors ligne." +auto_resigning_in = "Abandon automatique dans" +auto_aborting_in = "Abandon automatique dans" +not_logged_in = "Vous n'êtes plus connecté. Reconnectez vous pour pouvoir retourner dans cette partie s'il vous plaît." +game_no_longer_exists = "Cette partie n'existe plus." +another_window_connected = "Une autre fenêtre a établi une connexion." +server_restarting = "Le serveur redémarre bientôt..." +server_restarting_in = "Le serveur redémarrera dans" +minute = "minute" +minutes = "minutes" + +[play.javascript.websocket] +no_connection = "Pas de connexion." +reconnected = "Reconnecté." +unable_to_identify_ip = "L'IP n'a pas pu être identifiée." +online_play_disabled = "Le jeu en ligne est désactivé. Les cookies ne sont pas supportés. Essayez avec un autre navigateur." +too_many_requests = "Trop de requêtes. Réessayez dans quelques minutes." +message_too_big = "Message trop gros." +too_many_sockets = "Too many sockets" +origin_error = "Origin error." +connection_closed = "La connection a été fermé de façon imprévue. Message du serveur:" +please_report_bug = "Ceci ne devrait jamais arriver, reportez ce bug s'il vous plaît!" + +[play.javascript.results] +you_checkmate = "Victoire par échec-et-mat!" +you_time = "Victoire par le temps!" +you_resignation = "Victoire par abandon!" +you_disconnect = "Victoire par déconnexion!" +you_royalcapture = "Victoire par capture du Roi!" +you_allroyalscaptured = "Victoire par capture de tous les Rois!" +you_allpiecescaptured = "Victoire par capture de toutes les pièces!" +you_threecheck = "Victoire par triple échec!" +you_koth = "Victoire par KOTH!" +you_generic = "Victoire!" +draw_stalemate = "Pat!" +draw_repetition = "Égalité par répétition!" +draw_moverule = ["Égalité par la règle des ", " coups"] +draw_insuffmat = "Égalité par manque de matériel!" +draw_generic = "Égalité!" +aborted = "Partie abandonnée." +opponent_checkmate = "Défaite par échec-et-mat!" +opponent_time = "Défaite par le temps!" +opponen_resignation = "Défaite par abandon!" +opponent_disconnect = "Défaite par abandon!" +opponent_royalcapture = "Défaite par perte du Roi!" +opponent_allroyalscaptured = "Défaite par perte de tous les Rois!" +opponent_allpiecescaptured = "Défaite par perte de toutes les pièces!" +opponent_threecheck = "Défaite par triple échec!" +opponent_koth = "Défaite par KOTH!" +opponent_generic = "Défaite!" +white_checkmate = "Les Blancs gagnent par échec-et-mat!" +black_checkmate = "Les Noirs gagnent par échec-et-mat!" +bug_checkmate = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie par un échec-et-mat." +white_time = "Les Blancs gagnent par le temps!" +black_time = "Les Noirs gagnent par le temps!" +bug_time = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie à cause du temps." +white_royalcapture = "Les Blancs gagnent par capture du Roi!" +black_royalcapture = "Les Noirs gagnent par capture du Roi!" +bug_royalcapture = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie par capture d'un Roi." +white_allroyalscaptured = "Les blancs gagnent par capture de tous les Rois!" +black_allroyalscaptured = "Les noirs gagnent par capture de tous les Rois!" +bug_allroyalscaptured = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie par la capture de tous les Rois d'un camp." +white_allpiecescaptured = "Les Blancs gagnent par capture de toutes les pièces!" +black_allpiecescaptured = "Les Noirs gagnent par capture de toutes les pièces!" +bug_allpiecescaptured = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie par la capture de toutes les pièces d'un camp." +white_threecheck = "Les Blancs gagnent par triple-échec!" +black_threecheck = "Les Noirs gagnent par triple-échec!" +bug_threecheck = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie par un triple-échec." +white_koth = "Les Blancs gagnent par KOTH!" +black_koth = "Les noirs gagnent par KOTH!" +bug_koth = "Ceci est un bug, reportez le s'il vous plaît. La partie s'est finie par un KOTH." +bug_generic = "Ceci est un bug, reportez le s'il vous plaît." + +[terms] +title = "Conditions d'Utilisation" +warning = ["CE DOCUMENT N'A PAS DE VALEUR LÉGALE. Notre responsabilité n'est engagé que par la version anglaise de ce texte. Cette traduction n'a qu'une valeur informationelle et peut contenir des erreurs ou inexactitudes. Vous pouvez accèder à la version Anglaise", "ici", "."] +consent = "En utilisant ce site, vous vous engager à respecter les conditions suivantes. Si vous ne les acceptez pas, vous devez immédiatement arrêter d'utiliser ce site." +guardian_consent = "Si vous avez moins de 18 ans vous devez recervoir l'accord de vos responsables légaux pour utiliser ce site ou vous créer un compte." +parents_header = "Parents" +parents_paragraphs = [ +"Il y a un système qui empêche les utilisateur de mettre des certaines insultes en nom d'utilisateur. Pour l'instant il n'y pas de possibilité de communication entre les membres du site.", +"Actuellement, les membres ne peuvent pas mettre de photos de profil personalisées, c'est prévu cependant. Au moment où cette fonctionnalité arrivera nous ferons de notre mieux pour empêcher les photos inapropriées.", +] +fair_play_header = "Fairplay" +fair_play_paragraph1 = ["Vous ne pouvez pas créer plus d'un compte. Si vous voulez changer l'adresse email associée à votre compte, ", "contactez nous."] +fair_play_paragraph2 = "Pour garder le jeu amusant et équilibré pour tous, vous ne devez PAS:" +fair_play_rules = [ +"Modifier ou manipuler le code ce qui inclu mais n'est pas limité à: l'utilisation de commandes dans la console, la réecriture de donnée localement, les scripts personnalisés, modifier les requêtes http, etc. Ce qui peut être fait intentionellement pour empêcher le fonctionnement normal du jeu ou vous donner un avantage.", +"Dans les parties classées, recevoir de l'aide ou des conseils d'une autre personne ou d'un programme sur ce que vous devriez jouer. (Créer un programme est autorisé et encouragé mais vous devez vous limitez à l'utiliser dans des parties non classées).", +"Échanger des points d'elo avec d'autres personnes en perdant volontairement dans l'objectif d'augmenter le classement de votre adversaire, ou en recevant des points d'elo d'un adversaire qui cherche à perdre pour augmenter votre classement. Ces situations abusent du système et créent des classements non représentatifs de la réalité." +] +cleanliness_header = "Respect" +cleanliness_rules = [ +"Vous devez rester respecteux à travers tout le site, pas de vulgarité ou d'injures. Vous n'êtes pas autorisés à insulter, à harceler ou à menacer quiconque, ou à commettre un quelconque acte illégal. Vous n'êtes pas autorisé à spammer d'autres utilisateurs ou d'autres forums.", +"Vous n'êtes pas autorisé à uploader des images qui peuvent être inappropriés, suggestives ou gores sur votre profil. Le faire pourrait résulter en un bannissement ou en une suppression de votre compte." +] +privacy_header = "Confidentialité" +privacy_rules = [ +"Pour l'instant la seule information personelle que nous collectons est l'adresse email. Elle nous permet de vérifier les comptes des utilisateurs et nous donne un moyen de prouver qui ils sont lorsqu'ils font une demande de changement de mot de passe. Nous n'envoyons pas de mails ou d'offres promotionelles. Nous ne partageons pas vos adresses email avec des tiers.", +"InfiniteChess.org peut collecter des données sur votre utilisation du site comme votre adresse IP. Cette collecte nous aide à empêcher les attaques automatisées ou les connexions néfastes, et nous aide à collecter des statistiques précises sur l'utilisation du site. Votre adresse IP n'est PAS votre adresse réelle.", +"Toutes les parties que vous jouez sur le site sont des informations publiques. Si vous souhaitez rester anynome, ne partagez pas votre nom d'utilisateur avec vos amis ou votre famille. Si c'est ce que vous souhaitez, il est de votre responsabilité de vous assurer que personne ne trouve de lien entre votre nom d'utilisateur et votre identité réelle.", +"Le status d'activité de votre compte et la durée approximative depuis votre dernière connexion sur le site est aussi une information publique.", +["Malgré que InfiniteChess.org fasse tout son possible pour garder toutes les données personnelles et les comptes sécurisés, dans le cas d'un hack ou d'unne fuite de donnée, vous ne pourrez pas nous en tenir responsable. Si une fuite de donnée a lieu, les utilisateurs en seront informés sur la page ", "Actualités", ""], +"Il n'y pas d'achats intégrés au site. Tout autre information personelle n'est pas collectée.", +"Pour avoir toutes vos informations personnelles supprimées de nos serveurs, vous pouvez supprimer votre compte depuis votre page de profil. La seule chose en lien avec votre nom d'utilisateur que nous ne supprimerons PAS, est votre historique de parties, car toutes les parties jouées sont des informations publiques.", +] +cookie_header = "Utilisation de Cookies" +cookie_paragraphs = [ +"Ce site utilise des cookies, qui sont des petits fichiers textes stockés dans votre navigateur, et envoyés au serveur quand il vous vous connectez. Le but de ces cookies est de: vous authentifier, vérifier que votre navigateur est dans la bonne partie d'échec et de stocker les préférences de votre compte pour que vous puissez les conserver entre deux visites. Le site n'utilisent pas de cookies tierces, les cookies ne sont pas partagé avec des tiers.", +"Les cookies sont nécessaire pour que ce site et les parties fonctionnent correctement. Si vous ne voulez pas que ce site stocke des cookies, vous devez arrêter de l'utiliser. Vous pouvez supprimer les cookies existant dans les paramètre de votre navigateur. En continuant d'utiliser ce site, vous accepter l'utilisation de cookies." +] +conclusion_header = "Conclusion" +conclusion_paragraphs = [ +"Toute violations de ces termes peut resulter en un bannissement ou en une suppression de votre compte. InfiniteChess.org a pour souhait de donner à tout le monde l'opportunité de jouer et de prendre du plaisir! Mais, nous nous reservons le droit de bannir ou de supprimer des comptes, pour des raisons que nous ne sommes pas tenus de justifier ou de communiquer et ne ne pourrons légalement pas être tenu pour responsables.", +["Ces termes peuvent être modifiés à tout moment. Il est de VOTRE responsabilité de vous tenir au courant des mises à jour de ces derniers! Quand les conditions d'utilisations seront modifiées nous vous en informerons sur la page ", "Actualités", ". Si, vous n'adhérez plus aux conditions après une mise à jour de ces dernières vous devez immédiatement quitter le site. Vous pouvez supprimer votre compte depuis votre profil. Si vous supprimez votre compte, toutes vos informations personnelles et les données qui vont concernent seront supprimées SAUF l'historique de vos parties, qui sont associées à votre nom d'utilisateur. Car ces parties sont des informations publiques."], +["Ce site est open-source. Vous pouvez récuperer ou distribuer toutes les ressources qui s'y trouvent tant que vos respectez les conditions décrites dans les ", "licences", " ! Ce le lien ci dessus n'est pas fonctionelle il est de votre responsabilité de trouver les licences."], +"Nous ne garantissons pas que ce site sera en ligne 100% du temps. Nous ne garantissons pas non plus que les données ne seront jamais corrompues.", +"Vous n'êtes pas autorisé à performer des activités illégales sur ce site.", +["Si vous avez des questions concernant ces conditions, ou à propos du site de manière générale,", "envoyez nous un email !"] +] +update = "(Dernière mise à jour: 13 Juillet 2024. Ajout de l'avertissement sur le fait que toutes les parties jouées sur le site sont des informations publiques, ce qui inclu une approximation de la durée depuis la dernière activité de votre compte. Ces conditions peuvent être mises à jour à tout moment et il est de votre responsabilité de vous assurez que vous restez à jour.)" +thanks = "Merci!" + +[login] +title = "Se Connecter" +username = "Nom d'Utilisateur:" +password = "Mot de Passe:" +forgot_password = ["Mot de passe oublié ? ", "Envoyez nous un mail."] +login_button = "Se connecter" + +[error-pages] # Messages shown on some error pages explaining what went wrong +400_message = "Des paramètres invalides ont été reçus." +409_message = ["Il y a peut être eu un conflit de nom d'utilisateur ou d'email. ", "Rafraichissez", " la page s'il vous plaît."] +500_message = "Ceci n'est pas censé arriver. Du debugage doit être fait!" + +########### NEWS ########### + +[news] +title = "Actualités" +more_dev_logs = ["Plus de dev logs sont publiés sur le ", "discord officiel", ", et sur les ", "forums chess.com"] + +[news.july13-2024] +date = "13 Juillet 2024:" +tos_update = ["Les ", "Condition d'Utisation", " ont été mises à jour. Changements: Toutes les parties que vous jouez sur le site peuvent devenir publiques, ainsi que la durée approximative depuis votre dernière activité. Les conditions d'utilisations peuvent être modifiées à tout moment, et il est de votre responsabilité de vous assurez que vous êtes à jour sur ces dernières."] +game_history_warning = "L'historique de vos parties pourrait être accessible depuis votre profil dans le futur ." + +[news.july9-2024] +date = "9 Juillet 2024:" +text = ["Infinite-Chess est désormais Open Source! Regardez, et contribuez au projet sur ", "GitHub!"] + +[news.may27-2024] +date = "27 Mai 2024:" +text = "1.3.2: Ajout des variantes de showcase pour Omega^3 et Omega^4 qui ont été présentées dans la dernière vidéo. L'algorithme de mat est maintenant compatible avec plusieurs rois." + +[news.may24-2024] +date = "24 Mai 2024:" +text = "La version 1.3.1 est sortie ! Elle comprend le guide, des info-bulles au survol des boutons de navigation ainsi que des liens vers le discord et les crédits sur la page d'accueil !" + +[news.may14-2024] +date = "14 Mai 2024:" +text_top = "La version 1.3 est sortie aujourd'hui ! Elle comprend BEAUCOUP d'amélioration de la vitesse et de l'expérience utilisateur. Dont notamment:" +update_list = [ +"La transition vers des websockets, diminuant le délai entre les coups.", +"Plus de déconnexion de la partie lorsque l'on change de fenêtre.", +"Effets sonores lorsque vous ou quelqu'un d'autre crée une invitation ou joue un coup.", +"Ajout de la règle des 50 coups.", +"Un compte a rebour sonore se joue quand il ne vous reste plus que 10 secondes de temps.", +"Un minuteur d'abandon automatique va commencer si votre adversaire cesse de jouer (avec un avertissement sonore)." +] +text_box = ["Et pleins d'autres nouvelles choses! Pour la liste complète, rendez-vous sur ", "le discord!"] + +[news.jan29-2024] +date = "29 Janvier 2024:" +text = "Nouvelle vidéo sortie aujourd'hui !" + +[news.aug26-2023] +date = "26 Août 2023:" +text_top = "La version 1.2 d'Infinite-Chess a été publiée! Elle amène l'historique de coups. Utilisez les flèches gauches et droites pour rembobiner ou accèlerer la partie!" +text_box = "Le système de vérification de comptes a été mis-à-jour pour être plus fiable! Si votre compte n'est pas vérifié et que vous aviez des problèmes avec le lien de vérification, réessayez de vous vérifier!" + +[server.javascript] +ws-invalid_username = "Nom d'utilisateur invalide" +ws-incorrect_password = "Mot de passe incorrect" +ws-username_and_password_required = "Le nom d'utilisateur et le mot de passe sont requis." +ws-username_and_password_string = "Le nom d'utilisateur et le mot de passe doivent être des chaines de caractères." +ws-login_failure_retry_in = "Authentification impossible, réessayez dans" +ws-seconds = "secondes" +ws-second = "seconde" +ws-username_length = "Le nom d'utilisateur doit faire entre 3 et 20 caractères" +ws-username_letters = "Le nom d'utilisateur ne doit contenir que des lettres et des chiffres" +ws-username_taken = "Ce nom d'utilisateur est déjà pris" +ws-username_bad_word = "Ce nom d'utilisateur contient un mot interdit" +ws-email_too_long = "Votre email est trop looooong." +ws-email_invalid = "Votre email n'est pas valide" +ws-email_in_use = "Cette adresse est déjà utilisée" +ws-you_are_banned = "Vous êtes banni." +ws-password_length = "Le mot de passe doit fair entre 6 et 72 caractères" +ws-password_format = "Le mot de passe n'est pas dans le bon format" +ws-password_password = "Le mot de passe doit être différent de 'password'" +ws-refresh_token_not_found_logged_out = "Aucun utilisateur n'a ce token de rafraichissement (déjà déconnecté)" +ws-refresh_token_not_found = "Aucun utilisateur n'a ce token de rafraichissement" +ws-refresh_token_expired = "Pas de token de rafraichissement trouvé (session expirée)" +ws-refresh_token_invalid = "Le token de rafraichissement a expiré ou a été altéré" +ws-member_not_found = "Utilisateur introuvable" +ws-forbidden_wrong_account = "Interdit. Ceci n'est pas votre compte." +ws-deleting_account_not_found = "Erreur lors de la suppression du compte. Le compte est introuvable." +ws-server_error = "Désolé, une erreur de serveur s'est produite! Revenez en arrière s'il vous plaît." +ws-unable_to_identify_client_ip = "L'adresse ip du client n'a pas pu être identifiée" +ws-you_are_banned_by_server = "Vous êtes banni" +ws-too_many_requests_to_server = "Trop de requêtes. Réessayez dans quelques minutes." +ws-bad_request = "Mauvaise Requête" +ws-not_found = "404 Introuvable" +ws-forbidden = "Interdit." +ws-unauthorized_patron_page = "Non autorisé. Cette page est réservé aux patreons." \ No newline at end of file From aba8b117567b64624c4478ab419adb4a9668d28c Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:12:59 -0600 Subject: [PATCH 56/58] Fix --- src/client/scripts/game/chess/legalmoves.js | 9 ++++++--- src/client/scripts/game/misc/math.js | 16 +++++++++++++++- src/client/scripts/game/rendering/arrows.js | 2 +- .../scripts/game/rendering/highlightline.js | 8 ++++---- src/client/scripts/game/rendering/highlights.js | 2 +- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 4792bd814..0ae361144 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -180,7 +180,7 @@ const legalmoves = (function(){ for (let i = 0; i < line.length; i++) { // What are the coords of this piece? const thisPiece = line[i] // { type, coords } - const thisPieceSteps = math.getLineSteps(direction, coords, thisPiece.coords) + const thisPieceSteps = Math.floor((thisPiece.coords[axis]-coords[axis])/direction[axis]) const thisPieceColor = math.getPieceColorFromType(thisPiece.type) const isFriendlyPiece = color === thisPieceColor const isVoid = thisPiece.type === 'voidsN'; @@ -358,9 +358,12 @@ const legalmoves = (function(){ * @returns {boolean} true if the piece is able to slide to the coordinates */ function doesSlidingMovesetContainSquare(slideMoveset, direction, pieceCoords, coords) { - const step = math.getLineSteps(direction, pieceCoords, coords) + const axis = direction[0] === 0 ? 1 : 0 + const coordMag = coords[axis]; + const min = slideMoveset[0] * direction[axis] + pieceCoords[axis] + const max = slideMoveset[1] * direction[axis] + pieceCoords[axis] - return step >= slideMoveset[0] && step <= slideMoveset[1]; + return coordMag >= min && coordMag <= max; } /** diff --git a/src/client/scripts/game/misc/math.js b/src/client/scripts/game/misc/math.js index 83eb108af..1cf9ae51d 100644 --- a/src/client/scripts/game/misc/math.js +++ b/src/client/scripts/game/misc/math.js @@ -504,6 +504,19 @@ const math = (function() { // Doesn't intersect any tile in the box. } + /** + * Returns the number of steps needed to reach from startCoord to endCoord, rounded down. + * @param {number[]} step - [dx,dy] + * @param {number[]} startCoord - Coordinates to start on + * @param {number[]} endCoord - Coordinate to stop at, proceeding no further + * @returns {number} the number of steps + */ + function getLineSteps(step, startCoord, endCoord) { + const chebyshevDist = chebyshevDistance(startCoord, endCoord) + const stepChebyshev = Math.max(step[0], step[1]); + return Math.floor(chebyshevDist / stepChebyshev); + } + function convertWorldSpaceToGrid(value) { return value / movement.getBoardScale() } @@ -1009,7 +1022,8 @@ const math = (function() { convertWorldSpaceToPixels_Virtual, getAABBCornerOfLine, getCornerOfBoundingBox, - getIntersectionEntryTile, + getLineIntersectionEntryTile, + getLineSteps, convertWorldSpaceToGrid, euclideanDistance, manhattanDistance, diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index 1f58182ac..e3a158daa 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -187,7 +187,7 @@ const arrows = (function() { if (piece.type === 'voidsN') continue; const isLeft = side==="l" const corner = math.getAABBCornerOfLine(direction, isLeft) - const renderCoords = math.getIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) + const renderCoords = math.getLineIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) if (!renderCoords) continue; const arrowDirection = isLeft ? [-direction[0],-direction[1]] : direction concatData(renderCoords, piece.type, corner, worldWidth, 0, piece.coords, arrowDirection, !isLeft) diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index 1c8646828..a1b472c9b 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -51,7 +51,7 @@ const highlightline = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); - let point1 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + let point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); if (!point1) {continue}; const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, false); const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord); @@ -59,7 +59,7 @@ const highlightline = (function(){ const corner2 = math.getAABBCornerOfLine(line, false); - let point2 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + let point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); if (!point2) continue; // I hate this const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, true); const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord); @@ -128,13 +128,13 @@ const highlightline = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); - point1 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, false); point1 = capPointAtSlideLimit(point1, leftLimitPointCoord, false, lineIsVertical); const corner2 = math.getAABBCornerOfLine(line, false); - point2 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, true); point2 = capPointAtSlideLimit(point2, rightLimitPointCoord, true, lineIsVertical); diff --git a/src/client/scripts/game/rendering/highlights.js b/src/client/scripts/game/rendering/highlights.js index 47e623a6c..3ab0ecfb3 100644 --- a/src/client/scripts/game/rendering/highlights.js +++ b/src/client/scripts/game/rendering/highlights.js @@ -217,7 +217,7 @@ const highlights = (function(){ // First we need to calculate the data of the horizontal slide concatData_HighlightedMoves_Sliding_Horz(coords, boundingBoxOfRenderRange.left, boundingBoxOfRenderRange.right) - // Calculate the data of the vertical slide + // Calculate the data of the vertical slide concatData_HighlightedMoves_Sliding_Vert(coords, boundingBoxOfRenderRange.bottom, boundingBoxOfRenderRange.top) // Calculate the data of the diagonals concatData_HighlightedMoves_Diagonals(coords, boundingBoxOfRenderRange, r, g, b, a) From d44ae6dc8befdf5451a222ed72aa8e994161759b Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:21:36 -0600 Subject: [PATCH 57/58] uhhhh fix???? --- src/client/scripts/game/chess/legalmoves.js | 4 ++++ src/client/scripts/game/rendering/arrows.js | 2 +- src/client/scripts/game/rendering/highlightline.js | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/game/chess/legalmoves.js b/src/client/scripts/game/chess/legalmoves.js index 0ae361144..cfe0266c7 100644 --- a/src/client/scripts/game/chess/legalmoves.js +++ b/src/client/scripts/game/chess/legalmoves.js @@ -358,6 +358,10 @@ const legalmoves = (function(){ * @returns {boolean} true if the piece is able to slide to the coordinates */ function doesSlidingMovesetContainSquare(slideMoveset, direction, pieceCoords, coords) { + // const step = math.getLineSteps(direction, pieceCoords, coords) + // return step >= slideMoveset[0] && step <= slideMoveset[1]; + + const axis = direction[0] === 0 ? 1 : 0 const coordMag = coords[axis]; const min = slideMoveset[0] * direction[axis] + pieceCoords[axis] diff --git a/src/client/scripts/game/rendering/arrows.js b/src/client/scripts/game/rendering/arrows.js index 1f58182ac..e3a158daa 100644 --- a/src/client/scripts/game/rendering/arrows.js +++ b/src/client/scripts/game/rendering/arrows.js @@ -187,7 +187,7 @@ const arrows = (function() { if (piece.type === 'voidsN') continue; const isLeft = side==="l" const corner = math.getAABBCornerOfLine(direction, isLeft) - const renderCoords = math.getIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) + const renderCoords = math.getLineIntersectionEntryTile(direction[0], direction[1], intersect, paddedBoundingBox, corner) if (!renderCoords) continue; const arrowDirection = isLeft ? [-direction[0],-direction[1]] : direction concatData(renderCoords, piece.type, corner, worldWidth, 0, piece.coords, arrowDirection, !isLeft) diff --git a/src/client/scripts/game/rendering/highlightline.js b/src/client/scripts/game/rendering/highlightline.js index 1c8646828..a1b472c9b 100644 --- a/src/client/scripts/game/rendering/highlightline.js +++ b/src/client/scripts/game/rendering/highlightline.js @@ -51,7 +51,7 @@ const highlightline = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); - let point1 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + let point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); if (!point1) {continue}; const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, false); const leftLimitPointWorld = math.convertCoordToWorldSpace(leftLimitPointCoord); @@ -59,7 +59,7 @@ const highlightline = (function(){ const corner2 = math.getAABBCornerOfLine(line, false); - let point2 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + let point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); if (!point2) continue; // I hate this const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, legalmoves.sliding[strline], line, true); const rightLimitPointWorld = math.convertCoordToWorldSpace(rightLimitPointCoord); @@ -128,13 +128,13 @@ const highlightline = (function(){ const corner1 = math.getAABBCornerOfLine(line, true); - point1 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); + point1 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner1); const leftLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, false); point1 = capPointAtSlideLimit(point1, leftLimitPointCoord, false, lineIsVertical); const corner2 = math.getAABBCornerOfLine(line, false); - point2 = math.getIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); + point2 = math.getLineIntersectionEntryTile(line[0], line[1], diag, boundingBox, corner2); const rightLimitPointCoord = getPointOfDiagSlideLimit(pieceCoords, moveset, line, true); point2 = capPointAtSlideLimit(point2, rightLimitPointCoord, true, lineIsVertical); From 6df5bb5988270f25cafa30f088deaf674816b884 Mon Sep 17 00:00:00 2001 From: Naviary <163621561+Naviary2@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:43:43 -0600 Subject: [PATCH 58/58] Backwards compatibility with loading old Knighted Chess positions --- src/client/scripts/game/chess/variant.js | 18 ++++++- src/server/game/variant1.js | 67 ++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/client/scripts/game/chess/variant.js b/src/client/scripts/game/chess/variant.js index 31bef1ef1..a6e75276c 100644 --- a/src/client/scripts/game/chess/variant.js +++ b/src/client/scripts/game/chess/variant.js @@ -343,7 +343,7 @@ const variant = (function() { positionString = 'k5,8+|n3,8|n4,8|n6,8|n7,8|p-5,7+|p-4,7+|p-3,7+|p-2,7+|p-1,7+|p0,7+|p1,7+|p2,7+|p3,7+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p9,7+|p10,7+|p11,7+|p12,7+|p13,7+|p14,7+|p15,7+|K5,1+|N3,1|N4,1|N6,1|N7,1|P-5,2+|P-4,2+|P-3,2+|P-2,2+|P-1,2+|P0,2+|P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|P9,2+|P10,2+|P11,2+|P12,2+|P13,2+|P14,2+|P15,2+'; return getStartSnapshotPosition({ positionString }) case "Knighted Chess": - positionString = 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|NR2,1|NR7,1|nr2,8|nr7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+'; + positionString = getPositionStringOfKnightedChess(Date); return getStartSnapshotPosition({ positionString }) case "Omega": // Joel & Cory's version positionString = 'r-2,4|r2,4|r-2,2|r2,2|r-2,0|r0,0|r2,0|k0,-1|R1,-2|P-2,-3|Q-1,-3|P2,-3|K0,-4' @@ -383,6 +383,20 @@ const variant = (function() { else return "p-3,18+|r2,18|b4,18|b5,18|r7,18|p12,18+|p-4,17+|p13,17+|p-5,16+|p14,16+|p3,9+|p4,9+|p5,9+|p6,9+|n3,8|k4,8|q5,8|n6,8|p-6,7+|p1,7+|p2,7+|p3,7+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p-8,6+|p-7,6+|p16,6+|p17,6+|p-9,5+|p18,5+|P-9,4+|P18,4+|P-8,3+|P-7,3+|P16,3+|P17,3+|P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|P15,2+|N3,1|K4,1|Q5,1|N6,1|P3,0+|P4,0+|P5,0+|P6,0+|P-5,-7+|P14,-7+|P-4,-8+|P13,-8+|P-3,-9+|R2,-9|B4,-9|B5,-9|R7,-9|P12,-9+" } + /** + * Returns the specified version's starting position of Space Classic, latest if not specified. + * @param {string} Date - The date this game was played. If not specified, we'll return the latest version. + * @returns {string} The position in compressed short form + */ + function getPositionStringOfKnightedChess(Date) { + const UTCTimeStamp = Date ? math.getUTCTimestamp(Date) : Date.now(); + // UTC timestamp for Jul 21, 2024, 9:33 PM + // Original, oldest version. NO knightrider + if (UTCTimeStamp < 1721619205980) return 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|N2,1|N7,1|n2,8|n7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+' + // Latest version, with knightrider + else return 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|NR2,1|NR7,1|nr2,8|nr7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+' + } + /** * Given the provided information, returns the `positionString`, `position`, * and `specialRights` properties for the gamefile's startSnapshot. @@ -794,7 +808,7 @@ const variant = (function() { * @param {number} [metadata.Date] - Optional. The version of the variant to initialize its starting position. If not specified, returns latest version. */ function initKnightedChess(gamefile, { Variant, Date }) { - const { position, positionString, specialRights } = getStartingPositionOfVariant({ Variant: 'Knighted Chess' }) + const { position, positionString, specialRights } = getStartingPositionOfVariant({ Variant: 'Knighted Chess', Date }) gamefile.startSnapshot = { position, positionString, diff --git a/src/server/game/variant1.js b/src/server/game/variant1.js index 06a733fb5..809c5950b 100644 --- a/src/server/game/variant1.js +++ b/src/server/game/variant1.js @@ -48,7 +48,54 @@ const variant1 = (function() { if (options) initStartSnapshotAndGamerulesFromOptions(gamefile, metadata, options) // Ignores the "Variant" metadata, and just uses the specified startingPosition else initStartSnapshotAndGamerules(gamefile, metadata) // Default (built-in variant, not pasted) + initExistingTypes(gamefile); initPieceMovesets(gamefile) + initSlidingMoves(gamefile) + } + + /** + * Sets the `existingTypes` property of the `startSnapshot` of the gamefile, + * which contains all types of pieces in the game, without their color extension. + * @param {gamefile} gamefile + */ + function initExistingTypes(gamefile) { + const teamtypes = new Set(Object.values(gamefile.startSnapshot.position)); // Make a set of all pieces in game + const rawtypes = new Set(); + for (const tpiece of teamtypes) { + rawtypes.add(math1.trimWorBFromType(tpiece)); // Make a set wit the team colour trimmed + } + gamefile.startSnapshot.existingTypes = rawtypes; + } + + /** + * Inits the `slidingMoves` property of the `startSnapshot` of the gamefile. + * This contains the information of what slides are possible, according to + * what piece types are in this game. + * @param {gamefile} gamefile + */ + function initSlidingMoves(gamefile) { + gamefile.startSnapshot.slidingPossible = getPossibleSlides(gamefile) + } + + /** + * Calculates all possible slides that should be possible in the provided game, + * excluding pieces that aren't in the provided position. + * @param {gamefile} gamefile + */ + function getPossibleSlides(gamefile) { + const rawtypes = gamefile.startSnapshot.existingTypes; + const movesets = gamefile.pieceMovesets; + const slides = new Set(['1,0']); // '1,0' is required if castling is enabled. + for (const type of rawtypes) { + let moveset = movesets[type]; + if (!moveset) continue; + moveset = moveset(); + if (!moveset.sliding) continue; + Object.keys(moveset.sliding).forEach( slide => { slides.add(slide) }); + } + let temp = []; + slides.forEach(slideline => { temp.push(math1.getCoordsFromKey(slideline)) }) + return temp; } /** @@ -196,7 +243,7 @@ const variant1 = (function() { default: throw new Error('Unknown variant.') } - + // Every variant has the exact same initial moveRuleState value. if (gamefile.gameRules.moveRule) gamefile.startSnapshot.moveRuleState = 0 gamefile.startSnapshot.fullMove = 1; // Every variant has the exact same fullMove value. @@ -302,7 +349,7 @@ const variant1 = (function() { positionString = 'k5,8+|n3,8|n4,8|n6,8|n7,8|p-5,7+|p-4,7+|p-3,7+|p-2,7+|p-1,7+|p0,7+|p1,7+|p2,7+|p3,7+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p9,7+|p10,7+|p11,7+|p12,7+|p13,7+|p14,7+|p15,7+|K5,1+|N3,1|N4,1|N6,1|N7,1|P-5,2+|P-4,2+|P-3,2+|P-2,2+|P-1,2+|P0,2+|P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|P9,2+|P10,2+|P11,2+|P12,2+|P13,2+|P14,2+|P15,2+'; return getStartSnapshotPosition({ positionString }) case "Knighted Chess": - positionString = 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|N2,1|N7,1|n2,8|n7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+'; + positionString = getPositionStringOfKnightedChess(Date); return getStartSnapshotPosition({ positionString }) case "Omega": // Joel & Cory's version positionString = 'r-2,4|r2,4|r-2,2|r2,2|r-2,0|r0,0|r2,0|k0,-1|R1,-2|P-2,-3|Q-1,-3|P2,-3|K0,-4' @@ -342,6 +389,20 @@ const variant1 = (function() { else return "p-3,18+|r2,18|b4,18|b5,18|r7,18|p12,18+|p-4,17+|p13,17+|p-5,16+|p14,16+|p3,9+|p4,9+|p5,9+|p6,9+|n3,8|k4,8|q5,8|n6,8|p-6,7+|p1,7+|p2,7+|p3,7+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p-8,6+|p-7,6+|p16,6+|p17,6+|p-9,5+|p18,5+|P-9,4+|P18,4+|P-8,3+|P-7,3+|P16,3+|P17,3+|P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|P15,2+|N3,1|K4,1|Q5,1|N6,1|P3,0+|P4,0+|P5,0+|P6,0+|P-5,-7+|P14,-7+|P-4,-8+|P13,-8+|P-3,-9+|R2,-9|B4,-9|B5,-9|R7,-9|P12,-9+" } + /** + * Returns the specified version's starting position of Space Classic, latest if not specified. + * @param {string} Date - The date this game was played. If not specified, we'll return the latest version. + * @returns {string} The position in compressed short form + */ + function getPositionStringOfKnightedChess(Date) { + const UTCTimeStamp = Date ? math1.getUTCTimestamp(Date) : Date.now(); + // UTC timestamp for Jul 21, 2024, 9:33 PM + // Original, oldest version. NO knightrider + if (UTCTimeStamp < 1721619205980) return 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|N2,1|N7,1|n2,8|n7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+' + // Latest version, with knightrider + else return 'P1,2+|P2,2+|P3,2+|P4,2+|P5,2+|P6,2+|P7,2+|P8,2+|p1,7+|p2,7+|p3,7+|P0,1+|P1,0+|P2,0+|P3,0+|P6,0+|P7,0+|P8,0+|P9,1+|p4,7+|p5,7+|p6,7+|p7,7+|p8,7+|p0,8+|p1,9+|p2,9+|p3,9+|p6,9+|p7,9+|p8,9+|p9,8+|CH1,1+|CH8,1+|ch1,8+|ch8,8+|NR2,1|NR7,1|nr2,8|nr7,8|AR3,1|AR6,1|ar3,8|ar6,8|AM4,1|am4,8|RC5,1+|rc5,8+' + } + /** * Given the provided information, returns the `positionString`, `position`, * and `specialRights` properties for the gamefile's startSnapshot. @@ -753,7 +814,7 @@ const variant1 = (function() { * @param {number} [metadata.Date] - Optional. The version of the variant to initialize its starting position. If not specified, returns latest version. */ function initKnightedChess(gamefile, { Variant, Date }) { - const { position, positionString, specialRights } = getStartingPositionOfVariant({ Variant: 'Knighted Chess' }) + const { position, positionString, specialRights } = getStartingPositionOfVariant({ Variant: 'Knighted Chess', Date }) gamefile.startSnapshot = { position, positionString,