X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FEightpieces.js;h=6efb182f3c6302dfb833e21280c715ece3f17e9c;hb=1b56b73614509d1dca8c4353f18fb78349940cf8;hp=bea261a38ee5829abb8f21b9b678937420715c23;hpb=1c58eb76b86d89b9aad29920240b12451f77ab95;p=vchess.git diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index bea261a3..6efb182f 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -83,8 +83,15 @@ export 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 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 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,18 +144,33 @@ export 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); - }) - ]; + // 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 + }); + 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]; } } static GenRandInitFen(randomness) { if (randomness == 0) // Deterministic: - return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 ahah - -"; + return "jfsqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JDSQKBNR w 0 ahah - -"; let pieces = { w: new Array(8), b: new Array(8) }; let flags = ""; @@ -175,7 +199,8 @@ export class EightpiecesRules extends ChessRules { 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) + // NOTE: test % 2 == 1 because there are 7 slashes. + if ((pieces['w'].indexOf('s') - sentryPos) % 2 == 1) [bishopPos, sentryPos] = [sentryPos, bishopPos]; } positions.splice(Math.max(randIndex, randIndex_tmp), 1); @@ -195,7 +220,7 @@ export class EightpiecesRules extends ChessRules { positions.splice(randIndex, 1); // Rook, jailer and king positions are now almost fixed, - // only the ordering rook-> jailer or jailer->rook must be decided. + // only the ordering rook->jailer or jailer->rook must be decided. let rookPos = positions[0]; let jailerPos = positions[2]; const kingPos = positions[1]; @@ -285,15 +310,16 @@ export class EightpiecesRules extends ChessRules { 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) { @@ -447,6 +473,60 @@ export 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 = []; @@ -476,6 +556,7 @@ export 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]; if (!!this.sentryPush[L-1]) { // Maybe I was pushed const pl = this.sentryPush[L-1].length; @@ -486,7 +567,43 @@ export 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)]; + const curDir = V.LANCER_DIRS[dirCode]; + // 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 ( @@ -517,7 +634,6 @@ export class EightpiecesRules extends ChessRules { } } // 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 @@ -610,81 +726,6 @@ export 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; @@ -731,119 +772,12 @@ export 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, color) { return ( super.isAttacked(sq, color) || this.isAttackedByLancer(sq, color) || this.isAttackedBySentry(sq, color) + // The jailer doesn't capture. ); } @@ -919,6 +853,7 @@ export 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); @@ -937,7 +872,7 @@ export class EightpiecesRules extends ChessRules { 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) + (!!lancer && this.getColor(sq[0], sq[1]) == oppCol) ) { return false; } @@ -968,8 +903,8 @@ export 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"); } @@ -1017,6 +952,106 @@ export class EightpiecesRules extends ChessRules { // Jailer doesn't capture or give check + 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) { + 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); + } + + postUndo(move) { + super.postUndo(move); + this.sentryPush.pop(); + } + static get VALUES() { return Object.assign( { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations @@ -1107,10 +1142,27 @@ export class EightpiecesRules extends ChessRules { end: move.end }; notation = super.getNotation(simpleMove); - } else notation = super.getNotation(move); + } + else if ( + move.appear.length > 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; } };