X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FPacosako.js;h=0462d2dbcfda4b4c2d7e1e1da3f9f7975235aab3;hb=4313762da3237b04f204e121a20cab3ba7bb5dd2;hp=8cec688f73fa4d8af905266d830590938115a70a;hpb=0b4bca844aa20cb88531cd25bc0140a569f03947;p=vchess.git diff --git a/client/src/variants/Pacosako.js b/client/src/variants/Pacosako.js index 8cec688f..0462d2db 100644 --- a/client/src/variants/Pacosako.js +++ b/client/src/variants/Pacosako.js @@ -30,21 +30,25 @@ export class PacosakoRules extends ChessRules { x: ['b', 'k'], y: ['q', 'q'], z: ['q', 'k'], - '_': ['k', 'k'] + '@': ['k', 'k'] }; } + static fen2board(f) { + // Arobase is character 64 + return f.charCodeAt() <= 90 ? "w" + f.toLowerCase() : "b" + f; + } + static IsGoodPosition(position) { if (position.length == 0) return false; const rows = position.split("/"); if (rows.length != V.size.x) return false; - let kingSymb = ['k', 'g', 'm', 'u', 'x', '_']; + let kingSymb = ['k', 'g', 'm', 'u', 'x', 'z', '@']; let kings = { 'k': 0, 'K': 0 }; for (let row of rows) { let sumElts = 0; for (let i = 0; i < row.length; i++) { - const lowR = row[i].toLowerCase - if (!!(row[i].toLowerCase().match(/[a-z_]/))) { + if (!!(row[i].toLowerCase().match(/[a-z@]/))) { sumElts++; if (kingSymb.includes(row[i])) kings['k']++; // Not "else if", if two kings dancing together @@ -100,12 +104,12 @@ export class PacosakoRules extends ChessRules { this.kingPos = { w: [-1, -1], b: [-1, -1] }; const fenRows = V.ParseFen(fen).position.split("/"); const startRow = { 'w': V.size.x - 1, 'b': 0 }; - const kingSymb = ['k', 'g', 'm', 'u', 'x', '_']; + const kingSymb = ['k', 'g', 'm', 'u', 'x', 'z', '@']; for (let i = 0; i < fenRows.length; i++) { let k = 0; for (let j = 0; j < fenRows[i].length; j++) { const c = fenRows[i].charAt(j); - if (!!(c.toLowerCase().match(/[a-z_]/))) { + if (!!(c.toLowerCase().match(/[a-z@]/))) { if (kingSymb.includes(c)) this.kingPos["b"] = [i, k]; // Not "else if", in case of two kings dancing together @@ -135,6 +139,8 @@ export class PacosakoRules extends ChessRules { end: ChessRules.SquareToCoords(umove.substr(2)) }); } + // Local stack of positions to avoid redundant moves: + this.repetitions = []; } static IsGoodFen(fen) { @@ -176,7 +182,8 @@ export class PacosakoRules extends ChessRules { getUmove(move) { if ( move.vanish.length == 1 && - !(ChessRules.PIECES.includes(move.appear[0].p)) + !(ChessRules.PIECES.includes(move.appear[0].p)) && + move.appear[0].p == move.vanish[0].p //not a promotion ) { // An union moving return { start: move.start, end: move.end }; @@ -192,9 +199,9 @@ export class PacosakoRules extends ChessRules { ); } - static GenRandInitFen(randomness) { + static GenRandInitFen(options) { // Add 16 pawns flags + empty umove: - return ChessRules.GenRandInitFen(randomness) + return ChessRules.GenRandInitFen(options) .slice(0, -2) + "1111111111111111 - -"; } @@ -349,7 +356,8 @@ export class PacosakoRules extends ChessRules { p: cp.p }) ]; - mv.released = up[c]; + // In move.end, to be sent to the server + mv.end.released = up[c]; return mv; } @@ -364,7 +372,7 @@ export class PacosakoRules extends ChessRules { } let baseMoves = []; const c = this.turn; - switch (piece || this.getPiece(x, y)) { + switch (piece) { case V.PAWN: { const firstRank = (c == 'w' ? 7 : 0); baseMoves = this.getPotentialPawnMoves([x, y]).filter(m => { @@ -438,7 +446,6 @@ export class PacosakoRules extends ChessRules { return; // Fix en-passant capture: union type, maybe released piece too const cs = [m.end.x + (c == 'w' ? 1 : -1), m.end.y]; - const color = this.board[cs[0]][cs[1]].charAt(0); const code = this.board[cs[0]][cs[1]].charAt(1); if (code == V.PAWN) { // Simple en-passant capture (usual: just form union) @@ -446,9 +453,10 @@ export class PacosakoRules extends ChessRules { m.appear[0].p = 'a'; } else { - // An union pawn + something juste moved two squares + // An union pawn + something just moved two squares + const color = this.board[cs[0]][cs[1]].charAt(0); const up = this.getUnionPieces(color, code); - m.released = up[c]; + m.end.released = up[c]; let args = [V.PAWN, up[oppCol]]; if (c == 'b') args = args.reverse(); const cp = this.getUnionCode(args[0], args[1]); @@ -472,11 +480,10 @@ export class PacosakoRules extends ChessRules { const move = moveOrSquare; const s = move.start, e = move.end; - const oppCol = V.GetOppCol(this.turn); if ( s.y == e.y && Math.abs(s.x - e.x) == 2 && - this.getPiece(s.x, s.y, oppCol) == V.PAWN + this.getPiece(s.x, s.y, this.turn) == V.PAWN ) { return { x: (s.x + e.x) / 2, @@ -492,7 +499,7 @@ export class PacosakoRules extends ChessRules { !!m1 && !(ChessRules.PIECES.includes(m2.appear[0].p)) && m2.vanish.length == 1 && - !m2.released && + !m2.end.released && m1.start.x == m2.end.x && m1.end.x == m2.start.x && m1.start.y == m2.end.y && @@ -595,10 +602,23 @@ export class PacosakoRules extends ChessRules { // "positions" = array of FENs to detect infinite loops. Example: // r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/P1eDP3/3B1PP1/R3K1NR, // Bxd2 Bxc3 Bxb4 Bxc3 Bxb4 etc. - const newPos = { fen: super.getBaseFen(), piece: released }; - if (positions.some(p => p.piece == newPos.piece && p.fen == newPos.fen)) + const newPos = { + fen: super.getBaseFen(), + piece: released, + from: fromSquare + }; + if ( + positions.some(p => { + return ( + p.piece == newPos.piece && + p.fen == newPos.fen && + p.from == newPos.from + ); + }) + ) { // Start of an infinite loop: exit return false; + } positions.push(newPos); const rank = (color == 'w' ? 0 : 7); const moves = this.getPotentialMovesFrom(fromSquare); @@ -606,11 +626,11 @@ export class PacosakoRules extends ChessRules { // Found an attack! return true; for (let m of moves) { - if (!!m.released) { + if (!!m.end.released) { // Turn won't change since !!m.released this.play(m); const res = this.isAttacked_aux( - files, color, positions, [m.end.x, m.end.y], m.released); + files, color, positions, [m.end.x, m.end.y], m.end.released); this.undo(m); if (res) return true; } @@ -644,12 +664,12 @@ export class PacosakoRules extends ChessRules { break outerLoop; } for (let m of moves) { - if (!!m.released) { + if (!!m.end.released) { // Turn won't change since !!m.released this.play(m); let positions = []; res = this.isAttacked_aux( - files, color, positions, [m.end.x, m.end.y], m.released); + files, color, positions, [m.end.x, m.end.y], m.end.released); this.undo(m); if (res) break outerLoop; } @@ -661,37 +681,65 @@ export class PacosakoRules extends ChessRules { return res; } + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { + rx += step[0]; + ry += step[1]; + } + if ( + V.OnBoard(rx, ry) && + this.board[rx][ry] != V.EMPTY && + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color && + this.canTake([rx, ry], [x, y]) //TODO: necessary line? + //If not, generic method is OK + ) { + return true; + } + } + return false; + } + // Do not consider checks, except to forbid castling getCheckSquares() { return []; } + filterValid(moves) { if (moves.length == 0) return []; const L = this.umoves.length; //at least 1: init from FEN - return moves.filter(m => !this.oppositeMoves(this.umoves[L - 1], m)); - } - - play(move) { - move.flags = JSON.stringify(this.aggregateFlags()); - this.epSquares.push(this.getEpSquare(move)); - // Check if the move is the last of the turn: all cases except releases - if (!move.released) { - // No more union releases available - this.turn = V.GetOppCol(this.turn); - this.movesCount++; - this.lastMoveEnd.push(null); - } - else this.lastMoveEnd.push(Object.assign({ p: move.released }, move.end)); - V.PlayOnBoard(this.board, move); - this.umoves.push(this.getUmove(move)); - this.postPlay(move); + return moves.filter(m => { + if (this.oppositeMoves(this.umoves[L - 1], m)) return false; + if (!m.end.released) return true; + // Check for repetitions: + V.PlayOnBoard(this.board, m); + const newState = { + piece: m.end.released, + square: { x: m.end.x, y: m.end.y }, + position: this.getBaseFen() + }; + const repet = + this.repetitions.some(r => { + return ( + r.piece == newState.piece && + ( + r.square.x == newState.square.x && + r.square.y == newState.square.y + ) && + r.position == newState.position + ); + }); + V.UndoOnBoard(this.board, m); + return !repet; + }); } updateCastleFlags(move, piece) { - const c = V.GetOppCol(this.turn); + const c = this.turn; const firstRank = (c == "w" ? 7 : 0); - const oppCol = this.turn; - const oppFirstRank = 7 - firstRank; if (piece == V.KING && move.appear.length > 0) this.castleFlags[c] = [V.size.y, V.size.y]; else if ( @@ -701,33 +749,70 @@ export class PacosakoRules extends ChessRules { const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); this.castleFlags[c][flagIdx] = V.size.y; } - // No more checking: a rook in union can take part in castling. + else if ( + move.end.x == firstRank && + this.castleFlags[c].includes(move.end.y) + ) { + // Move to our rook: necessary normal piece, to union, releasing + // (or the rook was moved before!) + const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } } - postPlay(move) { - if (move.vanish.length == 0) - // A released piece just moved. Cannot be the king. - return; - const c = move.vanish[0].c; - const piece = move.vanish[0].p; + prePlay(move) { + // Easier before move is played in this case (flags are saved) + const c = this.turn; + const L = this.lastMoveEnd.length; + const lm = this.lastMoveEnd[L-1]; + // NOTE: lm.p != V.KING, always. + const piece = + !!lm + ? lm.p + : this.getPiece(move.vanish[0].x, move.vanish[0].y); if (piece == V.KING) this.kingPos[c] = [move.appear[0].x, move.appear[0].y]; this.updateCastleFlags(move, piece); + const pawnFirstRank = (c == 'w' ? 6 : 1); if ( - [1, 6].includes(move.start.x) && - move.vanish.length >= 1 && - move.appear.length == 1 + move.start.x == pawnFirstRank && + piece == V.PAWN && + Math.abs(move.end.x - move.start.x) == 2 ) { - // Does this move turn off a 2-squares pawn flag? - if ( - move.vanish[0].p == V.PAWN || - ( - !(ChessRules.PIECES.includes(move.vanish[0].p)) && - this.getUnionPieces(move.vanish[0].c, move.vanish[0].p)[c] == V.PAWN - ) - ) { - this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; - } + // This move turns off a 2-squares pawn flag + this.pawnFlags[c][move.start.y] = false; + } + } + + play(move) { + move.flags = JSON.stringify(this.aggregateFlags()); + this.prePlay(move); + this.epSquares.push(this.getEpSquare(move)); + // Check if the move is the last of the turn: all cases except releases + if (!move.end.released) { + // No more union releases available + this.turn = V.GetOppCol(this.turn); + this.movesCount++; + this.lastMoveEnd.push(null); + } + else { + this.lastMoveEnd.push({ + p: move.end.released, + x: move.end.x, + y: move.end.y + }); + } + V.PlayOnBoard(this.board, move); + this.umoves.push(this.getUmove(move)); + if (!move.end.released) this.repetitions = []; + else { + this.repetitions.push( + { + piece: move.end.released, + square: { x: move.end.x, y: move.end.y }, + position: this.getBaseFen() + } + ); } } @@ -736,11 +821,12 @@ export class PacosakoRules extends ChessRules { this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); this.lastMoveEnd.pop(); - if (!move.released) { + if (!move.end.released) { this.turn = V.GetOppCol(this.turn); this.movesCount--; } this.umoves.pop(); + if (!!move.end.released) this.repetitions.pop(); this.postUndo(move); } @@ -776,14 +862,15 @@ export class PacosakoRules extends ChessRules { mv = moves[randInt(moves.length)]; mvArray.push(mv); this.play(mv); - if (!!mv.released) + if (!!mv.end.released) // A piece was just released from an union moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]); else break; } for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]); - if (!mv.released) return (mvArray.length > 1 ? mvArray : mvArray[0]); + if (!mv.end.released) return (mvArray.length > 1 ? mvArray : mvArray[0]); } + return null; //never reached } // NOTE: evalPosition() is wrong, but unused since bot plays at random @@ -821,8 +908,13 @@ export class PacosakoRules extends ChessRules { // Add potential promotion indications: const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]); if (move.end.x == firstLastRank[1] && piece == V.PAWN) { - const up = this.getUnionPieces(move.appear[0].c, move.appear[0].p); - notation += "=" + up[c].toUpperCase(); + notation += "="; + if (ChessRules.PIECES.includes(move.appear[0].p)) + notation += move.appear[0].p.toUpperCase(); + else { + const up = this.getUnionPieces(move.appear[0].c, move.appear[0].p); + notation += up[c].toUpperCase(); + } } else if ( move.end.x == firstLastRank[0] &&