From: Benjamin Auder Date: Fri, 19 Jun 2026 13:11:06 +0000 (+0200) Subject: Finished letter E X-Git-Url: https://git.auder.net/js/doc/html/current/assets/mini-custom.min.css?a=commitdiff_plain;h=HEAD;p=xogo.git Finished letter E --- diff --git a/js/base_rules.js b/js/base_rules.js index 3a960aa..532932b 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -158,8 +158,8 @@ export default class ChessRules { // 3a --> {x:3, y:10} static SquareToCoords(sq) { - return ArrayFun.toObject(["x", "y"], - [0, 1].map(i => parseInt(sq[i], 36))); + const [x, y] = [0, 1].map(i => parseInt(sq[i], 36)); + return { x, y }; } // {x:11, y:12} --> bc @@ -452,14 +452,16 @@ export default class ChessRules { const counts = reserveStr.split("").map(c => parseInt(c, 36)); const L = V.ReserveArray.length; this.reserve = { - w: ArrayFun.toObject(V.ReserveArray, counts.slice(0, L)), - b: ArrayFun.toObject(V.ReserveArray, counts.slice(L, 2 * L)) + w: Object.fromEntries(V.ReserveArray.map((k, i) => [k, counts[i]])), + b: Object.fromEntries(V.ReserveArray.map((k, i) => [k, counts[L + i]])) }; } initIspawn(ispawnStr) { - if (ispawnStr != "-") - this.ispawn = ArrayFun.toObject(ispawnStr.split(","), true); + if (ispawnStr != "-") { + this.ispawn = + Object.fromEntries(ispawnStr.split(",").map(k => [k, true])); + } else this.ispawn = {}; } @@ -1568,7 +1570,9 @@ export default class ChessRules { const piece = this.getPieceType(x, y); let moves = this.getPotentialMovesOf(piece, [x, y]); if (this.hasEnpassant && !!this.epSquare_s) { - moves = [...moves, this.getEnpassantCaptures(piece, [x, y])]; + Array.prototype.push.apply(moves, + this.getEnpassantCaptures(piece, [x, y])); + } if (this.isKing(0, 0, piece) && this.hasCastle) Array.prototype.push.apply(moves, this.getCastleMoves([x, y])); if (!noPP) @@ -1581,24 +1585,19 @@ export default class ChessRules { return []; const color = this.getColor(moves[0].start.x, moves[0].start.y); const oppCols = this.getOppCols(color); - if (this.options["capture"] && this.atLeastOneCapture(color)) moves = this.capturePostProcess(moves, oppCols); - if (this.options["atomic"]) moves = this.atomicPostProcess(moves, color, oppCols); - if ( moves.length > 0 && this.getPieceType(moves[0].start.x, moves[0].start.y) == "p" ) { moves = this.pawnPostProcess(moves, color, oppCols); } - if (this.options["cannibal"] && this.options["rifle"]) // In this case a rifle-capture from last rank may promote a pawn moves = this.riflePromotePostProcess(moves, color); - return moves; } @@ -2024,7 +2023,7 @@ export default class ChessRules { // Extract potential en-passant square from just played move setEpSquare_s(move) { - this.epSquare = undefined; + this.epSquare_s = undefined; const s = move.start, e = move.end; const gap = Math.abs(e.x - s.x); @@ -2043,37 +2042,45 @@ export default class ChessRules { ) ) { const step = (e.x - s.x) / gap; - this.epSquare = { - x: (s.x + e.x) / 2, - y: s.y - }; + this.epSquare_s = [ + { + x: (s.x + e.x) / 2, + y: s.y + }, + e //add endpoint (to know where captured piece is) + ]; } } - // Special case of en-passant captures: treated separately + // Special case of (pawn) en-passant captures: treated separately getEnpassantCaptures(piece, [x, y]) { - if (piece != 'p') + if (piece != 'p' || !this.epSquare_s) return []; const color = this.getColor(x, y); - const shiftX = (color == 'w' ? -1 : 1); - const oppCols = this.getOppCols(color); - if ( - this.epSquare && - this.epSquare.x == x + shiftX && //NOTE: epSquare.x not on edge - Math.abs(this.getY(this.epSquare.y - y)) == 1 && - // Doublemove (and Progressive?) guards: - this.board[this.epSquare.x][this.epSquare.y] == "" && - oppCols.includes(this.getColor(x, this.epSquare.y)) - ) { - const [epx, epy] = [this.epSquare.x, this.epSquare.y]; - this.board[epx][epy] = this.board[x][this.epSquare.y]; - let enpassantMove = this.getBasicMove([x, y], [epx, epy]); - this.board[epx][epy] = ""; - const lastIdx = enpassantMove.vanish.length - 1; //think Rifle - enpassantMove.vanish[lastIdx].x = x; - return [enpassantMove]; - } - return []; + const shiftX = (color == 'w' ? -1 : 1), + oppCols = this.getOppCols(color); + const L = this.epSquare_s.length; + const captSq = this.epSquare_s[L-1]; + let res = []; + for (let i = 0; i < L-1; i++) { + const sq = this.epSquare_s[i]; + if ( + sq.x == x + shiftX && //NOTE: epSquare.x not on edge + Math.abs(this.getY(sq.y - y)) == 1 && + // Doublemove (and Progressive?) guards: + this.board[sq.x][sq.y] == "" && + oppCols.includes(this.getColor(captSq.x, captSq.y)) + ) { + this.board[sq.x][sq.y] = this.board[captSq.x][captSq.y]; + let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]); + this.board[sq.x][sq.y] = ""; + const lastIdx = enpassantMove.vanish.length - 1; //think Rifle + enpassantMove.vanish[lastIdx].x = captSq.x; + enpassantMove.vanish[lastIdx].y = captSq.y; //in case of (Enpassant..) + res.push(enpassantMove); + } + } + return res; } getCastleMoves([x, y], finalSquares, castleWith, castleFlags) { diff --git a/pieces/black_nightrider.svg b/pieces/black_nightrider.svg new file mode 100644 index 0000000..b38d2de --- /dev/null +++ b/pieces/black_nightrider.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pieces/white_nightrider.svg b/pieces/white_nightrider.svg new file mode 100644 index 0000000..aefaefe --- /dev/null +++ b/pieces/white_nightrider.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/variants/Enpassant/class.js b/variants/Enpassant/class.js index dd2dfd6..d60c028 100644 --- a/variants/Enpassant/class.js +++ b/variants/Enpassant/class.js @@ -1,4 +1,5 @@ import ChessRules from "/js/base_rules.js"; +import PiPo from "/utils/PiPo.js"; export default class EnpassantRules extends ChessRules { @@ -12,160 +13,77 @@ export default class EnpassantRules extends ChessRules { pieceDef(piece, color, x, y) { let res = super.pieceDef(piece, color, x, y); - if (piece == 'n') + if (piece == 'n') { + res["class"] = "nightrider"; res.both[0].range = 8; //"infinite" + } return res; } - // TODO: comma separated (convention ?! yes..) - readEpSquare_s(squares) { - if (squares == "-") - return undefined; - // Expand init + dest squares into a full path: - const init = C.SquareToCoords(square.substr(0, 2)); - let newPath = [init]; - if (square.length == 2) - return newPath; - const dest = C.SquareToCoords(square.substr(2)); - const delta = ['x', 'y'].map(i => Math.abs(dest[i] - init[i])); - // Check if it's a knight(rider) movement: - let step = [0, 0]; - if (delta[0] > 0 && delta[1] > 0 && delta[0] != delta[1]) { - // Knightrider - const minShift = Math.min(delta[0], delta[1]); - step[0] = (dest.x - init.x) / minShift; - step[1] = (dest.y - init.y) / minShift; - } - else { - // "Sliders" - 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); - return newPath; - } - setEpSquare_s(move) { - // Argument is a move: all intermediate squares are en-passant candidates, + // All intermediate squares are en-passant candidates, // except if the moving piece is a king. - const move = moveOrSquare; const piece = move.appear[0].p; - if (piece == V.KING || + if (piece == 'k' || ( - Math.abs(move.end.x-move.start.x) <= 1 && - Math.abs(move.end.y-move.start.y) <= 1 + Math.abs(move.end.x - move.start.x) <= 1 && + Math.abs(move.end.y - move.start.y) <= 1 ) ) { - return undefined; + this.epSquare_s = undefined; } - const delta = [move.end.x-move.start.x, move.end.y-move.start.y]; - let step = undefined; - if (piece == V.KNIGHT) { - const divisor = Math.min(Math.abs(delta[0]), Math.abs(delta[1])); - step = [delta[0]/divisor || 0, delta[1]/divisor || 0]; - } else { - step = [ - delta[0]/Math.abs(delta[0]) || 0, - delta[1]/Math.abs(delta[1]) || 0 - ]; - } - let res = []; - for ( - let [x,y] = [move.start.x+step[0],move.start.y+step[1]]; - x != move.end.x || y != move.end.y; - x += step[0], y += step[1] - ) { - res.push({ x: x, y: y }); + else { + const delta = [move.end.x-move.start.x, move.end.y-move.start.y]; + const divisor = [Math.abs(delta[0]), Math.abs(delta[1])].sort(); + const idx = (divisor[0] > 0 ? 0 : 1); + const step = [delta[0]/divisor[idx], delta[1]/divisor[idx]]; + this.epSquare_s = []; + for ( + let [x,y] = [move.start.x + step[0], move.start.y + step[1]]; + x != move.end.x || y != move.end.y; + x += step[0], y += step[1] + ) { + this.epSquare_s.push({ x: x, y: y }); + } + // Add final square to know which piece is taken en passant: + this.epSquare_s.push(move.end); } - // Add final square to know which piece is taken en passant: - res.push(move.end); - return res; - } - - getEnpassantFen() { - const L = this.epSquares.length; - if (!this.epSquares[L - 1]) return "-"; //no en-passant - const epsq = this.epSquares[L - 1]; - if (epsq.length <= 2) return epsq.map(V.CoordsToSquare).join(""); - // Condensate path: just need initial and final squares: - return V.CoordsToSquare(epsq[0]) + V.CoordsToSquare(epsq[epsq.length - 1]); } getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x,y]); - // Add en-passant captures from this square: - const L = this.epSquares.length; - if (!this.epSquares[L - 1]) return moves; - const squares = this.epSquares[L - 1]; - const S = squares.length; - // Object describing the removed opponent's piece: - const pipoV = new PiPo({ - x: squares[S-1].x, - y: squares[S-1].y, - c: V.GetOppCol(this.turn), - p: this.getPiece(squares[S-1].x, squares[S-1].y) - }); - // Check if existing non-capturing moves could also capture en passant - moves.forEach(m => { - if ( - m.appear[0].p != V.PAWN && //special pawn case is handled elsewhere - m.vanish.length <= 1 && - [...Array(S-1).keys()].some(i => { - return m.end.x == squares[i].x && m.end.y == squares[i].y; - }) - ) { - m.vanish.push(pipoV); - } - }); // Special case of the king knight's movement: - if (this.getPiece(x, y) == V.KING) { - V.steps[V.KNIGHT].forEach(step => { - const endX = x + step[0]; - const endY = y + step[1]; - if ( - V.OnBoard(endX, endY) && - [...Array(S-1).keys()].some(i => { - return endX == squares[i].x && endY == squares[i].y; - }) - ) { - let enpassantMove = this.getBasicMove([x, y], [endX, endY]); - enpassantMove.vanish.push(pipoV); - moves.push(enpassantMove); - } - }); - } - return moves; - } - - getEnpassantCaptures([x, y], shiftX) { - const Lep = this.epSquares.length; - const squares = this.epSquares[Lep - 1]; - let moves = []; - if (!!squares) { - const S = squares.length; - const taken = squares[S-1]; + if (!!this.epSquare_s) { + const L = this.epSquare_s.length; const pipoV = new PiPo({ - x: taken.x, - y: taken.y, - p: this.getPiece(taken.x, taken.y), - c: this.getColor(taken.x, taken.y) + x: this.epSquare_s[L-1].x, + y: this.epSquare_s[L-1].y, + c: C.GetOppTurn(this.turn), + p: this.getPiece(this.epSquare_s[L-1].x, this.epSquare_s[L-1].y) }); - [...Array(S-1).keys()].forEach(i => { - const sq = squares[i]; - if (sq.x == x + shiftX && Math.abs(sq.y - y) == 1) { - let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]); - enpassantMove.vanish.push(pipoV); - moves.push(enpassantMove); + // Check if existing non-capturing moves could also capture en passant + moves.forEach(m => { + if ( + m.appear[0].p != 'p' && //special pawn case is handled elsewhere + m.vanish.length <= 1 && + this.epSquare_s.some(sq => m.end.x == sq.x && m.end.y == sq.y) + ) { + m.vanish.push(pipoV); } }); + if (this.getPiece(x, y) == 'k') { + super.pieceDef('n').both[0].steps.forEach(step => { + const [endX, endY] = [x + step[0], y + step[1]]; + if ( + this.onBoard(endX, endY) && + this.epSquare_s.some(sq => endX == sq.x && endY == sq.y) + ) { + let enpassantMove = this.getBasicMove([x, y], [endX, endY]); + enpassantMove.vanish.push(pipoV); + moves.push(enpassantMove); + } + }); + } } return moves; } diff --git a/variants/Enpassant/rules.html b/variants/Enpassant/rules.html index e3ca56e..5e2fdfc 100644 --- a/variants/Enpassant/rules.html +++ b/variants/Enpassant/rules.html @@ -1,6 +1,6 @@

All pieces can be captured "en passant" when they make more than one step. - So, for more fun, knights can make multi-steps: they turn into knightriders. + So, for more fun, knights can make multi-steps: they turn into (k)nightriders.

@@ -8,6 +8,8 @@ 1.e4 d5 2.Qh5 Bxg4 would take the queen en passant.

+

Note: the king has the extra ability to capture en-passant like a knight.

+

Andy Kurnia (1998).

diff --git a/variants/Enpassant/style.css b/variants/Enpassant/style.css index 95e35b2..e72f3eb 100644 --- a/variants/Enpassant/style.css +++ b/variants/Enpassant/style.css @@ -1 +1,8 @@ @import url("/css/base_pieces.css"); + +piece.white.nightrider { + background-image: url('/pieces/white_nightrider.svg'); +} +piece.black.nightrider { + background-image: url('/pieces/black_nightrider.svg'); +} diff --git a/variants/Evolution/class.js b/variants/Evolution/class.js index 7b5abba..9371ff2 100644 --- a/variants/Evolution/class.js +++ b/variants/Evolution/class.js @@ -1,27 +1,27 @@ -import { ChessRules } from "@/js/base_rules"; +import ChessRules from "/js/base_rules.js"; -export class EvolutionRules extends ChessRules { +export default class EvolutionRules extends ChessRules { + +// static get Options() { +// return C.Options; +// } getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x, y]); const c = this.getColor(x, y); const piece = this.getPiece(x, y); if ( - [V.BISHOP, V.ROOK, V.QUEEN].includes(piece) && + ['b', 'r', 'q'].includes(piece) && (c == 'w' && x == 7) || (c == 'b' && x == 0) ) { // Move from first rank const forward = (c == 'w' ? -1 : 1); for (let shift of [-2, 0, 2]) { - if ( - (piece == V.ROOK && shift != 0) || - (piece == V.BISHOP && shift == 0) - ) { + if ((piece == 'r' && shift != 0) || (piece == 'b' && shift == 0)) continue; - } if ( - V.OnBoard(x+2*forward, y+shift) && - this.board[x+forward][y+shift/2] != V.EMPTY && + this.onBoard(x+2*forward, y+shift) && + this.board[x+forward][y+shift/2] != "" && this.getColor(x+2*forward, y+shift) != c ) { moves.push(this.getBasicMove([x,y], [x+2*forward,y+shift])); diff --git a/variants/Evolution/rules.html b/variants/Evolution/rules.html new file mode 100644 index 0000000..205b059 --- /dev/null +++ b/variants/Evolution/rules.html @@ -0,0 +1,9 @@ +

Long-range pieces can jump over an obstacle when they are on the first rank.

+ +

+ From the first rank, rook, bishops and queen can play directly on the third + rank, even if an obstacle stands in-between. Development is thus accelerated. + This also help for defense. +

+ +

Zied Haddad (2020).

diff --git a/variants/Evolution/style.css b/variants/Evolution/style.css new file mode 100644 index 0000000..95e35b2 --- /dev/null +++ b/variants/Evolution/style.css @@ -0,0 +1 @@ +@import url("/css/base_pieces.css"); diff --git a/variants/Extinction/class.js b/variants/Extinction/class.js index c8e575d..75fff2a 100644 --- a/variants/Extinction/class.js +++ b/variants/Extinction/class.js @@ -1,118 +1,31 @@ -import { ChessRules } from "@/js/base_rules"; +import ChessRules from "/js/base_rules.js"; -export class ExtinctionRules extends ChessRules { +export default class ExtinctionRules extends ChessRules { - static get PawnSpecs() { - return Object.assign( - {}, - ChessRules.PawnSpecs, - { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) } - ); - } +// static get Options() { +// return C.Options; +// } - static IsGoodPosition(position) { - if (!ChessRules.IsGoodPosition(position)) return false; - // Also check that each piece type is present - const rows = position.split("/"); - let pieces = {}; - for (let row of rows) { - for (let i = 0; i < row.length; i++) { - if (isNaN(parseInt(row[i], 10)) && !pieces[row[i]]) - pieces[row[i]] = true; - } - } - if (Object.keys(pieces).length != 12) return false; - return true; - } - - setOtherVariables(fen) { - super.setOtherVariables(fen); - const pos = V.ParseFen(fen).position; - // NOTE: no need for safety "|| []", because each piece type is present - // (otherwise game is already over!) - this.material = { - w: { - [V.KING]: pos.match(/K/g).length, - [V.QUEEN]: pos.match(/Q/g).length, - [V.ROOK]: pos.match(/R/g).length, - [V.KNIGHT]: pos.match(/N/g).length, - [V.BISHOP]: pos.match(/B/g).length, - [V.PAWN]: pos.match(/P/g).length - }, - b: { - [V.KING]: pos.match(/k/g).length, - [V.QUEEN]: pos.match(/q/g).length, - [V.ROOK]: pos.match(/r/g).length, - [V.KNIGHT]: pos.match(/n/g).length, - [V.BISHOP]: pos.match(/b/g).length, - [V.PAWN]: pos.match(/p/g).length - } - }; - } - - // TODO: verify this assertion - atLeastOneMove() { - return true; //always at least one possible move + pawnPromotions() { + return super.pawnPromotions().concat('k'); } filterValid(moves) { return moves; //there is no check } - getCheckSquares() { - return []; - } - - postPlay(move) { - super.postPlay(move); - // Treat the promotion case: (not the capture part) - if (move.appear[0].p != move.vanish[0].p) { - this.material[move.appear[0].c][move.appear[0].p]++; - this.material[move.appear[0].c][V.PAWN]--; - } - if (move.vanish.length == 2 && move.appear.length == 1) - //capture - this.material[move.vanish[1].c][move.vanish[1].p]--; - } - - postUndo(move) { - super.postUndo(move); - if (move.appear[0].p != move.vanish[0].p) { - this.material[move.appear[0].c][move.appear[0].p]--; - this.material[move.appear[0].c][V.PAWN]++; - } - if (move.vanish.length == 2 && move.appear.length == 1) - this.material[move.vanish[1].c][move.vanish[1].p]++; - } - getCurrentScore() { - if (this.atLeastOneMove()) { - // Game not over? - const color = this.turn; - if ( - Object.keys(this.material[color]).some(p => { - return this.material[color][p] == 0; - }) - ) { - return this.turn == "w" ? "0-1" : "1-0"; - } - return "*"; - } - return this.turn == "w" ? "0-1" : "1-0"; //NOTE: currently unreachable... - } - - evalPosition() { const color = this.turn; - if ( - Object.keys(this.material[color]).some(p => { - return this.material[color][p] == 0; - }) - ) { - // Very negative (resp. positive) - // if white (reps. black) pieces set is incomplete - return (color == "w" ? -1 : 1) * V.INFINITY; + let material = { 'w': {}, 'b': {} }; + this.board.flat().forEach(cell => { + if (cell != "") + material[cell.charAt(0)][cell.charAt(1)] = true; + }); + for (const c of ['w', 'b']) { + if (Object.keys(material[c]).length < 6) + return c == 'w' ? "0-1" : "1-0"; } - return super.evalPosition(); + return "*"; } }; diff --git a/variants/Extinction/rules.html b/variants/Extinction/rules.html new file mode 100644 index 0000000..0a06c37 --- /dev/null +++ b/variants/Extinction/rules.html @@ -0,0 +1,8 @@ +

Losing all pieces of some kind means losing the game.

+ +

+ There are no checks: kings can be captured too + (and pawns can promote into kings). +

+ +

R. Wayne Schmittberger (1985).

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