+PROBABLY WON'T FIX:
Embedded rules language not updated when language is set (in Analyse, Game and Problems)
If new live game starts in background, "new game" notify OK but not first move (not too serious however)
-On smartphone for Teleport, Chakart, Weiqi and some others: option "confirm moves on touch screen"
-(=> comme pour corr) + option "confirm moves in corr games"?
NEW VARIANTS:
https://www.pychess.org/variant/manchu
https://www.pychess.org/variant/dobutsu
-https://www.pychess.org/variant/cambodian
-https://www.pychess.org/variant/makpong
-https://www.pychess.org/variant/janggi
-https://www.pychess.org/variant/kyotoshogi
-https://www.pychess.org/variant/hoppelpoppel
https://musketeerchess.net/games/musketeer/index.php Attention règle de promotion + SVG / PNG
(https://www.pychess.org/variant/shogun)
Isardam (type B) : https://echekk.fr/spip.php?page=article&id_article=280
--- /dev/null
+../Xiangqi/ba.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/bc.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/be.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/bk.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/bn.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/bp.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/br.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/wa.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/wc.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/we.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/wk.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/wn.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/wp.svg
\ No newline at end of file
--- /dev/null
+../Xiangqi/wr.svg
\ No newline at end of file
// All possible moves from selected square
getPotentialMovesFrom([x, y]) {
switch (this.getPiece(x, y)) {
- case V.PAWN:
- return this.getPotentialPawnMoves([x, y]);
- case V.ROOK:
- return this.getPotentialRookMoves([x, y]);
- case V.KNIGHT:
- return this.getPotentialKnightMoves([x, y]);
- case V.BISHOP:
- return this.getPotentialBishopMoves([x, y]);
- case V.QUEEN:
- return this.getPotentialQueenMoves([x, y]);
- case V.KING:
- return this.getPotentialKingMoves([x, y]);
+ case V.PAWN: return this.getPotentialPawnMoves([x, y]);
+ case V.ROOK: return this.getPotentialRookMoves([x, y]);
+ case V.KNIGHT: return this.getPotentialKnightMoves([x, y]);
+ case V.BISHOP: return this.getPotentialBishopMoves([x, y]);
+ case V.QUEEN: return this.getPotentialQueenMoves([x, y]);
+ case V.KING: return this.getPotentialKingMoves([x, y]);
}
return []; //never reached
}
"King of the Hill": "King of the Hill",
"Kings cross the 8x8 board": "Kings cross the 8x8 board",
"Kings cross the 11x11 board": "Kings cross the 11x11 board",
+ "Knibis and Bisknis": "Knibis and Bisknis",
"Knight in pocket": "Knight in pocket",
"Knight versus pawns": "Knight versus pawns",
+ "Korean Chess": "Korean Chess",
"Lancers everywhere": "Lancers everywhere",
"Landing on the board": "Landing on the board",
"Laws of attraction": "Laws of attraction",
"Squares disappear": "Squares disappear",
"Standard rules": "Standard rules",
"Stun & kick pieces": "Stun & kick pieces",
- "Thai Chess": "Thai Chess",
+ "Thai Chess (v1)": "Thai Chess (v1)",
+ "Thai Chess (v2)": "Thai Chess (v2)",
"The colorbound clobberers": "The colorbound clobberers",
"The end of the world": "The end of the world",
"Transform an essay": "Transform an essay",
"King of the Hill": "Rey de la Colina",
"Kings cross the 8x8 board": "Los reyes cruzan el 8x8 tablero",
"Kings cross the 11x11 board": "Los reyes cruzan el 11x11 tablero",
+ "Knibis and Bisknis": "Knibis y Bisknis",
"Knight in pocket": "Caballo en bolsillo",
"Knight versus pawns": "Caballo contra peones",
+ "Korean Chess": "Ajedrez coreano",
"Lancers everywhere": "Lanceros por todas partes",
"Landing on the board": "Aterrizando en el tablero",
"Laws of attraction": "Las leyes de las atracciones",
"Squares disappear": "Las casillas desaparecen",
"Standard rules": "Reglas estandar",
"Stun & kick pieces": "Aturdir & patear piezas",
- "Thai Chess": "Ajedrez tailandés",
+ "Thai Chess (v1)": "Ajedrez tailandés (v1)",
+ "Thai Chess (v2)": "Ajedrez tailandés (v2)",
"The colorbound clobberers": "Los batidores unicolor",
"The end of the world": "El fin del mundo",
"Transform an essay": "Transformar un ensayo",
"King of the Hill": "Roi de la Colline",
"Kings cross the 8x8 board": "Les rois traversent l'échiquier 8x8",
"Kings cross the 11x11 board": "Les rois traversent l'échiquier 11x11",
+ "Knibis and Bisknis": "Knibis et Bisknis",
"Knight in pocket": "Cavalier en poche",
"Knight versus pawns": "Cavalier contre pions",
+ "Korean Chess": "Échecs coréens",
"Lancers everywhere": "Lanciers à tous les coins",
"Landing on the board": "Débarquement sur l'échiquier",
"Laws of attraction": "Les lois de l'attraction",
--- /dev/null
+p.boxed
+ | Knights capture as bishops, and bishops capture as knights.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR:
+ .diagram.diag22
+ | fen:r1bqkbnr/pppp1ppp/8/4B3/4n3/8/PPPP1PPP/RNBQK1NR:
+ figcaption Left: 1.e4 e5 2.Bc4 Nc6. Right: after 3.Bxe5 Nxe4
+
+p The minor pieces are thus, in a way, more balanced.
+
+h3 Source
+
+p
+ | The variant is reported by Jörg Knappen in 2002, but is probably anterior
+ | according to the
+ a(href="https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html")
+ | chessvariants page
+ | .
--- /dev/null
+p.boxed
+ | Los caballos capturan como alfiles y los alfiles como caballos.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR:
+ .diagram.diag22
+ | fen:r1bqkbnr/pppp1ppp/8/4B3/4n3/8/PPPP1PPP/RNBQK1NR:
+ figcaption Izquierda: 1.e4 e5 2.Bc4 Nc6. Derecha: después de 3.Bxe5 Nxe4
+
+p Por tanto, las piezas menores son, en cierto modo, más equilibradas.
+
+h3 Fuente
+
+p
+ | La variante es descrita por Jörg Knappen en 2002, pero se podría decir que
+ | arriba según la
+ a(href="https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html")
+ | página chessvariants
+ | .
--- /dev/null
+p.boxed
+ | Les cavaliers capturent comme des fous, et les fous comme des cavaliers.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR:
+ .diagram.diag22
+ | fen:r1bqkbnr/pppp1ppp/8/4B3/4n3/8/PPPP1PPP/RNBQK1NR:
+ figcaption Gauche : 1.e4 e5 2.Bc4 Nc6. Droite : après 3.Bxe5 Nxe4
+
+p Les pièces mineures sont ainsi, d'une certaine manière, plus équilibrées.
+
+h3 Source
+
+p
+ | La variante est décrite par Jörg Knappen en 2002, mais est sans doute
+ | antérieure d'après la
+ a(href="https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html")
+ | page chessvariants
+ | .
--- /dev/null
+p.boxed
+ | TODO: Korean Chess
+
+p https://www.pychess.org/variant/janggi
+
+p https://fr.wikipedia.org/wiki/Janggi
--- /dev/null
+p.boxed TODO
--- /dev/null
+p.boxed TODO
--- /dev/null
+p.boxed
+ | Makruk, with kings immobilized by checks.
+
+p
+ | Everything goes as in the variant
+ a(href="/#/variants/Makruk") Makruk
+ | , but the king isn't allowed to move when he's under check:
+ | one has to capture the attacker, or intercept the attack if possible.
+
+figure.diagram-container
+ .diagram
+ | fen:rnbqkb1r/8/pppNpppp/3n4/8/PPPPPPPP/8/R1BKQBNR:
+ figcaption Checkmate: the king cannot move.
+
+p.
+ This variant is designed to reduce draws.
+ It is played in Makruk single elimination tournaments in Thailand to
+ decide a winner after a certain number of games have been drawn.
+
+p
+ | I found the variant on
+ a(href="https://www.pychess.org/variant/makpong") pychess-variants
+ | , where you can also play it.
--- /dev/null
+p.boxed
+ | Makruk, con reyes inmovilizados por los jaques.
+
+p
+ | Todo va como en la variante
+ a(href="/#/variants/Makruk") Makruk
+ | , pero el rey no puede moverse cuando está en jaque:
+ | debemos capturar al agresor, o interponer una pieza si es posible.
+
+figure.diagram-container
+ .diagram
+ | fen:rnbqkb1r/8/pppNpppp/3n4/8/PPPPPPPP/8/R1BKQBNR:
+ figcaption Jaque mate: el rey no puede moverse.
+
+p.
+ Esta variante está diseñada para minimizar las tablas.
+ Se juega en los torneos eliminatorios de Makruk en
+ Tailandia, para decidir un ganador después
+ una serie de partes no decisivas.
+
+p
+ | Encontré esta variante en
+ a(href="https://www.pychess.org/variant/makpong") pychess-variants
+ | , donde también puedes jugarlo.
--- /dev/null
+p.boxed
+ | Makruk, avec rois immobilisés par les échecs.
+
+p
+ | Tout se déroule comme dans la variante
+ a(href="/#/variants/Makruk") Makruk
+ | , mais le roi n'a pas le droit de bouger lorsqu'il est en échec :
+ | il faut capturer l'assaillant, ou interposer une pièce si possible.
+
+figure.diagram-container
+ .diagram
+ | fen:rnbqkb1r/8/pppNpppp/3n4/8/PPPPPPPP/8/R1BKQBNR:
+ figcaption Échec et mat : le roi ne peut pas bouger.
+
+p.
+ Cette variante est conçue pour minimiser les nulles.
+ Elle est jouée dans les tournois de Makruk à élimination direct en
+ Thaïlande, pour décider d'un vainqueur après
+ un certain nombre de parties non décisives.
+
+p
+ | J'ai trouvé cette variante sur
+ a(href="https://www.pychess.org/variant/makpong") pychess-variants
+ | , où vous pouvez aussi y jouer.
li The Advisor (A) moves and captures exactly like a king.
li.
The Cannon (C) moves like a rook, but needs an
- intervening piece in-between to achieve a capture.
- It cannot hop over another cannon, however.
+ intervening piece in-between before it can land or capture something.
+ It cannot hop over another cannon.
li.
The Elephant (E) is a leaping piece that moves
diagonally one or two spaces.
.diagram.diag12
| fen:8/6P1/5P2/4e3/3P1p2/8/8/8 c3,c7,d4,d6,g3,f6,g7:
.diagram.diag22
- | fen:3B4/3P4/8/1P1c1p1P/8/3c4/8/3N4 d8,d6,c5,e5,h5,d4:
+ | fen:3B4/3P4/8/1P1cp2P/8/3c4/8/3N4 d8,a5,f5,g5,h5:
figcaption Elephant & Cannon movements.
h3 Piece valuation
li El Consejero (A) se mueve y captura como un rey.
li.
El cañón (C) se mueve como una torre, pero necesita una pieza
- intermediario para realizar una captura.
- Sin embargo, no puede saltar sobre otro cañón.
+ intermediario antes de aterrizar o de realizar una captura.
+ No puede saltar sobre otro cañón.
li.
El elefante (E) avanza en diagonal uno o dos cuadrados,
posiblemente saltando sobre una pieza.
.diagram.diag12
| fen:8/6P1/5P2/4e3/3P1p2/8/8/8 c3,c7,d4,d6,g3,f6,g7:
.diagram.diag22
- | fen:3B4/3P4/8/1P1c1p1P/8/3c4/8/3N4 d8,d6,c5,e5,h5,d4:
- figcaption Movimientos del elefante y el cañón.
+ | fen:3B4/3P4/8/1P1cp2P/8/3c4/8/3N4 d8,a5,f5,g5,h5:
+ figcaption Movimientos del elefante y del cañón.
h3 Valores de las piezas
li Le Conseiller (A) se déplace et capture exactement comme un roi.
li.
Le Canon (C) se déplace comme une tour, mais a besoin d'une pièce
- intermédiaire pour effectuer une capture.
- Cependant, il ne peut pas sauter par dessus un autre canon.
+ intermédiaire avant d'atterrir ou d'effectuer une capture.
+ Il ne peut pas sauter par dessus un autre canon.
li.
L'Éléphant (E) avance en diagonale d'une case ou deux,
éventuellement en sautant par dessus une pièce.
.diagram.diag12
| fen:8/6P1/5P2/4e3/3P1p2/8/8/8 c3,c7,d4,d6,g3,f6,g7:
.diagram.diag22
- | fen:3B4/3P4/8/1P1c1p1P/8/3c4/8/3N4 d8,d6,c5,e5,h5,d4:
+ | fen:3B4/3P4/8/1P1cp2P/8/3c4/8/3N4 d8,a5,f5,g5,h5:
figcaption Déplacements de l'Éléphant et du Canon.
h3 Valeurs des pièces
"Fullcavalry",
"Grand",
"Grasshopper",
+ "Hoppelpoppel",
"Omega",
"Ordamirror",
"Perfect",
p (Partial) Game evolution in time and space.
-
var varlist = [
+ "Jangqi",
+ "Makpong",
"Makruk",
"Minishogi",
"Minixiangqi",
"Fullcavalry",
"Grand",
"Grasshopper",
+ "Hoppelpoppel",
"Omega",
"Ordamirror",
"Perfect",
p Evolución (parcial) del juego en espacio y tiempo.
-
var varlist = [
+ "Jangqi",
+ "Makpong",
"Makruk",
"Minishogi",
"Minixiangqi",
"Fullcavalry",
"Grand",
"Grasshopper",
+ "Hoppelpoppel",
"Omega",
"Ordamirror",
"Perfect",
p Évolution (partielle) du jeu dans l'espace et le temps.
-
var varlist = [
+ "Jangqi",
+ "Makpong",
"Makruk",
"Minishogi",
"Minixiangqi",
--- /dev/null
+import { ChessRules } from "@/base_rules";
+
+export class HoppelpoppelRules extends ChessRules {
+
+ getSlideNJumpMoves_([x, y], steps, oneStep, options) {
+ options = options || {};
+ let moves = [];
+ outerLoop: for (let step of steps) {
+ let i = x + step[0];
+ let j = y + step[1];
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ if (!options.onlyTake) moves.push(this.getBasicMove([x, y], [i, j]));
+ if (!!oneStep) continue outerLoop;
+ i += step[0];
+ j += step[1];
+ }
+ if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !options.onlyMove)
+ moves.push(this.getBasicMove([x, y], [i, j]));
+ }
+ return moves;
+ }
+
+ getPotentialKnightMoves(sq) {
+ // The knight captures like a bishop
+ return (
+ this.getSlideNJumpMoves_(
+ sq, ChessRules.steps[V.KNIGHT], "oneStep", { onlyMove: true })
+ .concat(
+ this.getSlideNJumpMoves_(
+ sq, ChessRules.steps[V.BISHOP], null, { onlyTake: true }))
+ );
+ }
+
+ getPotentialBishopMoves(sq) {
+ // The bishop captures like a knight
+ return (
+ this.getSlideNJumpMoves_(
+ sq, ChessRules.steps[V.BISHOP], null, { onlyMove: true })
+ .concat(
+ this.getSlideNJumpMoves_(
+ sq, ChessRules.steps[V.KNIGHT], "oneStep", { onlyTake: true }))
+ );
+ }
+
+ isAttackedByKnight([x, y], color) {
+ return super.isAttackedBySlideNJump(
+ [x, y],
+ color,
+ V.KNIGHT,
+ V.steps[V.BISHOP]
+ );
+ }
+
+ isAttackedByAntiking([x, y], color) {
+ return super.isAttackedBySlideNJump(
+ [x, y],
+ color,
+ V.BISHOP,
+ V.steps[V.KNIGHT],
+ "oneStep"
+ );
+ }
+
+};
--- /dev/null
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class JangqiRules extends ChessRules {
+
+ static get Monochrome() {
+ return true;
+ }
+
+ static get Notoodark() {
+ return true;
+ }
+
+ static get Lines() {
+ let lines = [];
+ // Draw all inter-squares lines, shifted:
+ for (let i = 0; i < V.size.x; i++)
+ lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]);
+ for (let j = 0; j < V.size.y; j++)
+ lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]);
+ // Add palaces:
+ lines.push([[0.5, 3.5], [2.5, 5.5]]);
+ lines.push([[0.5, 5.5], [2.5, 3.5]]);
+ lines.push([[9.5, 3.5], [7.5, 5.5]]);
+ lines.push([[9.5, 5.5], [7.5, 3.5]]);
+ return lines;
+ }
+
+ // No castle, but flag: bikjang
+ static get HasCastle() {
+ return false;
+ }
+
+ static get HasEnpassant() {
+ return false;
+ }
+
+ static get LoseOnRepetition() {
+ return true;
+ }
+
+ static get ELEPHANT() {
+ return "e";
+ }
+
+ static get CANNON() {
+ return "c";
+ }
+
+ static get ADVISOR() {
+ return "a";
+ }
+
+ static get PIECES() {
+ return [V.PAWN, V.ROOK, V.KNIGHT, V.ELEPHANT, V.ADVISOR, V.KING, V.CANNON];
+ }
+
+ getPpath(b) {
+ return "Jiangqi/" + b;
+ }
+
+ static get size() {
+ return { x: 10, y: 9};
+ }
+
+ getPotentialMovesFrom(sq) {
+ switch (this.getPiece(sq[0], sq[1])) {
+ case V.PAWN: return this.getPotentialPawnMoves(sq);
+ case V.ROOK: return this.getPotentialRookMoves(sq);
+ case V.KNIGHT: return this.getPotentialKnightMoves(sq);
+ case V.ELEPHANT: return this.getPotentialElephantMoves(sq);
+ case V.ADVISOR: return this.getPotentialAdvisorMoves(sq);
+ case V.KING: return this.getPotentialKingMoves(sq);
+ case V.CANNON: return this.getPotentialCannonMoves(sq);
+ }
+ return []; //never reached
+ }
+
+ static IsGoodFlags(flags) {
+ // bikjang status of last move + pass
+ return !!flags.match(/^[0-2]{2,2}$/);
+ }
+
+ aggregateFlags() {
+ return [this.bikjangFlag, this.passFlag];
+ }
+
+ disaggregateFlags(flags) {
+ this.bikjangFlag = flags[0];
+ this.passFlag = flags[1];
+ }
+
+ getFlagsFen() {
+ return this.bikjangFlag.toString() + this.passFlag.toString()
+ }
+
+ setFlags(fenflags) {
+ this.bikjangFlag = parseInt(fenflags.charAt(0), 10);
+ this.passFlag = parseInt(fenflags.charAt(1), 10);
+ }
+
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ // Sub-turn is useful only at first move...
+ this.subTurn = 1;
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ let moves = [];
+ const c = this.getColor(x, y);
+ const oppCol = V.GetOppCol(c);
+ if (this.kingPos[c][0] == x && this.kingPos[c][1] == y) {
+ // Add pass move (might be impossible if undercheck)
+ moves.push(
+ new Move({
+ appear: [],
+ vanish: [],
+ start: { x: this.kingPos[c][0], y: this.kingPos[c][1] },
+ end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] }
+ })
+ );
+ }
+ // TODO: next "if" is mutually exclusive with the block above
+ if (this.movesCount <= 1) {
+ const firstRank = (this.movesCount == 0 ? 9 : 0);
+ const [initFile, destFile] = (this.subTurn == 1 ? [1, 2] : [7, 6]);
+ // Only option is knight / elephant swap:
+ if (x == firstRank && y == initFile) {
+ moves.push(
+ new Move({
+ appear: [
+ new PiPo({
+ x: x,
+ y: destFile,
+ c: c,
+ p: V.KNIGHT
+ }),
+ new PiPo({
+ x: x,
+ y: y,
+ c: c,
+ p: V.ELEPHANT
+ })
+ ],
+ vanish: [
+ new PiPo({
+ x: x,
+ y: y,
+ c: c,
+ p: V.KNIGHT
+ }),
+ new PiPo({
+ x: x,
+ y: destFile,
+ c: c,
+ p: V.ELEPHANT
+ })
+ ],
+ start: { x: x, y: y },
+ end: { x: x, y: destFile }
+ })
+ );
+ }
+ }
+ else
+ Array.prototype.push.apply(moves, super.getPotentialMovesFrom([x, y]));
+ return moves;
+ }
+
+ getPotentialPawnMoves([x, y]) {
+ const c = this.getColor(x, y);
+ const oppCol = V.GetOppCol(c);
+ const shiftX = (c == 'w' ? -1 : 1);
+ const rank23 = (oppCol == 'w' ? [8, 7] : [1, 2]);
+ let steps = [[shiftX, 0], [0, -1], [0, 1]];
+ // Diagonal moves inside enemy palace:
+ if (y == 4 && x == rank23[0])
+ Array.prototype.push.apply(steps, [[shiftX, 1], [shiftX, -1]]);
+ else if (x == rank23[1]) {
+ if (y == 3) steps.push([shiftX, 1]);
+ else if (y == 5) steps.push([shiftX, -1]);
+ }
+ return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ }
+
+ knightStepsFromRookStep(step) {
+ if (step[0] == 0) return [ [1, 2*step[1]], [-1, 2*step[1]] ];
+ return [ [2*step[0], 1], [2*step[0], -1] ];
+ }
+
+ getPotentialKnightMoves([x, y]) {
+ let steps = [];
+ for (let rookStep of ChessRules.steps[V.ROOK]) {
+ const [i, j] = [x + rookStep[0], y + rookStep[1]];
+ if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ Array.prototype.push.apply(steps,
+ // These moves might be impossible, but need to be checked:
+ this.knightStepsFromRookStep(rookStep));
+ }
+ }
+ return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ }
+
+ elephantStepsFromRookStep(step) {
+ if (step[0] == 0) return [ [2, 3*step[1]], [-2, 3*step[1]] ];
+ return [ [3*step[0], 2], [3*step[0], -2] ];
+ }
+
+ getPotentialElephantMoves([x, y]) {
+ let steps = [];
+ for (let rookStep of ChessRules.steps[V.ROOK]) {
+ const eSteps = this.elephantStepsFromRookStep(rookStep);
+ const [i, j] = [x + rookStep[0], y + rookStep[1]];
+ if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ // Check second crossing:
+ const knightSteps = this.knightStepsFromRookStep(rookStep);
+ for (let k of [0, 1]) {
+ const [ii, jj] = [x + knightSteps[k][0], y + knightSteps[k][1]];
+ if (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY)
+ steps.push(eSteps[k]); //ok: same ordering
+ }
+ }
+ }
+ return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ }
+
+ palacePeopleMoves([x, y]) {
+ const c = this.getColor(x, y);
+ let steps = [];
+ // Orthogonal steps:
+ if (x < (c == 'w' ? 9 : 2)) steps.push([1, 0]);
+ if (x > (c == 'w' ? 7 : 0)) steps.push([-1, 0]);
+ if (y > 3) steps.push([0, -1]);
+ if (y < 5) steps.push([0, 1]);
+ // Diagonal steps, if in the middle or corner:
+ if (
+ y != 4 &&
+ (
+ (c == 'w' && x != 8) ||
+ (c == 'b' && x != 1)
+ )
+ ) {
+ // In a corner: maximum one diagonal step available
+ let step = null;
+ const direction = (c == 'w' ? -1 : 1);
+ if ((c == 'w' && x == 9) || (c == 'b' && x == 0)) {
+ // On first line
+ if (y == 3) step = [direction, 1];
+ else step = [direction, -1];
+ }
+ else if ((c == 'w' && x == 7) || (c == 'b' && x == 2)) {
+ // On third line
+ if (y == 3) step = [-direction, 1];
+ else step = [-direction, -1];
+ }
+ steps.push(step);
+ }
+ else if (
+ y == 4 &&
+ (
+ (c == 'w' && x == 8) ||
+ (c == 'b' && x == 1)
+ )
+ ) {
+ // At the middle: all directions available
+ Array.prototype.push.apply(steps, ChessRules.steps[V.BISHOP]);
+ }
+ return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ }
+
+ getPotentialAdvisorMoves(sq) {
+ return this.palacePeopleMoves(sq);
+ }
+
+ getPotentialKingMoves(sq) {
+ return this.palacePeopleMoves(sq);
+ }
+
+ getPotentialRookMoves([x, y]) {
+ let moves = super.getPotentialRookMoves([x, y]);
+ if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) {
+ // In a corner of a palace: move along diagonal
+ const step = [[0, 7].includes(x) ? 1 : -1, 4 - y];
+ const oppCol = V.GetOppCol(this.getColor(x, y));
+ for (let i of [1, 2]) {
+ const [xx, yy] = [x + i * step[0], y + i * step[1]];
+ if (this.board[xx][yy] == V.EMPTY)
+ moves.push(this.getBasicMove([x, y], [xx, yy]));
+ else {
+ if (this.getColor(xx, yy) == oppCol)
+ moves.push(this.getBasicMove([x, y], [xx, yy]));
+ break;
+ }
+ }
+ }
+ else if (y == 4 && [1, 8].includes(x)) {
+ // In the middle of a palace: 4 one-diagonal-step to check
+ Array.prototype.push.apply(
+ moves,
+ super.getSlideNJumpMoves([x, y],
+ ChessRules.steps[V.BISHOP],
+ "oneStep")
+ );
+ }
+ return moves;
+ }
+
+ // NOTE: (mostly) duplicated from Shako (TODO?)
+ getPotentialCannonMoves([x, y]) {
+ const oppCol = V.GetOppCol(this.turn);
+ let moves = [];
+ // Look in every direction until an obstacle (to jump) is met
+ for (const step of V.steps[V.ROOK]) {
+ let i = x + step[0];
+ let j = y + step[1];
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ i += step[0];
+ j += step[1];
+ }
+ // Then, search for an enemy (if jumped piece isn't a cannon)
+ if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) {
+ i += step[0];
+ j += step[1];
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ moves.push(this.getBasicMove([x, y], [i, j]));
+ i += step[0];
+ j += step[1];
+ }
+ if (
+ V.OnBoard(i, j) &&
+ this.getColor(i, j) == oppCol &&
+ this.getPiece(i, j) != V.CANNON
+ ) {
+ moves.push(this.getBasicMove([x, y], [i, j]));
+ }
+ }
+ }
+ if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) {
+ // In a corner of a palace: hop over next obstacle if possible
+ const step = [[0, 7].includes(x) ? 1 : -1, 4 - y];
+ const [x1, y1] = [x + step[0], y + step[1]];
+ const [x2, y2] = [x + 2 * step[0], y + 2 * step[1]];
+ if (
+ this.board[x1][y1] != V.EMPTY &&
+ this.getPiece(x1, y1) != V.CANNON &&
+ (
+ this.board[x2][y2] == V.EMPTY ||
+ (
+ this.getColor(x2, y2) == oppCol &&
+ this.getPiece(x2, y2) != V.CANNON
+ )
+ )
+ ) {
+ moves.push(this.getBasicMove([x, y], [x2, y2]));
+ }
+ }
+ return moves;
+ }
+
+ // (King) Never attacked by advisor, since it stays in the palace
+ isAttacked(sq, color) {
+ return (
+ this.isAttackedByPawn(sq, color) ||
+ this.isAttackedByRook(sq, color) ||
+ this.isAttackedByKnight(sq, color) ||
+ this.isAttackedByElephant(sq, color) ||
+ this.isAttackedByCannon(sq, color)
+ );
+ }
+
+ onPalaceDiagonal([x, y]) {
+ return (
+ (y == 4 && [1, 8].includes(x)) ||
+ ([3, 5].includes(y) && [0, 2, 7, 9].includes(x))
+ );
+ }
+
+ isAttackedByPawn([x, y], color) {
+ const shiftX = (color == 'w' ? 1 : -1); //shift from king
+ if (super.isAttackedBySlideNJump(
+ [x, y], color, V.PAWN, [[shiftX, 0], [0, 1], [0, -1]], "oneStep")
+ ) {
+ return true;
+ }
+ if (this.onPalaceDiagonal([x, y])) {
+ for (let yStep of [-1, 1]) {
+ const [xx, yy] = [x + shiftX, y + yStep];
+ if (
+ this.onPalaceDiagonal([xx,yy]) &&
+ this.board[xx][yy] != V.EMPTY &&
+ this.getColor(xx, yy) == color &&
+ this.getPiece(xx, yy) == V.PAWN
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ knightStepsFromBishopStep(step) {
+ return [ [2*step[0], step[1]], [step[0], 2*step[1]] ];
+ }
+
+ isAttackedByKnight([x, y], color) {
+ // Check bishop steps: if empty, look continuation knight step
+ let steps = [];
+ for (let s of ChessRules.steps[V.BISHOP]) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] == V.EMPTY
+ ) {
+ Array.prototype.push.apply(steps, this.knightStepsFromBishopStep(s));
+ }
+ }
+ return (
+ super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, "oneStep")
+ );
+ }
+
+ elephantStepsFromBishopStep(step) {
+ return [ [3*step[0], 2*step[1]], [2*step[0], 3*step[1]] ];
+ }
+
+ isAttackedByElephant([x, y], color) {
+ // Check bishop steps: if empty, look continuation elephant step
+ let steps = [];
+ for (let s of ChessRules.steps[V.BISHOP]) {
+ const [i1, j1] = [x + s[0], y + s[1]];
+ const [i2, j2] = [x + 2*s[0], y + 2*s[1]];
+ if (
+ V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY &&
+ V.OnBoard(i1, j1) && this.board[i1][j1] == V.EMPTY
+ ) {
+ Array.prototype.push.apply(steps, this.elephantStepsFromBishopStep(s));
+ }
+ }
+ return (
+ super.isAttackedBySlideNJump([x, y], color, V.ELEPHANT, steps, "oneStep")
+ );
+ }
+
+ isAttackedByRook([x, y], color) {
+ if (super.isAttackedByRook([x, y], color)) return true;
+ // Also check diagonals, if inside palace
+ if (this.onPalaceDiagonal([x, y])) {
+ // TODO: next scan is clearly suboptimal
+ for (let s of ChessRules.steps[V.BISHOP]) {
+ for (let i of [1, 2]) {
+ const [xx, yy] = [x + i * s[0], y + i * s[1]];
+ if (
+ V.OnBoard(xx, yy) &&
+ this.onPalaceDiagonal([xx, yy])
+ ) {
+ if (this.board[xx][yy] != V.EMPTY) {
+ if (
+ this.getColor(xx, yy) == color &&
+ this.getPiece(xx, yy) == V.ROOK
+ ) {
+ return true;
+ }
+ break;
+ }
+ }
+ else continue;
+ }
+ }
+ }
+ return false;
+ }
+
+ // NOTE: (mostly) duplicated from Shako (TODO?)
+ isAttackedByCannon([x, y], color) {
+ // Reversed process: is there an obstacle in line,
+ // and a cannon next in the same line?
+ for (const step of V.steps[V.ROOK]) {
+ let [i, j] = [x+step[0], y+step[1]];
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ i += step[0];
+ j += step[1];
+ }
+ if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) {
+ // Keep looking in this direction
+ i += step[0];
+ j += step[1];
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ i += step[0];
+ j += step[1];
+ }
+ if (
+ V.OnBoard(i, j) &&
+ this.getPiece(i, j) == V.CANNON &&
+ this.getColor(i, j) == color
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ getCurrentScore() {
+ if ([this.bikjangFlag, this.passFlag].includes(2)) return "1/2";
+ const color = this.turn;
+ // super.atLeastOneMove() does not consider passing (OK)
+ if (this.underCheck(color) && !super.atLeastOneMove())
+ return (color == "w" ? "0-1" : "1-0");
+ return "*";
+ }
+
+ static get VALUES() {
+ return {
+ p: 2,
+ r: 13,
+ n: 5,
+ e: 3,
+ a: 3,
+ c: 7,
+ k: 1000
+ };
+ }
+
+ static get SEARCH_DEPTH() {
+ return 2;
+ }
+
+ static GenRandInitFen() {
+ // No randomization here (but initial setup choice)
+ return (
+ "rnea1aenr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNEA1AENR w 0 00"
+ );
+ }
+
+ play(move) {
+ move.subTurn = this.subTurn; //much easier
+ if (this.movesCount >= 2 || this.subTurn == 2 || move.vanish.length == 0) {
+ this.turn = V.GetOppCol(this.turn);
+ this.subTurn = 1;
+ this.movesCount++;
+ }
+ else this.subTurn = 2;
+ move.flags = JSON.stringify(this.aggregateFlags());
+ V.PlayOnBoard(this.board, move);
+ this.postPlay(move);
+ }
+
+ postPlay(move) {
+ if (move.vanish.length > 0) super.postPlay(move);
+ else if (this.movesCount > 2) this.passFlag++;
+ // Update bikjang flag
+ if (this.kingPos['w'][1] == this.kingPos['b'][1]) {
+ const y = this.kingPos['w'][1];
+ let bikjang = true;
+ for (let x = this.kingPos['b'][0] + 1; x < this.kingPos['w'][0]; x++) {
+ if (this.board[x][y] != V.EMPTY) {
+ bikjang = false;
+ break;
+ }
+ }
+ if (bikjang) this.bikjangFlag++;
+ else this.bikjangFlag = 0;
+ }
+ else this.bikjangFlag = 0;
+ }
+
+ undo(move) {
+ this.disaggregateFlags(JSON.parse(move.flags));
+ V.UndoOnBoard(this.board, move);
+ this.postUndo(move);
+ if (this.movesCount >= 2 || this.subTurn == 1 || move.vanish.length == 0) {
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount--;
+ }
+ this.subTurn = move.subTurn;
+ }
+
+ postUndo(move) {
+ if (move.vanish.length > 0) super.postUndo(move);
+ }
+
+ getNotation(move) {
+ if (move.vanish.length == 0) return "pass";
+ if (move.appear.length == 2) return "S"; //"swap"
+ let notation = super.getNotation(move);
+ if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN)
+ notation = "P" + notation.substr(1);
+ return notation;
+ }
+
+};
--- /dev/null
+import { MakrukRules } from "@/variants/Makruk";
+
+export class MakpongRules extends MakrukRules {
+
+ filterValid(moves) {
+ const color = this.turn;
+ if (this.underCheck(color)) {
+ // Filter out all moves involving king
+ return super.filterValid(moves.filter(m => m.vanish[0].p != V.KING));
+ }
+ return super.filterValid(moves);
+ }
+
+};
let i = x + step[0];
let j = y + step[1];
while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
- moves.push(this.getBasicMove([x, y], [i, j]));
i += step[0];
j += step[1];
}
i += step[0];
j += step[1];
while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ moves.push(this.getBasicMove([x, y], [i, j]));
i += step[0];
j += step[1];
}
export class XiangqiRules extends ChessRules {
+ // NOTE (TODO?) scanKings() could be more efficient (in Jangqi too)
+
static get Monochrome() {
return true;
}
return super.getSlideNJumpMoves([x, y], steps, "oneStep");
}
- insidePalace(x, y, c) {
- return (
- (y >= 3 && y <= 5) &&
- (
- (c == 'w' && x >= 7) ||
- (c == 'b' && x <= 2)
- )
- );
- }
-
getPotentialAdvisorMoves([x, y]) {
// Diagonal steps inside palace
- let steps = [];
const c = this.getColor(x, y);
- for (let s of ChessRules.steps[V.BISHOP]) {
- if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s);
+ if (
+ y != 4 ||
+ (c == 'w' && x != V.size.x - 2) ||
+ (c == 'b' && x != 1)
+ ) {
+ // In a corner: only one step available
+ let step = null;
+ const direction = (c == 'w' ? -1 : 1);
+ if ((c == 'w' && x == V.size.x - 1) || (c == 'b' && x == 0)) {
+ // On first line
+ if (y == 3) step = [direction, 1];
+ else step = [direction, -1];
+ }
+ else {
+ // On third line
+ if (y == 3) step = [-direction, 1];
+ else step = [-direction, -1];
+ }
+ return super.getSlideNJumpMoves([x, y], [step], "oneStep");
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ // In the middle of the palace:
+ return (
+ super.getSlideNJumpMoves([x, y], ChessRules.steps[V.BISHOP], "oneStep")
+ );
}
getPotentialKingMoves([x, y]) {
// Orthogonal steps inside palace
- let steps = [];
const c = this.getColor(x, y);
- for (let s of ChessRules.steps[V.ROOK]) {
- if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s);
+ if (
+ y != 4 ||
+ (c == 'w' && x != V.size.x - 2) ||
+ (c == 'b' && x != 1)
+ ) {
+ // On the edge: only two steps available
+ let steps = [];
+ if (x < (c == 'w' ? V.size.x - 1 : 2)) steps.push([1, 0]);
+ if (x > (c == 'w' ? V.size.x - 3 : 0)) steps.push([-1, 0]);
+ if (y > 3) steps.push([0, -1]);
+ if (y < 5) steps.push([0, 1]);
+ return super.getSlideNJumpMoves([x, y], steps, "oneStep");
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ // In the middle of the palace:
+ return (
+ super.getSlideNJumpMoves([x, y], ChessRules.steps[V.ROOK], "oneStep")
+ );
}
// NOTE: duplicated from Shako (TODO?)
return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0";
}
+ getNotation(move) {
+ let notation = super.getNotation(move);
+ if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN)
+ notation = "P" + substr(notation, 1);
+ return notation;
+ }
+
};
('Grasshopper', 'Long jumps over pieces'),
('Gridolina', 'Jump the borders'),
('Hamilton', 'Walk on a graph'),
+ ('Hoppelpoppel', 'Knibis and Bisknis'),
('Horde', 'A pawns cloud'),
('Interweave', 'Interweaved colorbound teams'),
+ ('Jangqi', 'Korean Chess'),
('Kinglet', 'Protect your pawns'),
('Knightmate', 'Mate the knight'),
('Knightpawns', 'Knight versus pawns'),
('Madhouse', 'Rearrange enemy pieces'),
('Madrasi', 'Paralyzed pieces'),
('Magnetic', 'Laws of attraction'),
- ('Makruk', 'Thai Chess'),
+ ('Makpong', 'Thai Chess (v2)'),
+ ('Makruk', 'Thai Chess (v1)'),
('Maxima', 'Occupy the enemy palace'),
('Minishogi', 'Shogi 5 x 5'),
('Minixiangqi', 'Xiangqi 7 x 7'),