From b8950be593d0663202c886f4252513bf1a499ee1 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 25 Jul 2023 11:39:41 +0200 Subject: [PATCH] Start working on Convert (mismatch name, Chaining?) --- base_rules.js | 2 +- variants/Convert/class.js | 278 +++++++++++++++++++++++++++++++++++- variants/Convert/rules.html | 7 + variants/Convert/style.css | 1 + 4 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 variants/Convert/rules.html create mode 100644 variants/Convert/style.css diff --git a/base_rules.js b/base_rules.js index 20ffbee..aea3f57 100644 --- a/base_rules.js +++ b/base_rules.js @@ -1188,7 +1188,7 @@ export default class ChessRules { } pieces(color, x, y) { - const pawnShift = this.getPawnShift(color); + const pawnShift = this.getPawnShift(color || 'w'); return { 'p': { "class": "pawn", diff --git a/variants/Convert/class.js b/variants/Convert/class.js index 1a006e2..566feb5 100644 --- a/variants/Convert/class.js +++ b/variants/Convert/class.js @@ -4,7 +4,7 @@ import Move from "/utils/Move.js"; export default class ConvertRules extends ChessRules { - // TODO + // TODO: options ? (balance progressive ok it seems?) static get Options() { return { select: C.Options.select, @@ -23,7 +23,7 @@ export default class ConvertRules extends ChessRules { setOtherVariables(fenParsed, pieceArray) { super.setOtherVariables(fenParsed, pieceArray); // Stack of "last move" only for intermediate chaining - this.lastMoveEnd = [null]; + this.lastMoveEnd = []; } genRandInitBaseFen() { @@ -86,7 +86,195 @@ export default class ConvertRules extends ChessRules { return mv; } -// TODO from here + getPiece(x, y) { + const L = this.lastMoveEnd.length; + if (L >= 1 && this.lastMoveEnd[L-1].x == x && this.lastMoveEnd[L-1].y == y) + return this.lastMoveEnd[L-1].p; + return super.getPiece(x, y); + } + + getPotentialMovesFrom([x, y], color) { + const L = this.lastMoveEnd.length; + if ( + L >= 1 && + (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y) + ) { + // A capture was played: wrong square + return []; + } + return super.getPotentialMovesFrom([x, y], color); + } + + underAttack_aux([x, y], color, explored) { + if (explored.some(sq => sq[0] == x && sq[1] == y)) + // Start of an infinite loop: exit + return false; + explored.push([x, y]); + if (super.underAttack([x, y], [color])) + return true; + // Maybe indirect "chaining" attack: + const myColor = this.turn; + let res = false; + let toCheck = []; //check all but king (no need) + // Pawns: + const shiftToPawn = (myColor == 'w' ? -1 : 1); + for (let yShift of [-1, 1]) { + const [i, j] = [x + shiftToPawn, y + yShift]; + if ( + this.onBoard(i, j) && + this.board[i][j] != "" && + // NOTE: no need to check color (no enemy pawn can take directly) + this.getPiece(i, j) == 'p' + ) { + toCheck.push([i, j]); + } + } + // Knights: + this.pieces()['n'].both[0].steps.forEach(s => { + const [i, j] = [x + s[0], y + s[1]]; + if ( + this.onBoard(i, j) && + this.board[i][j] != "" && + this.getPiece(i, j) == 'n' + ) { + toCheck.push([i, j]); + } + }); + // Sliders: + this.pieces()['q'].both[0].steps.forEach(s => { + let [i, j] = [x + s[0], y + s[1]]; + while (this.onBoard(i, j) && this.board[i][j] == "") { + i += s[0]; + j += s[1]; + } + if (!this.onBoard(i, j)) + return; + const piece = this.getPiece(i, j); + if ( + piece == 'q' || + (piece == 'r' && (s[0] == 0 || s[1] == 0)) || + (piece == 'b' && (s[0] != 0 && s[1] != 0)) + ) { + toCheck.push([i, j]); + } + }); + for (let ij of toCheck) { + if (this.underAttack_aux(ij, color, explored)) + return true; + } + return false; + } + + underAttack([x, y], color) { + let explored = []; + return this.underAttack_aux([x, y], color, explored); + } + + filterValid(moves) { + // No "checks" (except to forbid castle) + return moves; + } + + isLastMove(move) { + return ( + super.isLastMove(move) || + move.vanish.length <= 1 || + move.vanish[1].c != move.vanish[0].c || + move.appear.length == 2 //castle! + ); + } + + postPlay(move) { + super.postPlay(move); + if (!this.isLastMove(move)) { + this.lastMoveEnd.push({ + x: move.end.x, + y: move.end.y, + p: move.vanish[1].p //TODO: check this + }); + } + } + +}; + +// TODO: wrong rules! mismatch Convert (taking opponent pieces) and chaining (tend to be this) taking own units (with normal initial position). +// Initial Convert: + +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { randInt } from "@/utils/alea"; + +export class ConvertRules extends ChessRules { + + static get HasEnpassant() { + return false; + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + // Stack of "last move" only for intermediate chaining + this.lastMoveEnd = [null]; + } + + static GenRandInitFen(options) { + const baseFen = ChessRules.GenRandInitFen(options); + return ( + baseFen.substr(0, 8) + + "/8/pppppppp/8/8/PPPPPPPP/8/" + + baseFen.substr(35, 17) + ); + } + + getBasicMove([sx, sy], [ex, ey], tr) { + const L = this.lastMoveEnd.length; + const lm = this.lastMoveEnd[L-1]; + const piece = (!!lm ? lm.p : null); + const c = this.turn; + if (this.board[ex][ey] == V.EMPTY) { + if (!!piece && !tr) tr = { c: c, p: piece } + let mv = super.getBasicMove([sx, sy], [ex, ey], tr); + if (!!piece) mv.vanish.pop(); + return mv; + } + // Capture: initial, or inside a chain + const initPiece = (piece || this.getPiece(sx, sy)); + const oppCol = V.GetOppCol(c); + const oppPiece = this.getPiece(ex, ey); + let mv = new Move({ + start: { x: sx, y: sy }, + end: { x: ex, y: ey }, + appear: [ + new PiPo({ + x: ex, + y: ey, + c: c, + p: (!!tr ? tr.p : initPiece) + }) + ], + vanish: [ + new PiPo({ + x: ex, + y: ey, + c: oppCol, + p: oppPiece + }) + ] + }); + if (!piece) { + // Initial capture + mv.vanish.unshift( + new PiPo({ + x: sx, + y: sy, + c: c, + p: initPiece + }) + ); + } + // TODO: This "converted" indication isn't needed in fact, + // because it can be deduced from the move itself. + mv.end.converted = oppPiece; + return mv; + } getPotentialMovesFrom([x, y], asA) { const L = this.lastMoveEnd.length; @@ -189,6 +377,10 @@ export default class ConvertRules extends ChessRules { return moves; } + getCheckSquares() { + return []; + } + prePlay(move) { const c = this.turn; // Extra conditions to avoid tracking converted kings: @@ -221,4 +413,84 @@ export default class ConvertRules extends ChessRules { super.updateCastleFlags(move, move.appear[0].p, c); } + undo(move) { + this.disaggregateFlags(JSON.parse(move.flags)); + this.lastMoveEnd.pop(); + V.UndoOnBoard(this.board, move); + if (!move.end.converted) { + this.turn = V.GetOppCol(this.turn); + this.movesCount--; + } + this.postUndo(move); + } + + postUndo(move) { + const c = this.getColor(move.start.x, move.start.y); + if ( + move.appear[0].p == V.KING && + move.vanish.length >= 1 && + move.vanish[0].p == V.KING + ) { + this.kingPos[c] = [move.start.x, move.start.y]; + } + } + + getCurrentScore() { + const color = this.turn; + const kp = this.kingPos[color]; + if (this.getColor(kp[0], kp[1]) != color) + return (color == "w" ? "0-1" : "1-0"); + if (!super.atLeastOneMove()) return "1/2"; + return "*"; + } + + getComputerMove() { + let initMoves = this.getAllValidMoves(); + if (initMoves.length == 0) return null; + // Loop until valid move is found (no blocked pawn conversion...) + while (true) { + let moves = JSON.parse(JSON.stringify(initMoves)); + let mvArray = []; + let mv = null; + // Just play random moves (for now at least. TODO?) + while (moves.length > 0) { + mv = moves[randInt(moves.length)]; + mvArray.push(mv); + this.play(mv); + if (!!mv.end.converted) + // A piece was just converted + 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.end.converted) return (mvArray.length > 1 ? mvArray : mvArray[0]); + } + return null; //never reached + } + + getNotation(move) { + if (move.appear.length == 2 && move.appear[0].p == V.KING) + return (move.end.y < move.start.y ? "0-0-0" : "0-0"); + const c = this.turn; + const L = this.lastMoveEnd.length; + const lm = this.lastMoveEnd[L-1]; + const piece = (!lm ? move.appear[0].p : lm.p); + // Basic move notation: + let notation = piece.toUpperCase(); + if ( + this.board[move.end.x][move.end.y] != V.EMPTY || + (piece == V.PAWN && move.start.y != move.end.y) + ) { + notation += "x"; + } + const finalSquare = V.CoordsToSquare(move.end); + notation += finalSquare; + + // Add potential promotion indications: + const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]); + if (move.end.x == firstLastRank[1] && piece == V.PAWN) + notation += "=" + move.appear[0].p.toUpperCase(); + return notation; + } + }; diff --git a/variants/Convert/rules.html b/variants/Convert/rules.html new file mode 100644 index 0000000..f27cd96 --- /dev/null +++ b/variants/Convert/rules.html @@ -0,0 +1,7 @@ +

+ You can "capture" your own pieces, and then move them from the capturing + square in the same turn, with potential chaining if the captured unit + makes a self-capture too. +

+ +

Benjamin Auder (2021).

diff --git a/variants/Convert/style.css b/variants/Convert/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Convert/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); -- 2.44.0