From: Benjamin Auder Date: Tue, 17 Mar 2020 21:40:32 +0000 (+0100) Subject: Generalize pawn movements: cleaner and smaller code X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/css/doc/screen_timer.png?a=commitdiff_plain;h=32f6285ee325a14286562a53baefc647201df2af;p=vchess.git Generalize pawn movements: cleaner and smaller code --- diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 29b3af60..46064758 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -42,7 +42,19 @@ export const ChessRules = class ChessRules { return V.HasFlags; } - // Some variants don't have en-passant + // Pawns specifications + static get PawnSpecs() { + return { + directions: { 'w': -1, 'b': 1 }, + twoSquares: true, + promotions: [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN], + canCapture: true, + captureBackward: false, + bidirectional: false + }; + } + + // En-passant captures need a stack of squares: static get HasEnpassant() { return true; } @@ -646,82 +658,115 @@ export const ChessRules = class ChessRules { return moves; } + // Special case of en-passant captures: treated separately + getEnpassantCaptures([x, y], shiftX) { + const Lep = this.epSquares.length; + const epSquare = this.epSquares[Lep - 1]; //always at least one element + let enpassantMove = null; + if ( + !!epSquare && + epSquare.x == x + shiftX && + Math.abs(epSquare.y - y) == 1 + ) { + enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); + enpassantMove.vanish.push({ + x: x, + y: epSquare.y, + p: "p", + c: this.getColor(x, epSquare.y) + }); + } + return !!enpassantMove ? [enpassantMove] : []; + } + // What are the pawn moves from square x,y ? - getPotentialPawnMoves([x, y]) { + getPotentialPawnMoves([x, y], promotions) { const color = this.turn; - let moves = []; const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; + const pawnShiftX = V.PawnSpecs.directions[color]; const firstRank = color == "w" ? sizeX - 1 : 0; const startRank = color == "w" ? sizeX - 2 : 1; const lastRank = color == "w" ? 0 : sizeX - 1; - // NOTE: next condition is generally true (no pawn on last rank) - if (x + shiftX >= 0 && x + shiftX < sizeX) { - const finalPieces = - x + shiftX == lastRank - ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] - : [V.PAWN]; - if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: piece - }) - ); - } - // Next condition because pawns on 1st rank can generally jump - if ( - [startRank, firstRank].includes(x) && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } + // Consider all potential promotions: + const addMoves = ([x1, y1], [x2, y2], moves) => { + let finalPieces = [V.PAWN]; + if (x2 == lastRank) { + // promotions arg: special override for Hiddenqueen variant + if (!!promotions) finalPieces = promotions; + else if (!!V.PawnSpecs.promotions) + finalPieces = V.PawnSpecs.promotions; } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: piece - }) - ); + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x1, y1], [x2, y2], { + c: color, + p: piece + }) + ); + } + } + + // Pawn movements in shiftX direction: + const getPawnMoves = (shiftX) => { + let moves = []; + // NOTE: next condition is generally true (no pawn on last rank) + if (x + shiftX >= 0 && x + shiftX < sizeX) { + if (this.board[x + shiftX][y] == V.EMPTY) { + // One square forward + addMoves([x, y], [x + shiftX, y], moves); + // Next condition because pawns on 1st rank can generally jump + if ( + V.PawnSpecs.twoSquares && + [startRank, firstRank].includes(x) && + this.board[x + 2 * shiftX][y] == V.EMPTY + ) { + // Two squares jump + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); + } + } + // Captures + if (V.PawnSpecs.canCapture) { + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY + ) { + if ( + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + addMoves([x, y], [x + shiftX, y + shiftY], moves); + } + if ( + V.PawnSpecs.captureBackward && + x - shiftX >= 0 && x - shiftX < V.size.x && + this.board[x - shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x - shiftX, y + shiftY]) + ) { + addMoves([x, y], [x + shiftX, y + shiftY], moves); + } + } } } } + return moves; } + let pMoves = getPawnMoves(pawnShiftX); + if (V.PawnSpecs.bidirectional) + pMoves = pMoves.concat(getPawnMoves(-pawnShiftX)); + if (V.HasEnpassant) { - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare.x == x + shiftX && - Math.abs(epSquare.y - y) == 1 - ) { - let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare.y, - p: "p", - c: this.getColor(x, epSquare.y) - }); - moves.push(enpassantMove); - } + // NOTE: backward en-passant captures are not considered + // because no rules define them (for now). + Array.prototype.push.apply( + pMoves, + this.getEnpassantCaptures([x, y], pawnShiftX) + ); } - return moves; + return pMoves; } // What are the rook moves from square x,y ? @@ -781,6 +826,11 @@ export const ChessRules = class ChessRules { if (this.castleFlags[c][castleSide] >= V.size.y) continue; // If this code is reached, rooks and king are on initial position + const rookPos = this.castleFlags[c][castleSide]; + if (this.getColor(x, rookPos) != c) + // Rook is here but changed color (see Benedict) + continue; + // Nothing on the path of the king ? (and no checks) const finDist = finalSquares[castleSide][0] - y; let step = finDist / Math.max(1, Math.abs(finDist)); @@ -800,7 +850,6 @@ export const ChessRules = class ChessRules { // Nothing on the path to the rook? step = castleSide == 0 ? -1 : 1; - const rookPos = this.castleFlags[c][castleSide]; for (i = y + step; i != rookPos; i += step) { if (this.board[x][i] != V.EMPTY) continue castlingCheck; } diff --git a/client/src/playCompMove.js b/client/src/playCompMove.js index 05c2286c..d83148fb 100644 --- a/client/src/playCompMove.js +++ b/client/src/playCompMove.js @@ -2,8 +2,8 @@ onmessage = async function(e) { switch (e.data[0]) { case "scripts": { - const vModule = await import("@/variants/" + e.data[1] + ".js"); - self.V = vModule.VariantRules; + await import("@/variants/" + e.data[1] + ".js") + .then((vModule) => { self.V = vModule[e.data[1] + "Rules"]; }); break; } case "init": { diff --git a/client/src/translations/rules/Capture/es.pug b/client/src/translations/rules/Capture/es.pug index 4994990e..505a04e7 100644 --- a/client/src/translations/rules/Capture/es.pug +++ b/client/src/translations/rules/Capture/es.pug @@ -1,5 +1,5 @@ p.boxed - Las capturas son obligatorias. Gana por jaque mate. + | Las capturas son obligatorias. Gana por jaque mate. p. Todo va como el ajedrez ortodoxo, pero cuando es posible @@ -11,7 +11,7 @@ p. como en el siguiente diagrama: Qxd7 será forzado, perdiendo a la dama. figure.diagram-container - .diagrama + .diagram | fen:rnbqkbnr/pppp1pp1/7p/4P3/8/8/PPP1PPPP/RNBQKBNR: figcaption Después de 1.d4?? e5 2.dxe5 h6. diff --git a/client/src/variants/Alice.js b/client/src/variants/Alice.js index d64c0778..d30acf05 100644 --- a/client/src/variants/Alice.js +++ b/client/src/variants/Alice.js @@ -3,7 +3,7 @@ import { ArrayFun } from "@/utils/array"; // NOTE: alternative implementation, probably cleaner = use only 1 board // TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations) -export const VariantRules = class AliceRules extends ChessRules { +export class AliceRules extends ChessRules { static get ALICE_PIECES() { return { s: "p", diff --git a/client/src/variants/Allmate1.js b/client/src/variants/Allmate1.js index bc10478e..65e20b79 100644 --- a/client/src/variants/Allmate1.js +++ b/client/src/variants/Allmate1.js @@ -1,6 +1,6 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -export const VariantRules = class Allmate1Rules extends ChessRules { +export class Allmate1Rules extends ChessRules { static get HasEnpassant() { return false; } @@ -32,7 +32,7 @@ export const VariantRules = class Allmate1Rules extends ChessRules { let attacked = {}; for (let i=0; i= 0 && x + pawnShift < V.size.x) { - if ( - this.getPiece(x + pawnShift, y) == V.PAWN && - this.getColor(x + pawnShift, y) == color - ) { - return true; - } - } - return false; - } - isAttackedByKing([x, y], color) { // Antiking is not attacked by king: if (this.getPiece(x, y) == V.ANTIKING) return false; @@ -267,27 +222,4 @@ export const VariantRules = class Antiking1Rules extends ChessRules { static get SEARCH_DEPTH() { return 2; } - - // TODO: notation copied from Berolina - getNotation(move) { - const piece = this.getPiece(move.start.x, move.start.y); - if (piece == V.PAWN) { - // Pawn move - const finalSquare = V.CoordsToSquare(move.end); - let notation = ""; - if (move.vanish.length == 2) - //capture - notation = "Px" + finalSquare; - else { - // No capture: indicate the initial square for potential ambiguity - const startSquare = V.CoordsToSquare(move.start); - notation = startSquare + finalSquare; - } - if (move.appear[0].p != V.PAWN) - // Promotion - notation += "=" + move.appear[0].p.toUpperCase(); - return notation; - } - return super.getNotation(move); //all other pieces are orthodox - } }; diff --git a/client/src/variants/Antiking2.js b/client/src/variants/Antiking2.js index 3be41af5..0a427430 100644 --- a/client/src/variants/Antiking2.js +++ b/client/src/variants/Antiking2.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class Antiking2Rules extends ChessRules { +export class Antiking2Rules extends ChessRules { static get ANTIKING() { return "a"; } diff --git a/client/src/variants/Antimatter.js b/client/src/variants/Antimatter.js index 63d5ae56..f8f4c73e 100644 --- a/client/src/variants/Antimatter.js +++ b/client/src/variants/Antimatter.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class AntimatterRules extends ChessRules { +export class AntimatterRules extends ChessRules { getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x, y]); diff --git a/client/src/variants/Arena.js b/client/src/variants/Arena.js index 620b980a..b2b18ee6 100644 --- a/client/src/variants/Arena.js +++ b/client/src/variants/Arena.js @@ -1,10 +1,18 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class ArenaRules extends ChessRules { +export class ArenaRules extends ChessRules { static get HasFlags() { return false; } + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { captureBackward: true } + ); + } + static GenRandInitFen(randomness) { return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "-"; } @@ -28,60 +36,6 @@ export const VariantRules = class ArenaRules extends ChessRules { return moves; } - getPotentialPawnMoves([x, y]) { - const color = this.turn; - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - - if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward - moves.push(this.getBasicMove([x, y], [x + shiftX, y])); - // Next condition because pawns on 1st rank can generally jump - if ( - x == startRank && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures: also possible backward - for (let shiftY of [-1, 1]) { - if (y + shiftY >= 0 && y + shiftY < sizeY) { - for (let direction of [-1,1]) { - if ( - this.board[x + direction][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + direction, y + shiftY]) - ) { - moves.push(this.getBasicMove([x, y], [x + direction, y + shiftY])); - } - } - } - } - - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare.x == x + shiftX && - Math.abs(epSquare.y - y) == 1 - ) { - let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare.y, - p: "p", - c: this.getColor(x, epSquare.y) - }); - moves.push(enpassantMove); - } - - return moves; - } - getPotentialQueenMoves(sq) { return this.getSlideNJumpMoves( sq, diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js index 19521c2d..a6a5625b 100644 --- a/client/src/variants/Atomic.js +++ b/client/src/variants/Atomic.js @@ -1,6 +1,6 @@ import { ChessRules, PiPo } from "@/base_rules"; -export const VariantRules = class AtomicRules extends ChessRules { +export class AtomicRules extends ChessRules { getEpSquare(moveOrSquare) { if (typeof moveOrSquare !== "object" || moveOrSquare.appear.length > 0) return super.getEpSquare(moveOrSquare); diff --git a/client/src/variants/Baroque.js b/client/src/variants/Baroque.js index a19d09e5..92bae77f 100644 --- a/client/src/variants/Baroque.js +++ b/client/src/variants/Baroque.js @@ -1,8 +1,8 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; -import { randInt } from "@/utils/alea"; +import { shuffle } from "@/utils/alea"; -export const VariantRules = class BaroqueRules extends ChessRules { +export class BaroqueRules extends ChessRules { static get HasFlags() { return false; } @@ -558,46 +558,10 @@ export const VariantRules = class BaroqueRules extends ChessRules { break; } - let positions = ArrayFun.range(8); // Get random squares for every piece, totally freely - - let randIndex = randInt(8); - const bishop1Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(7); - const bishop2Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(6); - const knight1Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(5); - const knight2Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(4); - const queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(3); - const kingPos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(2); - const rookPos = positions[randIndex]; - positions.splice(randIndex, 1); - const immobilizerPos = positions[0]; - - pieces[c][bishop1Pos] = "b"; - pieces[c][bishop2Pos] = "b"; - pieces[c][knight1Pos] = "n"; - pieces[c][knight2Pos] = "n"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][rookPos] = "r"; - pieces[c][immobilizerPos] = "m"; + let positions = shuffle(ArrayFun.range(8)); + const composition = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'm']; + for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i]; } return ( pieces["b"].join("") + diff --git a/client/src/variants/Benedict.js b/client/src/variants/Benedict.js index f0d4f2ba..fbc4b488 100644 --- a/client/src/variants/Benedict.js +++ b/client/src/variants/Benedict.js @@ -1,10 +1,18 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -export const VariantRules = class BenedictRules extends ChessRules { +export class BenedictRules extends ChessRules { static get HasEnpassant() { return false; } + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { canCapture: false } + ); + } + // TODO(?): some duplicated code in 2 next functions getSlideNJumpMoves([x, y], steps, oneStep) { let moves = []; @@ -64,126 +72,6 @@ export const VariantRules = class BenedictRules extends ChessRules { return squares; } - getPotentialPawnMoves([x, y]) { - const color = this.getColor(x, y); - let moves = []; - const sizeY = V.size.y; - const shift = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeY - 2 : 1; - const firstRank = color == "w" ? sizeY - 1 : 0; - const lastRank = color == "w" ? 0 : sizeY - 1; - - if (x + shift != lastRank) { - // Normal moves - if (this.board[x + shift][y] == V.EMPTY) { - moves.push(this.getBasicMove([x, y], [x + shift, y])); - if ( - [startRank, firstRank].includes(x) && - this.board[x + 2 * shift][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shift, y])); - } - } - } - else { - // Promotion - let promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; - promotionPieces.forEach(p => { - // Normal move - if (this.board[x + shift][y] == V.EMPTY) - moves.push( - this.getBasicMove([x, y], [x + shift, y], { c: color, p: p }) - ); - }); - } - - // No en passant here - - return moves; - } - - // No "under check" verifications: - getCastleMoves([x, y]) { - const c = this.getColor(x, y); - if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) - return []; //x isn't first rank, or king has moved (shortcut) - - // Castling ? - const oppCol = V.GetOppCol(c); - let moves = []; - let i = 0; - // King, then rook: - const finalSquares = [ - [2, 3], - [V.size.y - 2, V.size.y - 3] - ]; - castlingCheck: for ( - let castleSide = 0; - castleSide < 2; - castleSide++ //large, then small - ) { - if (this.castleFlags[c][castleSide] >= 8) continue; - // If this code is reached, rooks and king are on initial position - - const rookPos = this.castleFlags[c][castleSide]; - if (this.getColor(x, rookPos) != c) - // Rook is here but changed color - continue; - - // Nothing on the path of the king ? - const finDist = finalSquares[castleSide][0] - y; - let step = finDist / Math.max(1, Math.abs(finDist)); - for (let i = y; i != finalSquares[castleSide][0]; i += step) { - if ( - this.board[x][i] != V.EMPTY && - // NOTE: next check is enough, because of chessboard constraints - (this.getColor(x, i) != c || - ![V.KING, V.ROOK].includes(this.getPiece(x, i))) - ) { - continue castlingCheck; - } - } - - // Nothing on the path to the rook? - step = castleSide == 0 ? -1 : 1; - for (i = y + step; i != rookPos; i += step) { - if (this.board[x][i] != V.EMPTY) continue castlingCheck; - } - - // Nothing on final squares, except maybe king and castling rook? - for (i = 0; i < 2; i++) { - if ( - this.board[x][finalSquares[castleSide][i]] != V.EMPTY && - this.getPiece(x, finalSquares[castleSide][i]) != V.KING && - finalSquares[castleSide][i] != rookPos - ) { - continue castlingCheck; - } - } - - // If this code is reached, castle is valid - moves.push( - new Move({ - appear: [ - new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), - new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c }) - ], - vanish: [ - new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) - ], - end: - Math.abs(y - rookPos) <= 2 - ? { x: x, y: rookPos } - : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } - }) - ); - } - - return moves; - } - // TODO: appear/vanish description of a move is too verbose for Benedict. // => Would need a new "flipped" array, to be passed in Game.vue... getPotentialMovesFrom([x, y]) { @@ -234,6 +122,11 @@ export const VariantRules = class BenedictRules extends ChessRules { return moves; } + // Since it's used just for the king, and there are no captures: + isAttacked(sq, color) { + return false; + } + // No notion of check here: getCheckSquares() { return []; diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js index 06592244..b14b19e8 100644 --- a/client/src/variants/Berolina.js +++ b/client/src/variants/Berolina.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class BerolinaRules extends ChessRules { +export class BerolinaRules extends ChessRules { // En-passant after 2-sq jump getEpSquare(moveOrSquare) { if (!moveOrSquare) return undefined; @@ -76,6 +76,7 @@ export const VariantRules = class BerolinaRules extends ChessRules { ); } if ( + V.PawnSpecs.twoSquares && x == startRank && y + 2 * shiftY >= 0 && y + 2 * shiftY < sizeY && @@ -99,22 +100,25 @@ export const VariantRules = class BerolinaRules extends ChessRules { ); } - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare[0].x == x + shiftX && - epSquare[0].y == y - ) { - let enpassantMove = this.getBasicMove([x, y], [x + shiftX, y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare[1], - p: "p", - c: this.getColor(x, epSquare[1]) - }); - moves.push(enpassantMove); + // Next condition so that other variants could inherit from this class + if (V.PawnSpecs.enPassant) { + // En passant + const Lep = this.epSquares.length; + const epSquare = this.epSquares[Lep - 1]; //always at least one element + if ( + !!epSquare && + epSquare[0].x == x + shiftX && + epSquare[0].y == y + ) { + let enpassantMove = this.getBasicMove([x, y], [x + shiftX, y]); + enpassantMove.vanish.push({ + x: x, + y: epSquare[1], + p: "p", + c: this.getColor(x, epSquare[1]) + }); + moves.push(enpassantMove); + } } return moves; @@ -133,6 +137,10 @@ export const VariantRules = class BerolinaRules extends ChessRules { return false; } + static get SEARCH_DEPTH() { + return 2; + } + getNotation(move) { const piece = this.getPiece(move.start.x, move.start.y); if (piece == V.PAWN) { diff --git a/client/src/variants/Capture.js b/client/src/variants/Capture.js index 5b286e0e..f0782a69 100644 --- a/client/src/variants/Capture.js +++ b/client/src/variants/Capture.js @@ -1,8 +1,6 @@ import { ChessRules } from "@/base_rules"; -import { ArrayFun } from "@/utils/array"; -import { randInt } from "@/utils/alea"; -export const VariantRules = class LosersRules extends ChessRules { +export class CaptureRules extends ChessRules { // Trim all non-capturing moves static KeepCaptures(moves) { return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1); @@ -17,8 +15,8 @@ export const VariantRules = class LosersRules extends ChessRules { if ( this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol && - this.getPotentialMovesFrom([i, j]).some(m => - // Warning: duscard castle moves + this.filterValid(this.getPotentialMovesFrom([i, j])).some(m => + // Warning: discard castle moves m.vanish.length == 2 && m.appear.length == 1) ) { return true; diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index 937b5741..d601d14a 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -1,6 +1,6 @@ import { ChessRules, Move, PiPo } from "@/base_rules"; -export const VariantRules = class CheckeredRules extends ChessRules { +export class CheckeredRules extends ChessRules { static board2fen(b) { const checkered_codes = { p: "s", @@ -118,7 +118,7 @@ export const VariantRules = class CheckeredRules extends ChessRules { getPotentialMovesFrom([x, y]) { let standardMoves = super.getPotentialMovesFrom([x, y]); const lastRank = this.turn == "w" ? 0 : 7; - // King has to be treated differently (for castles) + // King is treated differently: it never turn checkered if (this.getPiece(x, y) == V.KING) return standardMoves; let moves = []; standardMoves.forEach(m => { @@ -126,8 +126,9 @@ export const VariantRules = class CheckeredRules extends ChessRules { if ( Math.abs(m.end.x - m.start.x) == 2 && !this.pawnFlags[this.turn][m.start.y] - ) + ) { return; //skip forbidden 2-squares jumps + } if ( this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length == 2 && @@ -136,14 +137,16 @@ export const VariantRules = class CheckeredRules extends ChessRules { return; //checkered pawns cannot take en-passant } } - if (m.vanish.length == 1) moves.push(m); - // No capture + if (m.vanish.length == 1) + // No capture + moves.push(m); else { // A capture occured (m.vanish.length == 2) m.appear[0].c = "c"; moves.push(m); if ( - m.appear[0].p != m.vanish[1].p && //avoid promotions (already treated): + // Avoid promotions (already treated): + m.appear[0].p != m.vanish[1].p && (m.vanish[0].p != V.PAWN || m.end.x != lastRank) ) { // Add transformation into captured piece @@ -157,153 +160,13 @@ export const VariantRules = class CheckeredRules extends ChessRules { } getPotentialPawnMoves([x, y]) { - const color = this.turn; - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - const pawnColor = this.getColor(x, y); //can be checkered - - const finalPieces = - x + shiftX == lastRank - ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] - : [V.PAWN]; - if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: pawnColor, - p: piece - }) - ); - } - if ( - x == startRank && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: pawnColor, - p: piece - }) - ); - } - } - } - - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare.x == x + shiftX && - Math.abs(epSquare.y - y) == 1 - ) { - let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare.y, - p: "p", - c: this.getColor(x, epSquare.y) + let moves = super.getPotentialPawnMoves([x, y]); + // Post-process: set right color for checkered moves + if (this.getColor(x, y) == 'c') + moves.forEach(m => { + m.appear[0].c = 'c'; //may be done twice if capture + m.vanish[0].c = 'c'; }); - moves.push(enpassantMove); - } - - return moves; - } - - // Same as in base_rules but with an array given to isAttacked: - getCastleMoves([x, y]) { - const c = this.getColor(x, y); - if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) - return []; //x isn't first rank, or king has moved (shortcut) - - // Castling ? - const oppCol = V.GetOppCol(c); - let moves = []; - let i = 0; - // King, then rook: - const finalSquares = [ - [2, 3], - [V.size.y - 2, V.size.y - 3] - ]; - castlingCheck: for ( - let castleSide = 0; - castleSide < 2; - castleSide++ //large, then small - ) { - if (this.castleFlags[c][castleSide] >= V.size.y) continue; - // If this code is reached, rooks and king are on initial position - - // Nothing on the path of the king ? (and no checks) - const finDist = finalSquares[castleSide][0] - y; - let step = finDist / Math.max(1, Math.abs(finDist)); - i = y; - do { - if ( - this.isAttacked([x, i], [oppCol]) || - (this.board[x][i] != V.EMPTY && - // NOTE: next check is enough, because of chessboard constraints - (this.getColor(x, i) != c || - ![V.KING, V.ROOK].includes(this.getPiece(x, i)))) - ) { - continue castlingCheck; - } - i += step; - } while (i != finalSquares[castleSide][0]); - - // Nothing on the path to the rook? - step = castleSide == 0 ? -1 : 1; - const rookPos = this.castleFlags[c][castleSide]; - for (i = y + step; i != rookPos; i += step) { - if (this.board[x][i] != V.EMPTY) continue castlingCheck; - } - - // Nothing on final squares, except maybe king and castling rook? - for (i = 0; i < 2; i++) { - if ( - this.board[x][finalSquares[castleSide][i]] != V.EMPTY && - this.getPiece(x, finalSquares[castleSide][i]) != V.KING && - finalSquares[castleSide][i] != rookPos - ) { - continue castlingCheck; - } - } - - // If this code is reached, castle is valid - moves.push( - new Move({ - appear: [ - new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), - new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c }) - ], - vanish: [ - new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) - ], - end: - Math.abs(y - rookPos) <= 2 - ? { x: x, y: rookPos } - : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } - }) - ); - } - return moves; } @@ -375,6 +238,7 @@ export const VariantRules = class CheckeredRules extends ChessRules { // colors: array, generally 'w' and 'c' or 'b' and 'c' isAttacked(sq, colors) { + if (!Array.isArray(colors)) colors = [colors]; return ( this.isAttackedByPawn(sq, colors) || this.isAttackedByRook(sq, colors) || diff --git a/client/src/variants/Chess960.js b/client/src/variants/Chess960.js index 7e10e2a5..3f6af502 100644 --- a/client/src/variants/Chess960.js +++ b/client/src/variants/Chess960.js @@ -1,4 +1,4 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class Chess960Rules extends ChessRules { +export class Chess960Rules extends ChessRules { // Standard rules }; diff --git a/client/src/variants/Circular.js b/client/src/variants/Circular.js index 61997f29..4338c91f 100644 --- a/client/src/variants/Circular.js +++ b/client/src/variants/Circular.js @@ -1,8 +1,8 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; -import { randInt, shuffle } from "@/utils/alea"; +import { shuffle } from "@/utils/alea"; -export const VariantRules = class CircularRules extends ChessRules { +export class CircularRules extends ChessRules { static get HasCastle() { return false; } @@ -39,53 +39,25 @@ export const VariantRules = class CircularRules extends ChessRules { return "8/8/pppppppp/rnbqkbnr/8/8/PPPPPPPP/RNBQKBNR w 0 1111111111111111"; let pieces = { w: new Array(8), b: new Array(8) }; - // Shuffle pieces on first and fifth rank + // Shuffle pieces on first and last rank for (let c of ["w", "b"]) { if (c == 'b' && randomness == 1) { pieces['b'] = pieces['w']; break; } - let positions = ArrayFun.range(8); - - // Get random squares for bishops - let randIndex = 2 * randInt(4); - const bishop1Pos = positions[randIndex]; - // The second bishop must be on a square of different color - let randIndex_tmp = 2 * randInt(4) + 1; - const bishop2Pos = positions[randIndex_tmp]; - // Remove chosen squares - positions.splice(Math.max(randIndex, randIndex_tmp), 1); - positions.splice(Math.min(randIndex, randIndex_tmp), 1); - - // Get random squares for knights - randIndex = randInt(6); - const knight1Pos = positions[randIndex]; - positions.splice(randIndex, 1); - randIndex = randInt(5); - const knight2Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Get random square for queen - randIndex = randInt(4); - const queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Rooks and king positions are now fixed, - // because of the ordering rook-king-rook - const rook1Pos = positions[0]; - const kingPos = positions[1]; - const rook2Pos = positions[2]; - - // Finally put the shuffled pieces in the board array - pieces[c][rook1Pos] = "r"; - pieces[c][knight1Pos] = "n"; - pieces[c][bishop1Pos] = "b"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][bishop2Pos] = "b"; - pieces[c][knight2Pos] = "n"; - pieces[c][rook2Pos] = "r"; + // Get random squares for every piece, totally freely + let positions = shuffle(ArrayFun.range(8)); + const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q']; + const rem2 = positions[0] % 2; + if (rem2 == positions[1] % 2) { + // Fix bishops (on different colors) + for (let i=2; i<8; i++) { + if (positions[i] % 2 != rem2) + [positions[1], positions[i]] = [positions[i], positions[1]]; + } + } + for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i]; } return ( "8/8/pppppppp/" + diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js index fb8c7441..321bf4cf 100644 --- a/client/src/variants/Crazyhouse.js +++ b/client/src/variants/Crazyhouse.js @@ -1,7 +1,7 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; -export const VariantRules = class CrazyhouseRules extends ChessRules { +export class CrazyhouseRules extends ChessRules { static IsGoodFen(fen) { if (!ChessRules.IsGoodFen(fen)) return false; const fenParsed = V.ParseFen(fen); diff --git a/client/src/variants/Cylinder.js b/client/src/variants/Cylinder.js index 44d1ab16..f26f3e62 100644 --- a/client/src/variants/Cylinder.js +++ b/client/src/variants/Cylinder.js @@ -2,7 +2,7 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt, shuffle } from "@/utils/alea"; -export const VariantRules = class CylinderRules extends ChessRules { +export class CylinderRules extends ChessRules { // Output basically x % 8 (circular board) static ComputeY(y) { let res = y % V.size.y; diff --git a/client/src/variants/Dark.js b/client/src/variants/Dark.js index a7edb916..3bca2340 100644 --- a/client/src/variants/Dark.js +++ b/client/src/variants/Dark.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class DarkRules extends ChessRules { +export class DarkRules extends ChessRules { // Analyse in Dark mode makes no sense static get CanAnalyze() { return false; diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index a0eed50d..fe36ab24 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -2,7 +2,7 @@ import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; import { ChessRules, PiPo, Move } from "@/base_rules"; -export const VariantRules = class EightpiecesRules extends ChessRules { +export class EightpiecesRules extends ChessRules { static get JAILER() { return "j"; } diff --git a/client/src/variants/Enpassant.js b/client/src/variants/Enpassant.js index dc181cab..ee04cf4f 100644 --- a/client/src/variants/Enpassant.js +++ b/client/src/variants/Enpassant.js @@ -1,7 +1,6 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -export const VariantRules = class EnpassantRules extends ChessRules { - +export class EnpassantRules extends ChessRules { static IsGoodEnpassant(enpassant) { if (enpassant != "-") { const squares = enpassant.split(","); @@ -113,60 +112,10 @@ export const VariantRules = class EnpassantRules extends ChessRules { return moves; } - // TODO: this getPotentialPawnMovesFrom() is mostly duplicated: - // it could be split in "capture", "promotion", "enpassant"... - getPotentialPawnMoves([x, y]) { - const color = this.turn; - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - - const finalPieces = - x + shiftX == lastRank - ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] - : [V.PAWN]; - // One square forward - if (this.board[x + shiftX][y] == V.EMPTY) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: piece - }) - ); - } - if ( - x == startRank && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: piece - }) - ); - } - } - } - - // En passant + getEnpassantCaptures([x, y], shiftX) { const Lep = this.epSquares.length; const squares = this.epSquares[Lep - 1]; + let moves = []; if (!!squares) { const S = squares.length; const taken = squares[S-1]; @@ -185,7 +134,6 @@ export const VariantRules = class EnpassantRules extends ChessRules { } }); } - return moves; } diff --git a/client/src/variants/Extinction.js b/client/src/variants/Extinction.js index 3fa53276..e2571378 100644 --- a/client/src/variants/Extinction.js +++ b/client/src/variants/Extinction.js @@ -1,6 +1,14 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class ExtinctionRules extends ChessRules { +export class ExtinctionRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) } + ); + } + static IsGoodPosition(position) { if (!ChessRules.IsGoodPosition(position)) return false; @@ -43,43 +51,6 @@ export const VariantRules = class ExtinctionRules extends ChessRules { }; } - getPotentialPawnMoves([x, y]) { - let moves = super.getPotentialPawnMoves([x, y]); - // Add potential promotions into king - const color = this.turn; - const shift = color == "w" ? -1 : 1; - const lastRank = color == "w" ? 0 : V.size.x - 1; - - if (x + shift == lastRank) { - // Normal move - if (this.board[x + shift][y] == V.EMPTY) - moves.push( - this.getBasicMove([x, y], [x + shift, y], { c: color, p: V.KING }) - ); - // Captures - if ( - y > 0 && - this.board[x + shift][y - 1] != V.EMPTY && - this.canTake([x, y], [x + shift, y - 1]) - ) { - moves.push( - this.getBasicMove([x, y], [x + shift, y - 1], { c: color, p: V.KING }) - ); - } - if ( - y < V.size.y - 1 && - this.board[x + shift][y + 1] != V.EMPTY && - this.canTake([x, y], [x + shift, y + 1]) - ) { - moves.push( - this.getBasicMove([x, y], [x + shift, y + 1], { c: color, p: V.KING }) - ); - } - } - - return moves; - } - // TODO: verify this assertion atLeastOneMove() { return true; //always at least one possible move diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index d4f620f7..5523244b 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -4,7 +4,7 @@ import { randInt } from "@/utils/alea"; // NOTE: initial setup differs from the original; see // https://www.chessvariants.com/large.dir/freeling.html -export const VariantRules = class GrandRules extends ChessRules { +export class GrandRules extends ChessRules { static IsGoodFen(fen) { if (!ChessRules.IsGoodFen(fen)) return false; const fenParsed = V.ParseFen(fen); @@ -223,7 +223,7 @@ export const VariantRules = class GrandRules extends ChessRules { for (let epsq of epSquare) { // TODO: some redundant checks if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) { - var enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); + let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); // WARNING: the captured pawn may be diagonally behind us, // if it's a 3-squares jump and we take on 1st passing square const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX; diff --git a/client/src/variants/Grasshopper.js b/client/src/variants/Grasshopper.js index ec52d9b3..fb8c27cd 100644 --- a/client/src/variants/Grasshopper.js +++ b/client/src/variants/Grasshopper.js @@ -2,11 +2,22 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class GrasshopperRules extends ChessRules { +export class GrasshopperRules extends ChessRules { static get HasEnpassant() { return false; } + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { + twoSquares: false, + promotions: ChessRules.PawnSpecs.promotions.concat([V.GRASSHOPPER]) + } + ); + } + static get GRASSHOPPER() { return "g"; } diff --git a/client/src/variants/Hidden.js b/client/src/variants/Hidden.js index 8a5858ea..c060310c 100644 --- a/client/src/variants/Hidden.js +++ b/client/src/variants/Hidden.js @@ -2,7 +2,7 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class HiddenRules extends ChessRules { +export class HiddenRules extends ChessRules { static get HasFlags() { return false; } diff --git a/client/src/variants/Hiddenqueen.js b/client/src/variants/Hiddenqueen.js index 15d9b1e9..8db5a50e 100644 --- a/client/src/variants/Hiddenqueen.js +++ b/client/src/variants/Hiddenqueen.js @@ -2,7 +2,7 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class HiddenqueenRules extends ChessRules { +export class HiddenqueenRules extends ChessRules { // Analyse in Hiddenqueen mode makes no sense static get CanAnalyze() { return false; @@ -87,79 +87,13 @@ export const VariantRules = class HiddenqueenRules extends ChessRules { return super.getPotentialMovesFrom([x, y]); } - // TODO: find a more general way to describe pawn movements to avoid - // re-writing almost the same function for several variants. getPotentialPawnMoves([x, y]) { - const color = this.turn; const piece = this.getPiece(x, y); - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - - const finalPieces = - x + shiftX == lastRank - ? piece == V.PAWN - ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] - : [V.QUEEN] //hidden queen revealed - : [piece]; //V.PAWN - if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward - for (let p of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: p - }) - ); - } - if ( - x == startRank && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let p of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: p - }) - ); - } - } - } - - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare.x == x + shiftX && - Math.abs(epSquare.y - y) == 1 - ) { - let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare.y, - p: "p", - c: this.getColor(x, epSquare.y) - }); - moves.push(enpassantMove); - } - - return moves; + const promotions = + piece == V.PAWN + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.QUEEN]; //hidden queen revealed + return super.getPotentialPawnMoves([x, y], promotions); } getPossibleMovesFrom(sq) { diff --git a/client/src/variants/Knightmate.js b/client/src/variants/Knightmate.js index a8d6dc6c..593fcc09 100644 --- a/client/src/variants/Knightmate.js +++ b/client/src/variants/Knightmate.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class KnightmateRules extends ChessRules { +export class KnightmateRules extends ChessRules { static get COMMONER() { return "c"; } diff --git a/client/src/variants/Knightrelay1.js b/client/src/variants/Knightrelay1.js index 3e7bf38c..1d1ab527 100644 --- a/client/src/variants/Knightrelay1.js +++ b/client/src/variants/Knightrelay1.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class Knightrelay1Rules extends ChessRules { +export class Knightrelay1Rules extends ChessRules { static get HasEnpassant() { return false; } diff --git a/client/src/variants/Knightrelay2.js b/client/src/variants/Knightrelay2.js index 9fa5dcca..0fc6c331 100644 --- a/client/src/variants/Knightrelay2.js +++ b/client/src/variants/Knightrelay2.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class Knightrelay2Rules extends ChessRules { +export class Knightrelay2Rules extends ChessRules { getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x, y]); diff --git a/client/src/variants/Losers.js b/client/src/variants/Losers.js index d752f984..1c087eff 100644 --- a/client/src/variants/Losers.js +++ b/client/src/variants/Losers.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class LosersRules extends ChessRules { +export class LosersRules extends ChessRules { // Trim all non-capturing moves static KeepCaptures(moves) { return moves.filter(m => m.vanish.length == 2); diff --git a/client/src/variants/Magnetic.js b/client/src/variants/Magnetic.js index a59ce411..a6150cb5 100644 --- a/client/src/variants/Magnetic.js +++ b/client/src/variants/Magnetic.js @@ -1,6 +1,6 @@ import { ChessRules, PiPo } from "@/base_rules"; -export const VariantRules = class MagneticRules extends ChessRules { +export class MagneticRules extends ChessRules { static get HasEnpassant() { return false; } diff --git a/client/src/variants/Marseille.js b/client/src/variants/Marseille.js index c3bec32f..ed9965b0 100644 --- a/client/src/variants/Marseille.js +++ b/client/src/variants/Marseille.js @@ -1,7 +1,7 @@ import { ChessRules } from "@/base_rules"; import { randInt } from "@/utils/alea"; -export const VariantRules = class MarseilleRules extends ChessRules { +export class MarseilleRules extends ChessRules { static IsGoodEnpassant(enpassant) { const squares = enpassant.split(","); if (squares.length > 2) return false; @@ -43,52 +43,8 @@ export const VariantRules = class MarseilleRules extends ChessRules { this.subTurn = fullTurn[1] || 1; } - getPotentialPawnMoves([x, y]) { - const color = this.turn; + getEnpassantCaptures([x, y], shiftX) { let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const firstRank = color == "w" ? sizeX - 1 : 0; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - const finalPieces = - x + shiftX == lastRank ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] : [V.PAWN]; - - // One square forward - if (this.board[x + shiftX][y] == V.EMPTY) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) - ); - } - // Next condition because pawns on 1st rank can generally jump - if ( - [startRank, firstRank].includes(x) && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: piece - }) - ); - } - } - } - // En passant: always OK if subturn 1, // OK on subturn 2 only if enPassant was played at subturn 1 // (and if there are two e.p. squares available). @@ -99,7 +55,7 @@ export const VariantRules = class MarseilleRules extends ChessRules { if (sq) epSqs.push(sq); }); if (epSqs.length == 0) return moves; - const oppCol = V.GetOppCol(color); + const oppCol = V.GetOppCol(this.getColor(x, y)); for (let sq of epSqs) { if ( this.subTurn == 1 || @@ -126,7 +82,6 @@ export const VariantRules = class MarseilleRules extends ChessRules { } } } - return moves; } diff --git a/client/src/variants/Racingkings.js b/client/src/variants/Racingkings.js index f1161fce..11457485 100644 --- a/client/src/variants/Racingkings.js +++ b/client/src/variants/Racingkings.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class RacingkingsRules extends ChessRules { +export class RacingkingsRules extends ChessRules { static get HasFlags() { return false; } diff --git a/client/src/variants/Recycle.js b/client/src/variants/Recycle.js index d91e8248..8980c721 100644 --- a/client/src/variants/Recycle.js +++ b/client/src/variants/Recycle.js @@ -1,7 +1,15 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; -export const VariantRules = class RecycleRules extends ChessRules { +export class RecycleRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { promotions: [V.PAWN] } //in fact, none + ); + } + static IsGoodFen(fen) { if (!ChessRules.IsGoodFen(fen)) return false; const fenParsed = V.ParseFen(fen); @@ -131,66 +139,13 @@ export const VariantRules = class RecycleRules extends ChessRules { } getPotentialPawnMoves([x, y]) { + let moves = super.getPotentialPawnMoves([x, y]); + // Remove pawns on 8th rank ("fallen"): const color = this.turn; - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - - // One square forward - if (this.board[x + shiftX][y] == V.EMPTY) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y]) - ); - // Next condition because pawns on 1st rank can generally jump - if ( - x == startRank && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY]) - ); - } - } - - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare.x == x + shiftX && - Math.abs(epSquare.y - y) == 1 - ) { - let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare.y, - p: "p", - c: this.getColor(x, epSquare.y) - }); - moves.push(enpassantMove); - } - - // Post-processing: remove falling pawns - if (x + shiftX == lastRank) { - moves.forEach(m => { - m.appear.pop(); - }); - } - + const lastRank = (color == "w" ? 0 : V.size.x - 1); + moves.forEach(m => { + if (m.appear[0].x == lastRank) m.appear.pop(); + }); return moves; } @@ -223,10 +178,10 @@ export const VariantRules = class RecycleRules extends ChessRules { return this.getPiece(x2, y2) != V.KING; } - postPlay(move) { - super.postPlay(move); + prePlay(move) { + super.prePlay(move); if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle - const color = move.appear[0].c; + const color = this.turn; if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; else if (move.vanish.length == 2 && move.vanish[1].c == color) // Self-capture diff --git a/client/src/variants/Rifle.js b/client/src/variants/Rifle.js index 1cdbbbe4..98661ae7 100644 --- a/client/src/variants/Rifle.js +++ b/client/src/variants/Rifle.js @@ -1,6 +1,6 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -export const VariantRules = class RifleRules extends ChessRules { +export class RifleRules extends ChessRules { getEpSquare(moveOrSquare) { if (typeof moveOrSquare !== "object" || moveOrSquare.appear.length > 0) return super.getEpSquare(moveOrSquare); @@ -49,54 +49,8 @@ export const VariantRules = class RifleRules extends ChessRules { return mv; } - getPotentialPawnMoves([x, y]) { - const color = this.turn; + getEnpassantCaptures([x, y], shiftX) { let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - - const finalPieces = - x + shiftX == lastRank - ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] - : [V.PAWN]; - if (this.board[x + shiftX][y] == V.EMPTY) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: piece - }) - ); - } - if ( - x == startRank && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: piece - }) - ); - } - } - } - - // En passant const Lep = this.epSquares.length; const epSquare = this.epSquares[Lep - 1]; //always at least one element if ( @@ -118,7 +72,10 @@ export const VariantRules = class RifleRules extends ChessRules { }); moves.push(enpassantMove); } - return moves; } + + static get SEARCH_DEPTH() { + return 2; + } }; diff --git a/client/src/variants/Royalrace.js b/client/src/variants/Royalrace.js index c9095558..9b6575ac 100644 --- a/client/src/variants/Royalrace.js +++ b/client/src/variants/Royalrace.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt, shuffle } from "@/utils/alea"; -export const VariantRules = class RoyalraceRules extends ChessRules { +export class RoyalraceRules extends ChessRules { static get HasFlags() { return false; } diff --git a/client/src/variants/Shatranj.js b/client/src/variants/Shatranj.js index 45a34c04..00b14564 100644 --- a/client/src/variants/Shatranj.js +++ b/client/src/variants/Shatranj.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class ShatranjRules extends ChessRules { +export class ShatranjRules extends ChessRules { static get HasFlags() { return false; } @@ -13,6 +13,17 @@ export const VariantRules = class ShatranjRules extends ChessRules { return false; } + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { + twoSquares: false, + promotions: [V.QUEEN] + } + ); + } + static get ElephantSteps() { return [ [-2, -2], @@ -27,45 +38,6 @@ export const VariantRules = class ShatranjRules extends ChessRules { return ChessRules.GenRandInitFen(randomness).slice(0, -7); } - getPotentialPawnMoves([x, y]) { - const color = this.turn; - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - // Promotion in minister (queen) only: - const finalPiece = x + shiftX == lastRank ? V.QUEEN : V.PAWN; - - if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: finalPiece - }) - ); - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: finalPiece - }) - ); - } - } - - return moves; - } - getPotentialBishopMoves(sq) { let moves = this.getSlideNJumpMoves(sq, V.ElephantSteps, "oneStep"); // Complete with "repositioning moves": like a queen, without capture diff --git a/client/src/variants/Suction.js b/client/src/variants/Suction.js index a39bec0b..d15a7c84 100644 --- a/client/src/variants/Suction.js +++ b/client/src/variants/Suction.js @@ -1,6 +1,6 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -export const VariantRules = class SuctionRules extends ChessRules { +export class SuctionRules extends ChessRules { static get HasFlags() { return false; } @@ -85,50 +85,8 @@ export const VariantRules = class SuctionRules extends ChessRules { return mv; } - getPotentialPawnMoves([x, y]) { - const color = this.turn; + getEnpassantCaptures([x, y], shiftX) { let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; - const shiftX = color == "w" ? -1 : 1; - const startRank = color == "w" ? sizeX - 2 : 1; - const firstRank = color == "w" ? sizeX - 1 : 0; - - if (x + shiftX >= 0 && x + shiftX < sizeX) { - // One square forward - if (this.board[x + shiftX][y] == V.EMPTY) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: "p" - }) - ); - if ( - [startRank,firstRank].includes(x) && - this.board[x + 2 * shiftX][y] == V.EMPTY - ) { - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - } - // Swaps - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: "p" - }) - ); - } - } - } - - // En passant const Lep = this.epSquares.length; const epSquare = this.epSquares[Lep - 1]; //always at least one element if ( @@ -152,7 +110,6 @@ export const VariantRules = class SuctionRules extends ChessRules { }); moves.push(enpassantMove); } - return moves; } diff --git a/client/src/variants/Suicide.js b/client/src/variants/Suicide.js index 4bb5d36b..1304ba98 100644 --- a/client/src/variants/Suicide.js +++ b/client/src/variants/Suicide.js @@ -1,8 +1,8 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; -import { randInt } from "@/utils/alea"; +import { shuffle } from "@/utils/alea"; -export const VariantRules = class SuicideRules extends ChessRules { +export class SuicideRules extends ChessRules { static get HasFlags() { return false; } @@ -11,41 +11,12 @@ export const VariantRules = class SuicideRules extends ChessRules { return false; } - getPotentialPawnMoves([x, y]) { - let moves = super.getPotentialPawnMoves([x, y]); - - // Complete with promotion(s) into king, if possible - const color = this.turn; - const shift = color == "w" ? -1 : 1; - const lastRank = color == "w" ? 0 : V.size.x - 1; - if (x + shift == lastRank) { - // Normal move - if (this.board[x + shift][y] == V.EMPTY) - moves.push( - this.getBasicMove([x, y], [x + shift, y], { c: color, p: V.KING }) - ); - // Captures - if ( - y > 0 && - this.canTake([x, y], [x + shift, y - 1]) && - this.board[x + shift][y - 1] != V.EMPTY - ) { - moves.push( - this.getBasicMove([x, y], [x + shift, y - 1], { c: color, p: V.KING }) - ); - } - if ( - y < V.size.y - 1 && - this.canTake([x, y], [x + shift, y + 1]) && - this.board[x + shift][y + 1] != V.EMPTY - ) { - moves.push( - this.getBasicMove([x, y], [x + shift, y + 1], { c: color, p: V.KING }) - ); - } - } - - return moves; + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) } + ); } // Trim all non-capturing moves (not the most efficient, but easy) @@ -152,49 +123,18 @@ export const VariantRules = class SuicideRules extends ChessRules { break; } - let positions = ArrayFun.range(8); - - // Get random squares for bishops - let randIndex = 2 * randInt(4); - let bishop1Pos = positions[randIndex]; - // The second bishop must be on a square of different color - let randIndex_tmp = 2 * randInt(4) + 1; - let bishop2Pos = positions[randIndex_tmp]; - // Remove chosen squares - positions.splice(Math.max(randIndex, randIndex_tmp), 1); - positions.splice(Math.min(randIndex, randIndex_tmp), 1); - - // Get random squares for knights - randIndex = randInt(6); - let knight1Pos = positions[randIndex]; - positions.splice(randIndex, 1); - randIndex = randInt(5); - let knight2Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Get random square for queen - randIndex = randInt(4); - let queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Random square for king (no castle) - randIndex = randInt(3); - let kingPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Rooks positions are now fixed - let rook1Pos = positions[0]; - let rook2Pos = positions[1]; - - // Finally put the shuffled pieces in the board array - pieces[c][rook1Pos] = "r"; - pieces[c][knight1Pos] = "n"; - pieces[c][bishop1Pos] = "b"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][bishop2Pos] = "b"; - pieces[c][knight2Pos] = "n"; - pieces[c][rook2Pos] = "r"; + // Get random squares for every piece, totally freely + let positions = shuffle(ArrayFun.range(8)); + const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q']; + const rem2 = positions[0] % 2; + if (rem2 == positions[1] % 2) { + // Fix bishops (on different colors) + for (let i=2; i<8; i++) { + if (positions[i] % 2 != rem2) + [positions[1], positions[i]] = [positions[i], positions[1]]; + } + } + for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i]; } return ( pieces["b"].join("") + diff --git a/client/src/variants/Threechecks.js b/client/src/variants/Threechecks.js index 3aa6db98..f9fd4d46 100644 --- a/client/src/variants/Threechecks.js +++ b/client/src/variants/Threechecks.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class ThreechecksRules extends ChessRules { +export class ThreechecksRules extends ChessRules { static IsGoodFlags(flags) { // 4 for castle + 2 for checks (0,1 or 2) return !!flags.match(/^[01]{4,4}[012]{2,2}$/); diff --git a/client/src/variants/Upsidedown.js b/client/src/variants/Upsidedown.js index e8242004..20768240 100644 --- a/client/src/variants/Upsidedown.js +++ b/client/src/variants/Upsidedown.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { randInt } from "@/utils/alea"; import { ArrayFun } from "@/utils/array"; -export const VariantRules = class UpsidedownRules extends ChessRules { +export class UpsidedownRules extends ChessRules { static get HasFlags() { return false; } diff --git a/client/src/variants/Wildebeest.js b/client/src/variants/Wildebeest.js index f4e3b83c..8192fa04 100644 --- a/client/src/variants/Wildebeest.js +++ b/client/src/variants/Wildebeest.js @@ -2,7 +2,7 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { sample, randInt } from "@/utils/alea"; -export const VariantRules = class WildebeestRules extends ChessRules { +export class WildebeestRules extends ChessRules { static get size() { return { x: 10, y: 11 }; } diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js index 41c934a3..f47fea39 100644 --- a/client/src/variants/Wormhole.js +++ b/client/src/variants/Wormhole.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class WormholeRules extends ChessRules { +export class WormholeRules extends ChessRules { static get HasFlags() { return false; } diff --git a/client/src/variants/Zen.js b/client/src/variants/Zen.js index bc51e6ad..56e7108b 100644 --- a/client/src/variants/Zen.js +++ b/client/src/variants/Zen.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class ZenRules extends ChessRules { +export class ZenRules extends ChessRules { // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare static get HasEnpassant() { return false; diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index becc4de0..484b8046 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -71,7 +71,7 @@ export default { // Obtain VariantRules object await import("@/variants/" + this.gameRef.vname + ".js") .then((vModule) => { - window.V = vModule.VariantRules; + window.V = vModule[this.gameRef.vname + "Rules"]; if (!V.CanAnalyze) // Late check, in case the user tried to enter URL by hand this.alertAndQuit("Analysis disabled for this variant"); diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index bceb361c..0b31bc3b 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -913,156 +913,161 @@ export default { // - from server (one correspondance game I play[ed] or not) // - from remote peer (one live game I don't play, finished or not) loadGame: function(game, callback) { - const afterRetrieval = async (game) => { - const vModule = await import("@/variants/" + game.vname + ".js"); - window.V = vModule.VariantRules; - this.vr = new V(game.fen); - const gtype = this.getGameType(game); - const tc = extractTime(game.cadence); - const myIdx = game.players.findIndex(p => { - return p.sid == this.st.user.sid || p.id == this.st.user.id; - }); - const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers - if (!game.chats) game.chats = []; //live games don't have chat history - if (gtype == "corr") { - // NOTE: clocks in seconds, initime in milliseconds - game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of - game.clocks = [tc.mainTime, tc.mainTime]; - const L = game.moves.length; - if (game.score == "*") { - // Set clocks + initime - game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; - if (L >= 1) game.initime[L % 2] = game.moves[L-1].played; - // NOTE: game.clocks shouldn't be computed right now: - // job will be done in re_setClocks() called soon below. - } - // Sort chat messages from newest to oldest - game.chats.sort((c1, c2) => { - return c2.added - c1.added; - }); - if (myIdx >= 0 && game.score == "*" && game.chats.length > 0) { - // Did a chat message arrive after my last move? - let dtLastMove = 0; - if (L == 1 && myIdx == 0) - dtLastMove = game.moves[0].played; - else if (L >= 2) { - if (L % 2 == 0) { - // It's now white turn - dtLastMove = game.moves[L-1-(1-myIdx)].played; - } else { - // Black turn: - dtLastMove = game.moves[L-1-myIdx].played; - } - } - if (dtLastMove < game.chats[0].added) - document.getElementById("chatBtn").classList.add("somethingnew"); - } - // Now that we used idx and played, re-format moves as for live games - game.moves = game.moves.map(m => m.squares); - } - if (gtype == "live" && game.clocks[0] < 0) { - // Game is unstarted. clocks and initime are ignored until move 2 - game.clocks = [tc.mainTime, tc.mainTime]; + this.vr = new V(game.fen); + const gtype = this.getGameType(game); + const tc = extractTime(game.cadence); + const myIdx = game.players.findIndex(p => { + return p.sid == this.st.user.sid || p.id == this.st.user.id; + }); + const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers + if (!game.chats) game.chats = []; //live games don't have chat history + if (gtype == "corr") { + // NOTE: clocks in seconds, initime in milliseconds + game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of + game.clocks = [tc.mainTime, tc.mainTime]; + const L = game.moves.length; + if (game.score == "*") { + // Set clocks + initime game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; - if (myIdx >= 0) { - // I play in this live game - GameStorage.update(game.id, { - clocks: game.clocks, - initime: game.initime - }); - } + if (L >= 1) game.initime[L % 2] = game.moves[L-1].played; + // NOTE: game.clocks shouldn't be computed right now: + // job will be done in re_setClocks() called soon below. } - // TODO: merge next 2 "if" conditions - if (!!game.drawOffer) { - if (game.drawOffer == "t") - // Three repetitions - this.drawOffer = "threerep"; - else { - // Draw offered by any of the players: - if (myIdx < 0) this.drawOffer = "received"; - else { - // I play in this game: - if ( - (game.drawOffer == "w" && myIdx == 0) || - (game.drawOffer == "b" && myIdx == 1) - ) - this.drawOffer = "sent"; - else this.drawOffer = "received"; + // Sort chat messages from newest to oldest + game.chats.sort((c1, c2) => { + return c2.added - c1.added; + }); + if (myIdx >= 0 && game.score == "*" && game.chats.length > 0) { + // Did a chat message arrive after my last move? + let dtLastMove = 0; + if (L == 1 && myIdx == 0) + dtLastMove = game.moves[0].played; + else if (L >= 2) { + if (L % 2 == 0) { + // It's now white turn + dtLastMove = game.moves[L-1-(1-myIdx)].played; + } else { + // Black turn: + dtLastMove = game.moves[L-1-myIdx].played; } } + if (dtLastMove < game.chats[0].added) + document.getElementById("chatBtn").classList.add("somethingnew"); + } + // Now that we used idx and played, re-format moves as for live games + game.moves = game.moves.map(m => m.squares); + } + if (gtype == "live" && game.clocks[0] < 0) { + // Game is unstarted. clocks and initime are ignored until move 2 + game.clocks = [tc.mainTime, tc.mainTime]; + game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; + if (myIdx >= 0) { + // I play in this live game + GameStorage.update(game.id, { + clocks: game.clocks, + initime: game.initime + }); } - if (!!game.rematchOffer) { - if (myIdx < 0) this.rematchOffer = "received"; + } + // TODO: merge next 2 "if" conditions + if (!!game.drawOffer) { + if (game.drawOffer == "t") + // Three repetitions + this.drawOffer = "threerep"; + else { + // Draw offered by any of the players: + if (myIdx < 0) this.drawOffer = "received"; else { // I play in this game: if ( - (game.rematchOffer == "w" && myIdx == 0) || - (game.rematchOffer == "b" && myIdx == 1) + (game.drawOffer == "w" && myIdx == 0) || + (game.drawOffer == "b" && myIdx == 1) ) - this.rematchOffer = "sent"; - else this.rematchOffer = "received"; + this.drawOffer = "sent"; + else this.drawOffer = "received"; } } - this.repeat = {}; //reset: scan past moves' FEN: - let repIdx = 0; - let vr_tmp = new V(game.fenStart); - let curTurn = "n"; - game.moves.forEach(m => { - playMove(m, vr_tmp); - const fenIdx = vr_tmp.getFen().replace(/ /g, "_"); - this.repeat[fenIdx] = this.repeat[fenIdx] - ? this.repeat[fenIdx] + 1 - : 1; - }); - if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; - this.game = Object.assign( - // NOTE: assign mycolor here, since BaseGame could also be VS computer - { - type: gtype, - increment: tc.increment, - mycolor: mycolor, - // opponent sid not strictly required (or available), but easier - // at least oppsid or oppid is available anyway: - oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid, - oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].id - }, - game - ); - this.$refs["basegame"].re_setVariables(this.game); - if (!this.gameIsLoading) { - // Initial loading: - this.gotMoveIdx = game.moves.length - 1; - // If we arrive here after 'nextGame' action, the board might be hidden - let boardDiv = document.querySelector(".game"); - if (!!boardDiv && boardDiv.style.visibility == "hidden") - boardDiv.style.visibility = "visible"; + } + if (!!game.rematchOffer) { + if (myIdx < 0) this.rematchOffer = "received"; + else { + // I play in this game: + if ( + (game.rematchOffer == "w" && myIdx == 0) || + (game.rematchOffer == "b" && myIdx == 1) + ) + this.rematchOffer = "sent"; + else this.rematchOffer = "received"; } - this.re_setClocks(); - this.$nextTick(() => { - this.game.rendered = true; - // Did lastate arrive before game was rendered? - if (this.lastate) this.processLastate(); + } + this.repeat = {}; //reset: scan past moves' FEN: + let repIdx = 0; + let vr_tmp = new V(game.fenStart); + let curTurn = "n"; + game.moves.forEach(m => { + playMove(m, vr_tmp); + const fenIdx = vr_tmp.getFen().replace(/ /g, "_"); + this.repeat[fenIdx] = this.repeat[fenIdx] + ? this.repeat[fenIdx] + 1 + : 1; + }); + if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; + this.game = Object.assign( + // NOTE: assign mycolor here, since BaseGame could also be VS computer + { + type: gtype, + increment: tc.increment, + mycolor: mycolor, + // opponent sid not strictly required (or available), but easier + // at least oppsid or oppid is available anyway: + oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid, + oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].id + }, + game + ); + this.$refs["basegame"].re_setVariables(this.game); + if (!this.gameIsLoading) { + // Initial loading: + this.gotMoveIdx = game.moves.length - 1; + // If we arrive here after 'nextGame' action, the board might be hidden + let boardDiv = document.querySelector(".game"); + if (!!boardDiv && boardDiv.style.visibility == "hidden") + boardDiv.style.visibility = "visible"; + } + this.re_setClocks(); + this.$nextTick(() => { + this.game.rendered = true; + // Did lastate arrive before game was rendered? + if (this.lastate) this.processLastate(); + }); + if (this.lastateAsked) { + this.lastateAsked = false; + this.sendLastate(game.oppsid); + } + if (this.gameIsLoading) { + this.gameIsLoading = false; + if (this.gotMoveIdx >= game.moves.length) + // Some moves arrived meanwhile... + this.askGameAgain(); + } + if (!!callback) callback(); + }, + fetchGame: function(game, callback) { + const afterRetrieval = async (game) => { + await import("@/variants/" + game.vname + ".js") + .then((vModule) => { + window.V = vModule[game.vname + "Rules"]; + this.loadGame(game, callback); }); - if (this.lastateAsked) { - this.lastateAsked = false; - this.sendLastate(game.oppsid); - } - if (this.gameIsLoading) { - this.gameIsLoading = false; - if (this.gotMoveIdx >= game.moves.length) - // Some moves arrived meanwhile... - this.askGameAgain(); - } - if (!!callback) callback(); }; if (!!game) { afterRetrieval(game); return; } - if (this.gameRef.rid) { + if (this.gameRef.rid) // Remote live game: forgetting about callback func... (TODO: design) this.send("askfullgame", { target: this.gameRef.rid }); - } else { + else { // Local or corr game on server. // NOTE: afterRetrieval() is never called if game not found const gid = this.gameRef.id; @@ -1074,11 +1079,10 @@ export default { { data: { gid: gid }, success: (res) => { - let g = res.game; - g.moves.forEach(m => { + res.game.moves.forEach(m => { m.squares = JSON.parse(m.squares); }); - afterRetrieval(g); + afterRetrieval(res.game); } } ); diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 8f86f4c4..0840cd91 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -901,10 +901,13 @@ export default { }, loadNewchallVariant: async function(cb) { const vname = this.getVname(this.newchallenge.vid); - const vModule = await import("@/variants/" + vname + ".js"); - this.newchallenge.V = vModule.VariantRules; - this.newchallenge.vname = vname; - if (!!cb) cb(); + await import("@/variants/" + vname + ".js") + .then((vModule) => { + window.V = vModule[vname + "Rules"]; + this.newchallenge.V = window.V; + this.newchallenge.vname = vname; + if (!!cb) cb(); + }); }, trySetNewchallDiag: function() { if (!this.newchallenge.fen) { @@ -1101,22 +1104,24 @@ export default { return; } c.accepted = true; - const vModule = await import("@/variants/" + c.vname + ".js"); - window.V = vModule.VariantRules; - if (!!c.to) { - // c.to == this.st.user.name (connected) - if (!!c.fen) { - const parsedFen = V.ParseFen(c.fen); - c.mycolor = V.GetOppCol(parsedFen.turn); - this.tchallDiag = getDiagram({ - position: parsedFen.position, - orientation: c.mycolor - }); + await import("@/variants/" + c.vname + ".js") + .then((vModule) => { + window.V = vModule[c.vname + "Rules"]; + if (!!c.to) { + // c.to == this.st.user.name (connected) + if (!!c.fen) { + const parsedFen = V.ParseFen(c.fen); + c.mycolor = V.GetOppCol(parsedFen.turn); + this.tchallDiag = getDiagram({ + position: parsedFen.position, + orientation: c.mycolor + }); + } + this.curChallToAccept = c; + document.getElementById("modalAccept").checked = true; } - this.curChallToAccept = c; - document.getElementById("modalAccept").checked = true; - } - else this.finishProcessingChallenge(c); + else this.finishProcessingChallenge(c); + }); } else { // My challenge diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue index 2ea875e0..ae918b95 100644 --- a/client/src/views/Problems.vue +++ b/client/src/views/Problems.vue @@ -284,10 +284,12 @@ export default { // Condition: vid is a valid variant ID this.loadedVar = 0; const variant = this.st.variants.find(v => v.id == vid); - const vModule = await import("@/variants/" + variant.name + ".js"); - window.V = vModule.VariantRules; - this.loadedVar = vid; - cb(); + await import("@/variants/" + variant.name + ".js") + .then((vModule) => { + window.V = vModule[variant.name + "Rules"]; + this.loadedVar = vid; + cb(); + }); }, trySetDiagram: function(prob) { // Problem edit: FEN could be wrong or incomplete, diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index 844457d6..2af53b49 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -114,7 +114,7 @@ export default { re_setVariant: async function(vname) { await import("@/variants/" + vname + ".js") .then((vModule) => { - this.V = window.V = vModule.VariantRules; + this.V = window.V = vModule[vname + "Rules"]; this.gameInfo.vname = vname; }) .catch((err) => { @@ -176,12 +176,13 @@ figure.diagram-container display: block .diagram display: block - width: 40% + width: 50% min-width: 240px margin-left: auto margin-right: auto .diag12 float: left + width: 40% margin-left: calc(10% - 20px) margin-right: 40px @media screen and (max-width: 630px) @@ -189,6 +190,7 @@ figure.diagram-container margin: 0 auto 10px auto .diag22 float: left + width: 40% margin-right: calc(10% - 20px) @media screen and (max-width: 630px) float: none