From: Benjamin Auder Date: Tue, 25 Jul 2023 14:50:27 +0000 (+0200) Subject: Fix Convert, start Chaining X-Git-Url: https://git.auder.net/doc/html/vendor/current/img/DESCRIPTION?a=commitdiff_plain;h=b98feb3f6bb7e03319474f7a032e296750eb179f;p=xogo.git Fix Convert, start Chaining --- diff --git a/base_rules.js b/base_rules.js index aea3f57..c0d9b60 100644 --- a/base_rules.js +++ b/base_rules.js @@ -97,6 +97,14 @@ export default class ChessRules { return true; } + // Allow to take (moving: not disappearing) own pieces? + get hasSelfCaptures() { + return ( + this.options["recycle"] || + (this.options["teleport"] && this.subTurnTeleport == 1) + ); + } + get hasReserve() { return ( !!this.options["crazyhouse"] || @@ -1720,10 +1728,7 @@ export default class ChessRules { }); Array.prototype.push.apply(squares, zenCaptures); } - if ( - this.options["recycle"] || - (this.options["teleport"] && this.subTurnTeleport == 1) - ) { + if (this.hasSelfCaptures) { const selfCaptures = this.findDestSquares( [x, y], { diff --git a/variants.js b/variants.js index f3565dd..182fb8f 100644 --- a/variants.js +++ b/variants.js @@ -25,6 +25,7 @@ const variants = [ {name: 'Cannibal', desc: 'Capture powers'}, {name: 'Capablanca', desc: 'Capablanca Chess', disp: 'Capablanca'}, {name: 'Capture', desc: 'Mandatory captures'}, + {name: 'Chaining', desc: 'Speed-up development'}, {name: 'Chakart', desc: 'Capture the princess'}, {name: 'Checkered', desc: 'Shared pieces'}, {name: 'Checkless', desc: 'No-check mode'}, diff --git a/variants/Chaining/class.js b/variants/Chaining/class.js new file mode 100644 index 0000000..5cd4c1d --- /dev/null +++ b/variants/Chaining/class.js @@ -0,0 +1,117 @@ +import ChessRules from "/base_rules.js"; +import PiPo from "/utils/PiPo.js"; +import Move from "/utils/Move.js"; + +export default class ChainingRules extends ChessRules { + + static get Options() { + return { + select: C.Options.select, + input: C.Options.input, + styles: ["atomic", "capture", "crazyhouse", "cylinder", "dark", "zen"] + }; + } + + get hasSelfCaptures() { + return true; + } + + canSelfTake() { + return true; //self captures induce chaining + } + + setOtherVariables(fenParsed, pieceArray) { + super.setOtherVariables(fenParsed, pieceArray); + // Stack of "last move" only for intermediate chaining + this.lastMoveEnd = []; + } + + getBasicMove([sx, sy], [ex, ey], tr) { + const L = this.lastMoveEnd.length; + const piece = (L >= 1 ? this.lastMoveEnd[L-1].p : null); + if ( + this.board[ex][ey] == "" || + this.getColor(ex, ey) == C.GetOppTurn(this.turn) + ) { + if (piece && !tr) + tr = {c: this.turn, p: piece}; + let mv = super.getBasicMove([sx, sy], [ex, ey], tr); + if (piece) + mv.vanish.pop(); //end of a chain: initial piece remains + return mv; + } + // (Self)Capture: initial, or inside a chain + const initPiece = (piece || this.getPiece(sx, sy)), + destPiece = 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: this.turn, + p: (!!tr ? tr.p : initPiece) + }) + ], + vanish: [ + new PiPo({ + x: ex, + y: ey, + c: this.turn, + p: destPiece + }) + ] + }); + if (!piece) { + // Initial capture + mv.vanish.unshift( + new PiPo({ + x: sx, + y: sy, + c: this.turn, + p: initPiece + }) + ); + } + mv.chained = destPiece; //easier (no need to detect it) + return mv; + } + + 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 self-capture was played: wrong square + return []; + } + return super.getPotentialMovesFrom([x, y], color); + } + + isLastMove(move) { + return !move.chained; + } + + postPlay(move) { + super.postPlay(move); + if (!!move.converted) { + this.lastMoveEnd.push({ + x: move.end.x, + y: move.end.y, + p: move.chained + }); + } + else + this.lastMoveEnd = []; + } + +}; diff --git a/variants/Chaining/rules.html b/variants/Chaining/rules.html new file mode 100644 index 0000000..f27cd96 --- /dev/null +++ b/variants/Chaining/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/Chaining/style.css b/variants/Chaining/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Chaining/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); diff --git a/variants/Convert/class.js b/variants/Convert/class.js index 566feb5..37c2c3d 100644 --- a/variants/Convert/class.js +++ b/variants/Convert/class.js @@ -4,15 +4,10 @@ import Move from "/utils/Move.js"; export default class ConvertRules extends ChessRules { - // TODO: options ? (balance progressive ok it seems?) static get Options() { return { select: C.Options.select, - input: C.Options.input, - styles: [ - "atomic", "cannibal", "capture", "cylinder", - "dark", "madrasi", "rifle", "teleport" - ] + styles: ["cylinder", "dark", "recycle", "teleport"] }; } @@ -37,21 +32,18 @@ export default class ConvertRules extends ChessRules { 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; + const piece = (L >= 1 ? this.lastMoveEnd[L-1].p : null); if (this.board[ex][ey] == "") { if (piece && !tr) - tr = {c: c, p: piece}; + tr = {c: this.turn, p: piece}; let mv = super.getBasicMove([sx, sy], [ex, ey], tr); if (piece) - mv.vanish.pop(); + mv.vanish.pop(); //end of a chain: initial piece remains return mv; } // Capture: initial, or inside a chain - const initPiece = (piece || this.getPiece(sx, sy)); - const oppCol = C.GetOppTurn(c); - const oppPiece = this.getPiece(ex, ey); + const initPiece = (piece || this.getPiece(sx, sy)), + destPiece = this.getPiece(ex, ey); let mv = new Move({ start: {x: sx, y: sy}, end: {x: ex, y: ey}, @@ -59,7 +51,7 @@ export default class ConvertRules extends ChessRules { new PiPo({ x: ex, y: ey, - c: c, + c: this.turn, p: (!!tr ? tr.p : initPiece) }) ], @@ -67,8 +59,8 @@ export default class ConvertRules extends ChessRules { new PiPo({ x: ex, y: ey, - c: oppCol, - p: oppPiece + c: C.GetOppTurn(this.turn), + p: destPiece }) ] }); @@ -78,11 +70,12 @@ export default class ConvertRules extends ChessRules { new PiPo({ x: sx, y: sy, - c: c, + c: this.turn, p: initPiece }) ); } + mv.converted = destPiece; //easier (no need to detect it) return mv; } @@ -165,7 +158,7 @@ export default class ConvertRules extends ChessRules { return false; } - underAttack([x, y], color) { + underAttack([x, y], [color]) { let explored = []; return this.underAttack_aux([x, y], color, explored); } @@ -176,321 +169,20 @@ export default class ConvertRules extends ChessRules { } isLastMove(move) { - return ( - super.isLastMove(move) || - move.vanish.length <= 1 || - move.vanish[1].c != move.vanish[0].c || - move.appear.length == 2 //castle! - ); + return !move.converted; } postPlay(move) { super.postPlay(move); - if (!this.isLastMove(move)) { + if (!!move.converted) { this.lastMoveEnd.push({ x: move.end.x, y: move.end.y, - p: move.vanish[1].p //TODO: check this + p: move.converted }); } - } - -}; - -// 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; - if (!!this.lastMoveEnd[L-1]) { - if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y) - // A capture was played: wrong square - return []; - asA = this.lastMoveEnd[L-1].p; - } - switch (asA || this.getPiece(x, y)) { - case V.PAWN: return super.getPotentialPawnMoves([x, y]); - case V.ROOK: return super.getPotentialRookMoves([x, y]); - case V.KNIGHT: return super.getPotentialKnightMoves([x, y]); - case V.BISHOP: return super.getPotentialBishopMoves([x, y]); - case V.QUEEN: return super.getPotentialQueenMoves([x, y]); - case V.KING: return super.getPotentialKingMoves([x, y]); - } - return []; - } - - getPossibleMovesFrom(sq) { - const L = this.lastMoveEnd.length; - let asA = undefined; - if (!!this.lastMoveEnd[L-1]) { - if ( - sq[0] != this.lastMoveEnd[L-1].x || - sq[1] != this.lastMoveEnd[L-1].y - ) { - return []; - } - asA = this.lastMoveEnd[L-1].p; - } - return this.filterValid(this.getPotentialMovesFrom(sq, asA)); - } - - isAttacked_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.isAttacked([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 ( - V.OnBoard(i, j) && - this.board[i][j] != V.EMPTY && - // NOTE: no need to check color (no enemy pawn can take directly) - this.getPiece(i, j) == V.PAWN - ) { - toCheck.push([i, j]); - } - } - // Knights: - V.steps[V.KNIGHT].forEach(s => { - const [i, j] = [x + s[0], y + s[1]]; - if ( - V.OnBoard(i, j) && - this.board[i][j] != V.EMPTY && - this.getPiece(i, j) == V.KNIGHT - ) { - toCheck.push([i, j]); - } - }); - // Sliders: - V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => { - let [i, j] = [x + s[0], y + s[1]]; - while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { - i += s[0]; - j += s[1]; - } - if (!V.OnBoard(i, j)) return; - const piece = this.getPiece(i, j); - if ( - piece == V.QUEEN || - (piece == V.ROOK && (s[0] == 0 || s[1] == 0)) || - (piece == V.BISHOP && (s[0] != 0 && s[1] != 0)) - ) { - toCheck.push([i, j]); - } - }); - for (let ij of toCheck) { - if (this.isAttacked_aux(ij, color, explored)) return true; - } - return false; - } - - isAttacked([x, y], color) { - let explored = []; - return this.isAttacked_aux([x, y], color, explored); - } - - filterValid(moves) { - // No "checks" (except to forbid castle) - return moves; - } - - getCheckSquares() { - return []; - } - - prePlay(move) { - const c = this.turn; - // Extra conditions to avoid tracking converted kings: - if ( - move.appear[0].p == V.KING && - move.vanish.length >= 1 && - move.vanish[0].p == V.KING - ) { - this.kingPos[c][0] = move.appear[0].x; - this.kingPos[c][1] = move.appear[0].y; - } - } - - play(move) { - this.prePlay(move); - const c = this.turn; - move.flags = JSON.stringify(this.aggregateFlags()); - V.PlayOnBoard(this.board, move); - if (!move.end.converted) { - // Not a capture: change turn - this.turn = V.GetOppCol(this.turn); - this.movesCount++; - this.lastMoveEnd.push(null); - } - else { - this.lastMoveEnd.push( - Object.assign({}, move.end, { p: move.end.converted }) - ); - } - 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; + else + this.lastMoveEnd = []; } }; diff --git a/variants/Convert/rules.html b/variants/Convert/rules.html index f27cd96..5221fab 100644 --- a/variants/Convert/rules.html +++ b/variants/Convert/rules.html @@ -1,7 +1,6 @@

- 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. + Enemy units are converted to your color after capture (not removed from board). + They must be moved from the destination square (potentially capturing too).

Benjamin Auder (2021).