X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FEightpieces.js;h=dc7580b32a63e806eb2eb6a6737e5ec7c9ab0bd9;hb=abbda16dbd485dfc43ee502bbef67045b1aecc59;hp=f7d93d8484382cacf4e711d561bdf83949e96ccb;hpb=518a0dc97d18276d7c0cf946c69ec761b1ed50a5;p=vchess.git diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index f7d93d84..dc7580b3 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -1,8 +1,8 @@ -import { ArrayFun } from "@/utils/array"; -import { randInt } from "@/utils/alea"; +import { randInt, sample } 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"; } @@ -83,8 +83,15 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return "Eightpieces/tmp_png/" + b; } - getPPpath(b, orientation) { - return this.getPpath(b, null, null, orientation); + getPPpath(m, orientation) { + return ( + this.getPpath( + m.appear[0].c + m.appear[0].p, + null, + null, + orientation + ) + ); } static ParseFen(fen) { @@ -101,7 +108,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules { // 5) Check sentry push (if any) if ( fenParsed.sentrypush != "-" && - !fenParsed.sentrypush.match(/^([a-h][1-8],?)+$/) + !fenParsed.sentrypush.match(/^([a-h][1-8]){2,2}$/) ) { return false; } @@ -120,9 +127,11 @@ export const VariantRules = class EightpiecesRules extends ChessRules { const L = this.sentryPush.length; if (!this.sentryPush[L-1]) return "-"; let res = ""; - this.sentryPush[L-1].forEach(coords => - res += V.CoordsToSquare(coords) + ","); - return res.slice(0, -1); + const spL = this.sentryPush[L-1].length; + // Condensate path: just need initial and final squares: + return [0, spL - 1] + .map(i => V.CoordsToSquare(this.sentryPush[L-1][i])) + .join(""); } setOtherVariables(fen) { @@ -135,88 +144,77 @@ export const VariantRules = class EightpiecesRules extends ChessRules { const parsedFen = V.ParseFen(fen); if (parsedFen.sentrypush == "-") this.sentryPush = [null]; else { - this.sentryPush = [ - parsedFen.sentrypush.split(",").map(sq => { - return V.SquareToCoords(sq); - }) - ]; - } - } - - static GenRandInitFen(randomness) { - if (randomness == 0) - // Deterministic: - return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 ahah - -"; - - let pieces = { w: new Array(8), b: new Array(8) }; - let flags = ""; - // Shuffle pieces on first (and last rank if randomness == 2) - for (let c of ["w", "b"]) { - if (c == 'b' && randomness == 1) { - const lancerIdx = pieces['w'].findIndex(p => { - return Object.keys(V.LANCER_DIRS).includes(p); + // Expand init + dest squares into a full path: + const init = V.SquareToCoords(parsedFen.sentrypush.substr(0, 2)), + dest = V.SquareToCoords(parsedFen.sentrypush.substr(2)); + let newPath = [init]; + const delta = ['x', 'y'].map(i => Math.abs(dest[i] - init[i])); + // Check that it's not a knight movement: + if (delta[0] == 0 || delta[1] == 0 || delta[0] == delta[1]) { + const step = ['x', 'y'].map((i, idx) => { + return (dest[i] - init[i]) / delta[idx] || 0 }); - pieces['b'] = - pieces['w'].slice(0, lancerIdx) - .concat(['g']) - .concat(pieces['w'].slice(lancerIdx + 1)); - flags += flags; - break; + let x = init.x + step[0], + y = init.y + step[1]; + while (x != dest.x || y != dest.y) { + newPath.push({ x: x, y: y }); + x += step[0]; + y += step[1]; + } } + newPath.push(dest); + this.sentryPush = [newPath]; + } + } - let positions = ArrayFun.range(8); - - // Get random squares for bishop and sentry - let randIndex = 2 * randInt(4); - let bishopPos = positions[randIndex]; - // The sentry must be on a square of different color - let randIndex_tmp = 2 * randInt(4) + 1; - let sentryPos = positions[randIndex_tmp]; - if (c == 'b') { - // Check if white sentry is on the same color as ours. - // If yes: swap bishop and sentry positions. - if ((pieces['w'].indexOf('s') - sentryPos) % 2 == 0) - [bishopPos, sentryPos] = [sentryPos, bishopPos]; + static GenRandInitFen(options) { + if (options.randomness == 0) + return "jfsqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JDSQKBNR w 0 ahah - -"; + + const baseFen = ChessRules.GenRandInitFen(options); + const fenParts = baseFen.split(' '); + const posParts = fenParts[0].split('/'); + + // Replace one bishop by sentry, so that sentries on different colors + // Also replace one random rook by jailer, + // and one random knight by lancer (facing north/south) + let pieceLine = { b: posParts[0], w: posParts[7].toLowerCase() }; + let posBlack = { r: -1, n: -1, b: -1 }; + const mapP = { r: 'j', n: 'l', b: 's' }; + ['w', 'b'].forEach(c => { + ['r', 'n', 'b'].forEach(p => { + let pl = pieceLine[c]; + let pos = -1; + if (options.randomness == 2 || c == 'b') + pos = (randInt(2) == 0 ? pl.indexOf(p) : pl.lastIndexOf(p)); + else pos = posBlack[p]; + pieceLine[c] = + pieceLine[c].substr(0, pos) + mapP[p] + pieceLine[c].substr(pos+1); + if (options.randomness == 1 && c == 'b') posBlack[p] = pos; + }); + }); + // Rename 'l' into 'g' (black) or 'c' (white) + pieceLine['w'] = pieceLine['w'].replace('l', 'c'); + pieceLine['b'] = pieceLine['b'].replace('l', 'g'); + if (options.randomness == 2) { + const ws = pieceLine['w'].indexOf('s'); + const bs = pieceLine['b'].indexOf('s'); + if (ws % 2 != bs % 2) { + // Fix sentry: should be on different colors. + // => move sentry on other bishop for random color + const c = sample(['w', 'b'], 1); + pieceLine[c] = pieceLine[c] + .replace('b', 't'); //tmp + .replace('s', 'b'); + .replace('t', 's'); } - positions.splice(Math.max(randIndex, randIndex_tmp), 1); - positions.splice(Math.min(randIndex, randIndex_tmp), 1); - - // Get random squares for knight and lancer - randIndex = randInt(6); - const knightPos = positions[randIndex]; - positions.splice(randIndex, 1); - randIndex = randInt(5); - const lancerPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Get random square for queen - randIndex = randInt(4); - const queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Rook, jailer and king positions are now almost fixed, - // only the ordering rook-> jailer or jailer->rook must be decided. - let rookPos = positions[0]; - let jailerPos = positions[2]; - const kingPos = positions[1]; - flags += V.CoordToColumn(rookPos) + V.CoordToColumn(jailerPos); - if (Math.random() < 0.5) [rookPos, jailerPos] = [jailerPos, rookPos]; - - pieces[c][rookPos] = "r"; - pieces[c][knightPos] = "n"; - pieces[c][bishopPos] = "b"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][sentryPos] = "s"; - // Lancer faces north for white, and south for black: - pieces[c][lancerPos] = c == 'w' ? 'c' : 'g'; - pieces[c][jailerPos] = "j"; } + return ( - pieces["b"].join("") + - "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - " w 0 " + flags + " - -" + pieceLine['b'] + "/" + + posParts.slice(1, 7).join('/') + "/" + + pieceLine['w'].toUpperCase() + " " + + fenParts.slice(1, 5).join(' ') + " -" ); } @@ -244,54 +242,18 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return null; } - // Because of the lancers, getPiece() could be wrong: - // use board[x][y][1] instead (always valid). - getBasicMove([sx, sy], [ex, ey], tr) { - let mv = new Move({ - appear: [ - new PiPo({ - x: ex, - y: ey, - c: tr ? tr.c : this.getColor(sx, sy), - p: tr ? tr.p : this.board[sx][sy].charAt(1) - }) - ], - vanish: [ - new PiPo({ - x: sx, - y: sy, - c: this.getColor(sx, sy), - p: this.board[sx][sy].charAt(1) - }) - ] - }); - - // The opponent piece disappears if we take it - if (this.board[ex][ey] != V.EMPTY) { - mv.vanish.push( - new PiPo({ - x: ex, - y: ey, - c: this.getColor(ex, ey), - p: this.board[ex][ey].charAt(1) - }) - ); - } - - return mv; - } - canIplay(side, [x, y]) { return ( - (this.subTurn == 1 && this.turn == side && this.getColor(x, y) == side) || + (this.subTurn == 1 && this.turn == side && this.getColor(x, y) == side) + || (this.subTurn == 2 && x == this.sentryPos.x && y == this.sentryPos.y) ); } getPotentialMovesFrom([x, y]) { - // At subTurn == 2, jailers aren't effective (Jeff K) const piece = this.getPiece(x, y); const L = this.sentryPush.length; + // At subTurn == 2, jailers aren't effective (Jeff K) if (this.subTurn == 1) { const jsq = this.isImmobilized([x, y]); if (!!jsq) { @@ -355,7 +317,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules { } return true; }); - } else if (this.subTurn == 2) { + } + else if (this.subTurn == 2) { // Put back the sentinel on board: const color = this.turn; moves.forEach(m => { @@ -378,12 +341,18 @@ export const VariantRules = class EightpiecesRules extends ChessRules { // Pawns might be pushed on 1st rank and attempt to move again: if (!V.OnBoard(x + shiftX, y)) return []; - const finalPieces = - // A push cannot put a pawn on last rank (it goes backward) - x + shiftX == lastRank - ? Object.keys(V.LANCER_DIRS).concat( - [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.SENTRY, V.JAILER]) - : [V.PAWN]; + // A push cannot put a pawn on last rank (it goes backward) + let finalPieces = [V.PAWN]; + if (x + shiftX == lastRank) { + // Only allow direction facing inside board: + const allowedLancerDirs = + lastRank == 0 + ? ['e', 'f', 'g', 'h', 'm'] + : ['c', 'd', 'e', 'm', 'o']; + finalPieces = + allowedLancerDirs + .concat([V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.SENTRY, V.JAILER]); + } if (this.board[x + shiftX][y] == V.EMPTY) { // One square forward for (let piece of finalPieces) { @@ -445,6 +414,60 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return moves; } + doClick(square) { + if (isNaN(square[0])) return null; + const L = this.sentryPush.length; + const [x, y] = [square[0], square[1]]; + const color = this.turn; + if ( + this.subTurn == 2 || + this.board[x][y] == V.EMPTY || + this.getPiece(x, y) != V.LANCER || + this.getColor(x, y) != color || + !!this.sentryPush[L-1] + ) { + return null; + } + // Stuck lancer? + const orientation = this.board[x][y][1]; + const step = V.LANCER_DIRS[orientation]; + if (!V.OnBoard(x + step[0], y + step[1])) { + let choices = []; + Object.keys(V.LANCER_DIRS).forEach(k => { + const dir = V.LANCER_DIRS[k]; + if ( + (dir[0] != step[0] || dir[1] != step[1]) && + V.OnBoard(x + dir[0], y + dir[1]) + ) { + choices.push( + new Move({ + vanish: [ + new PiPo({ + x: x, + y: y, + c: color, + p: orientation + }) + ], + appear: [ + new PiPo({ + x: x, + y: y, + c: color, + p: k + }) + ], + start: { x: x, y : y }, + end: { x: -1, y: -1 } + }) + ); + } + }); + return choices; + } + return null; + } + // Obtain all lancer moves in "step" direction getPotentialLancerMoves_aux([x, y], step, tr) { let moves = []; @@ -474,6 +497,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules { // Except if just after a push: allow all movements from init square then const L = this.sentryPush.length; const color = this.getColor(x, y); + const dirCode = this.board[x][y][1]; + const curDir = V.LANCER_DIRS[dirCode]; if (!!this.sentryPush[L-1]) { // Maybe I was pushed const pl = this.sentryPush[L-1].length; @@ -484,7 +509,42 @@ export const VariantRules = class EightpiecesRules extends ChessRules { // I was pushed: allow all directions (for this move only), but // do not change direction after moving, *except* if I keep the // same orientation in which I was pushed. - const curDir = V.LANCER_DIRS[this.board[x][y].charAt(1)]; + // Also allow simple reorientation ("capturing king"): + if (!V.OnBoard(x + curDir[0], y + curDir[1])) { + const kp = this.kingPos[color]; + let reorientMoves = []; + Object.keys(V.LANCER_DIRS).forEach(k => { + const dir = V.LANCER_DIRS[k]; + if ( + (dir[0] != curDir[0] || dir[1] != curDir[1]) && + V.OnBoard(x + dir[0], y + dir[1]) + ) { + reorientMoves.push( + new Move({ + vanish: [ + new PiPo({ + x: x, + y: y, + c: color, + p: dirCode + }) + ], + appear: [ + new PiPo({ + x: x, + y: y, + c: color, + p: k + }) + ], + start: { x: x, y : y }, + end: { x: kp[0], y: kp[1] } + }) + ); + } + }); + Array.prototype.push.apply(moves, reorientMoves); + } Object.values(V.LANCER_DIRS).forEach(step => { const dirCode = Object.keys(V.LANCER_DIRS).find(k => { return ( @@ -503,36 +563,46 @@ export const VariantRules = class EightpiecesRules extends ChessRules { let chooseMoves = []; dirMoves.forEach(m => { Object.keys(V.LANCER_DIRS).forEach(k => { - let mk = JSON.parse(JSON.stringify(m)); - mk.appear[0].p = k; - moves.push(mk); + const newDir = V.LANCER_DIRS[k]; + // Prevent orientations toward outer board: + if (V.OnBoard(m.end.x + newDir[0], m.end.y + newDir[1])) { + let mk = JSON.parse(JSON.stringify(m)); + mk.appear[0].p = k; + chooseMoves.push(mk); + } }); }); Array.prototype.push.apply(moves, chooseMoves); - } else Array.prototype.push.apply(moves, dirMoves); + } + else Array.prototype.push.apply(moves, dirMoves); }); return moves; } } // I wasn't pushed: standard lancer move - const dirCode = this.board[x][y][1]; const monodirMoves = this.getPotentialLancerMoves_aux([x, y], V.LANCER_DIRS[dirCode]); // Add all possible orientations aftermove except if I'm being pushed if (this.subTurn == 1) { monodirMoves.forEach(m => { Object.keys(V.LANCER_DIRS).forEach(k => { - let mk = JSON.parse(JSON.stringify(m)); - mk.appear[0].p = k; - moves.push(mk); + const newDir = V.LANCER_DIRS[k]; + // Prevent orientations toward outer board: + if (V.OnBoard(m.end.x + newDir[0], m.end.y + newDir[1])) { + let mk = JSON.parse(JSON.stringify(m)); + mk.appear[0].p = k; + moves.push(mk); + } }); }); return moves; - } else { - // I'm pushed: add potential nudges + } + else { + // I'm pushed: add potential nudges, except for current orientation let potentialNudges = []; for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { if ( + (step[0] != curDir[0] || step[1] != curDir[1]) && V.OnBoard(x + step[0], y + step[1]) && this.board[x + step[0]][y + step[1]] == V.EMPTY ) { @@ -597,10 +667,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules { getPotentialKingMoves(sq) { const moves = this.getSlideNJumpMoves( - sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), - "oneStep" - ); + sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1); return ( this.subTurn == 1 ? moves.concat(this.getCastleMoves(sq)) @@ -608,81 +675,6 @@ export const VariantRules = class EightpiecesRules extends ChessRules { ); } - // Adapted: castle with jailer possible - getCastleMoves([x, y]) { - const c = this.getColor(x, y); - const firstRank = (c == "w" ? V.size.x - 1 : 0); - if (x != firstRank || y != this.INIT_COL_KING[c]) - return []; - - const oppCol = V.GetOppCol(c); - let moves = []; - let i = 0; - // King, then rook or jailer: - const finalSquares = [ - [2, 3], - [V.size.y - 2, V.size.y - 3] - ]; - castlingCheck: for ( - let castleSide = 0; - castleSide < 2; - castleSide++ - ) { - if (this.castleFlags[c][castleSide] >= 8) continue; - // Rook (or jailer) and king are on initial position - 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 && - (this.getColor(x, i) != c || - ![V.KING, V.ROOK, V.JAILER].includes(this.getPiece(x, i)))) - ) { - continue castlingCheck; - } - i += step; - } while (i != finalSquares[castleSide][0]); - step = castleSide == 0 ? -1 : 1; - const rookOrJailerPos = this.castleFlags[c][castleSide]; - for (i = y + step; i != rookOrJailerPos; i += step) - if (this.board[x][i] != V.EMPTY) continue castlingCheck; - - // Nothing on final squares, except maybe king and castling rook or jailer? - 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] != rookOrJailerPos - ) { - continue castlingCheck; - } - } - - // If this code is reached, castle is valid - const castlingPiece = this.getPiece(firstRank, rookOrJailerPos); - 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: castlingPiece, c: c }) - ], - vanish: [ - new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookOrJailerPos, p: castlingPiece, c: c }) - ], - end: - Math.abs(y - rookOrJailerPos) <= 2 - ? { x: x, y: rookOrJailerPos } - : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } - }) - ); - } - - return moves; - } - atLeastOneMove() { // If in second-half of a move, we already know that a move is possible if (this.subTurn == 2) return true; @@ -729,123 +721,16 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return this.filterValid(this.getPotentialMovesFrom(sentrySq)); } - prePlay(move) { - if (move.appear.length == 0 && move.vanish.length == 1) - // The sentry is about to push a piece: subTurn goes from 1 to 2 - this.sentryPos = { x: move.end.x, y: move.end.y }; - if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) { - // A piece is pushed: forbid array of squares between start and end - // of move, included (except if it's a pawn) - let squares = []; - if ([V.KNIGHT,V.KING].includes(move.vanish[0].p)) - // short-range pieces: just forbid initial square - squares.push({ x: move.start.x, y: move.start.y }); - else { - const deltaX = move.end.x - move.start.x; - const deltaY = move.end.y - move.start.y; - const step = [ - deltaX / Math.abs(deltaX) || 0, - deltaY / Math.abs(deltaY) || 0 - ]; - for ( - let sq = {x: move.start.x, y: move.start.y}; - sq.x != move.end.x || sq.y != move.end.y; - sq.x += step[0], sq.y += step[1] - ) { - squares.push({ x: sq.x, y: sq.y }); - } - } - // Add end square as well, to know if I was pushed (useful for lancers) - squares.push({ x: move.end.x, y: move.end.y }); - this.sentryPush.push(squares); - } else this.sentryPush.push(null); - } - - play(move) { -// if (!this.states) this.states = []; -// const stateFen = this.getFen(); -// this.states.push(stateFen); - - this.prePlay(move); - move.flags = JSON.stringify(this.aggregateFlags()); - this.epSquares.push(this.getEpSquare(move)); - V.PlayOnBoard(this.board, move); - // Is it a sentry push? (useful for undo) - move.sentryPush = (this.subTurn == 2); - if (this.subTurn == 1) this.movesCount++; - if (move.appear.length == 0 && move.vanish.length == 1) this.subTurn = 2; - else { - // Turn changes only if not a sentry "pre-push" - this.turn = V.GetOppCol(this.turn); - this.subTurn = 1; - } - this.postPlay(move); - } - - postPlay(move) { - if (move.vanish.length == 0 || this.subTurn == 2) - // Special pass move of the king, or sentry pre-push: nothing to update - return; - const c = move.vanish[0].c; - const piece = move.vanish[0].p; - const firstRank = c == "w" ? V.size.x - 1 : 0; - - if (piece == V.KING) { - this.kingPos[c][0] = move.appear[0].x; - this.kingPos[c][1] = move.appear[0].y; - this.castleFlags[c] = [V.size.y, V.size.y]; - return; - } - // Update castling flags if rooks are moved - const oppCol = V.GetOppCol(c); - const oppFirstRank = V.size.x - 1 - firstRank; - if ( - move.start.x == firstRank && //our rook moves? - this.castleFlags[c].includes(move.start.y) - ) { - const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); - this.castleFlags[c][flagIdx] = V.size.y; - } else if ( - move.end.x == oppFirstRank && //we took opponent rook? - this.castleFlags[oppCol].includes(move.end.y) - ) { - const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); - this.castleFlags[oppCol][flagIdx] = V.size.y; - } - } - - undo(move) { - this.epSquares.pop(); - this.disaggregateFlags(JSON.parse(move.flags)); - V.UndoOnBoard(this.board, move); - // Decrement movesCount except if the move is a sentry push - if (!move.sentryPush) this.movesCount--; - if (this.subTurn == 2) this.subTurn = 1; - else { - this.turn = V.GetOppCol(this.turn); - if (move.sentryPush) this.subTurn = 2; - } - this.postUndo(move); - -// const stateFen = this.getFen(); -// if (stateFen != this.states[this.states.length-1]) debugger; -// this.states.pop(); - } - - postUndo(move) { - super.postUndo(move); - this.sentryPush.pop(); - } - - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - super.isAttacked(sq, colors) || - this.isAttackedByLancer(sq, colors) || - this.isAttackedBySentry(sq, colors) + super.isAttacked(sq, color) || + this.isAttackedByLancer(sq, color) || + this.isAttackedBySentry(sq, color) + // The jailer doesn't capture. ); } - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { for (let step of steps) { let rx = x + step[0], ry = y + step[1]; @@ -855,8 +740,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules { } if ( V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) && + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color && !this.isImmobilized([rx, ry]) ) { return true; @@ -865,27 +750,25 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return false; } - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - const pawnShift = c == "w" ? 1 : -1; - if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { - for (let i of [-1, 1]) { - if ( - y + i >= 0 && - y + i < V.size.y && - this.getPiece(x + pawnShift, y + i) == V.PAWN && - this.getColor(x + pawnShift, y + i) == c && - !this.isImmobilized([x + pawnShift, y + i]) - ) { - return true; - } + isAttackedByPawn([x, y], color) { + const pawnShift = (color == "w" ? 1 : -1); + if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < V.size.y && + this.getPiece(x + pawnShift, y + i) == V.PAWN && + this.getColor(x + pawnShift, y + i) == color && + !this.isImmobilized([x + pawnShift, y + i]) + ) { + return true; } } } return false; } - isAttackedByLancer([x, y], colors) { + isAttackedByLancer([x, y], color) { for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { // If in this direction there are only enemy pieces and empty squares, // and we meet a lancer: can he reach us? @@ -896,7 +779,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules { V.OnBoard(coord.x, coord.y) && ( this.board[coord.x][coord.y] == V.EMPTY || - colors.includes(this.getColor(coord.x, coord.y)) + this.getColor(coord.x, coord.y) == color ) ) { if ( @@ -908,9 +791,21 @@ export const VariantRules = class EightpiecesRules extends ChessRules { coord.x += step[0]; coord.y += step[1]; } + const L = this.sentryPush.length; + const pl = (!!this.sentryPush[L-1] ? this.sentryPush[L-1].length : 0); for (let xy of lancerPos) { const dir = V.LANCER_DIRS[this.board[xy.x][xy.y].charAt(1)]; - if (dir[0] == -step[0] && dir[1] == -step[1]) return true; + if ( + (dir[0] == -step[0] && dir[1] == -step[1]) || + // If the lancer was just pushed, this is an attack too: + ( + !!this.sentryPush[L-1] && + this.sentryPush[L-1][pl-1].x == xy.x && + this.sentryPush[L-1][pl-1].y == xy.y + ) + ) { + return true; + } } } return false; @@ -919,10 +814,11 @@ export const VariantRules = class EightpiecesRules extends ChessRules { // Helper to check sentries attacks: selfAttack([x1, y1], [x2, y2]) { const color = this.getColor(x1, y1); + const oppCol = V.GetOppCol(color); const sliderAttack = (allowedSteps, lancer) => { const deltaX = x2 - x1, - absDeltaX = Math.abs(deltaX); - const deltaY = y2 - y1, + deltaY = y2 - y1; + const absDeltaX = Math.abs(deltaX), absDeltaY = Math.abs(deltaY); const step = [ deltaX / absDeltaX || 0, deltaY / absDeltaY || 0 ]; if ( @@ -933,13 +829,19 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return false; } let sq = [ x1 + step[0], y1 + step[1] ]; - while (sq[0] != x2 && sq[1] != y2) { - if ( - // NOTE: no need to check OnBoard in this special case - (!lancer && this.board[sq[0]][sq[1]] != V.EMPTY) || - (!!lancer && this.getColor(sq[0], sq[1]) != color) - ) { - return false; + while (sq[0] != x2 || sq[1] != y2) { + // NOTE: no need to check OnBoard in this special case + if (this.board[sq[0]][sq[1]] != V.EMPTY) { + const p = this.getPiece(sq[0], sq[1]); + const pc = this.getColor(sq[0], sq[1]); + if ( + // Enemy sentry on the way will be gone: + (p != V.SENTRY || pc != oppCol) && + // Lancer temporarily "changed color": + (!lancer || pc == color) + ) { + return false; + } } sq[0] += step[0]; sq[1] += step[1]; @@ -968,8 +870,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules { case V.QUEEN: return sliderAttack(V.steps[V.ROOK].concat(V.steps[V.BISHOP])); case V.LANCER: { - // Special case: as long as no enemy units stands in-between, it attacks - // (if it points toward the king). + // Special case: as long as no enemy units stands in-between, + // it attacks (if it points toward the king). const allowedStep = V.LANCER_DIRS[this.board[x1][y1].charAt(1)]; return sliderAttack([allowedStep], "lancer"); } @@ -978,17 +880,17 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return false; } - isAttackedBySentry([x, y], colors) { + isAttackedBySentry([x, y], color) { // Attacked by sentry means it can self-take our king. // Just check diagonals of enemy sentry(ies), and if it reaches // one of our pieces: can I self-take? - const color = V.GetOppCol(colors[0]); + const myColor = V.GetOppCol(color); let candidates = []; for (let i=0; i 0 && + move.vanish[0].x == move.appear[0].x && + move.vanish[0].y == move.appear[0].y + ) { + // Lancer in-place reorientation: + notation = "L" + V.CoordsToSquare(move.start) + ":R"; + } + else notation = super.getNotation(move); + if (Object.keys(V.LANCER_DIRNAMES).includes(move.vanish[0].p)) + // Lancer: add direction info + notation += "=" + V.LANCER_DIRNAMES[move.appear[0].p]; + else if ( + move.vanish[0].p == V.PAWN && + Object.keys(V.LANCER_DIRNAMES).includes(move.appear[0].p) + ) { + // Fix promotions in lancer: + notation = notation.slice(0, -1) + + "L:" + V.LANCER_DIRNAMES[move.appear[0].p]; + } + return notation; } + };