From 059f0aa261609b7421f8576a53d93a764049da5f Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 5 Jan 2021 16:41:15 +0100 Subject: [PATCH] Improve Paco-Sako rules. Draft Isardam --- TODO | 7 +- client/public/images/pieces/Pacosako/w_.png | 1 + client/src/base_rules.js | 3 +- client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + client/src/translations/rules/Isardam/en.pug | 21 ++ client/src/translations/rules/Isardam/es.pug | 22 ++ client/src/translations/rules/Isardam/fr.pug | 22 ++ client/src/translations/rules/Pacosako/en.pug | 42 ++- client/src/translations/rules/Pacosako/es.pug | 39 ++- client/src/translations/rules/Pacosako/fr.pug | 46 ++- client/src/translations/variants/en.pug | 1 + client/src/translations/variants/es.pug | 1 + client/src/translations/variants/fr.pug | 1 + client/src/variants/Isardam.js | 76 +++++ client/src/variants/Pacosako.js | 274 +++++++++++++++--- server/db/populate.sql | 1 + server/db/queryGameStat.sql | 4 + 19 files changed, 483 insertions(+), 81 deletions(-) create mode 100644 client/public/images/pieces/Pacosako/w_.png create mode 100644 client/src/translations/rules/Isardam/en.pug create mode 100644 client/src/translations/rules/Isardam/es.pug create mode 100644 client/src/translations/rules/Isardam/fr.pug create mode 100644 client/src/variants/Isardam.js create mode 100644 server/db/queryGameStat.sql diff --git a/TODO b/TODO index c3bde3fa..feb1425a 100644 --- a/TODO +++ b/TODO @@ -6,8 +6,8 @@ NEW VARIANTS: https://www.pychess.org/variant/manchu https://www.pychess.org/variant/dobutsu 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 +https://musketeerchess.net/games/cerebral/rules/rules.php : from Titan, should be easy +https://www.pychess.org/variant/shogun https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/ https://www.chessvariants.com/other.dir/fugue.html https://www.chessvariants.com/rules/spartan-chess @@ -16,8 +16,6 @@ https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html https://brainking.com/en/GameRules?tp=47&fwa=ArchivedGame!g=8204276$i=1 related to: Crown Chess: place all units on move 1 (similar to Sittuyin, more freely --> in own half-board, possible pawns on 1st rank) -https://musketeerchess.net/games/cerebral/rules/rules.php ---> MusketeerChess : from Titan, should be easy http://history.chess.free.fr/rollerball.htm Squatter Chess: safe on last rank = win Companion Chess : pieces of same nature don't attack each others @@ -34,7 +32,6 @@ https://boardgamegeek.com/boardgame/18661/alapo Alapo is a strategy game. Each player owns twelve abstract pieces, two each of six different kinds. Round pieces move in any of the eight directions on the 6 by 6 board; square pieces move only orthogonally and triangular pieces only diagonally. Large pieces move any distance, small pieces only one field per turn. Opponent pieces can be eliminated by moving onto their position. The goal is to reach the opponent's base line with one of your pieces without the opponent being able to eliminate your piece in his/her next move. https://www.chessvariants.com/incinf.dir/bario.html -Paco-Sako : https://www.youtube.com/watch?v=tEv8TVqyZdQ + PDF rules https://discord.com/channels/686736099959504907/687076968046395410/735678637432635473 + Amazon Chess Maybe: https://www.chessvariants.com/diffmove.dir/asymmetric.html diff --git a/client/public/images/pieces/Pacosako/w_.png b/client/public/images/pieces/Pacosako/w_.png new file mode 100644 index 00000000..6894f686 --- /dev/null +++ b/client/public/images/pieces/Pacosako/w_.png @@ -0,0 +1 @@ +#$# git-fat 0dc17361d21d3332394822b39c515aec26b1d092 6018 diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 69549525..5f47904f 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -892,7 +892,6 @@ export const ChessRules = class ChessRules { // Castling ? const oppCol = V.GetOppCol(c); let moves = []; - let i = 0; // King, then rook: finalSquares = finalSquares || [ [2, 3], [V.size.y - 2, V.size.y - 3] ]; const castlingKing = this.board[x][y].charAt(1); @@ -919,7 +918,7 @@ export const ChessRules = class ChessRules { // Nothing on the path of the king ? (and no checks) const finDist = finalSquares[castleSide][0] - y; let step = finDist / Math.max(1, Math.abs(finDist)); - i = y; + let i = y; do { if ( (!castleInCheck && this.isAttacked([x, i], oppCol)) || diff --git a/client/src/translations/en.js b/client/src/translations/en.js index be4811e5..dad1e8c0 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -241,6 +241,7 @@ export const translations = { "Move under cover": "Move under cover", "Moving forward": "Moving forward", "Neverending rows": "Neverending rows", + "No paralyzed pieces": "No paralyzed pieces", "No-check mode": "No-check mode", "Non-conformism and utopia": "Non-conformism and utopia", "Occupy the enemy palace": "Occupy the enemy palace", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 017f45c0..c1658bc0 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -241,6 +241,7 @@ export const translations = { "Move under cover": "Ir bajo cubierta", "Moving forward": "Ir adelante", "Neverending rows": "Filas interminables", + "No paralyzed pieces": "No piezas paralizadas", "No-check mode": "Modo sin jaque", "Non-conformism and utopia": "No-conformismo y utopía", "Occupy the enemy palace": "Ocupar el palacio enemigo", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 6b1f25c6..630437d3 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -241,6 +241,7 @@ export const translations = { "Move under cover": "Avancez à couvert", "Moving forward": "Aller de l'avant", "Neverending rows": "Rangées sans fin", + "No paralyzed pieces": "Pas de pièces paralysées", "No-check mode": "Mode sans échec", "Non-conformism and utopia": "Non-conformisme et utopie", "Occupy the enemy palace": "Occuper le palais ennemi", diff --git a/client/src/translations/rules/Isardam/en.pug b/client/src/translations/rules/Isardam/en.pug new file mode 100644 index 00000000..67bce217 --- /dev/null +++ b/client/src/translations/rules/Isardam/en.pug @@ -0,0 +1,21 @@ +p.boxed + | Moves leading to a Madrasi-immobilization are forbidden. + +p + | In the + a(href="/#/variants/Madrasi") Madrasi + |  variant, pieces are paralyzed when observing an enemy piece of the + | same nature. In Isardam, moves leading to such situations are forbidden. + +figure.diagram-container + .diagram + | fen:rnbq1rk1/ppp2ppp/3p1n2/2b1p3/2B1P3/2NP1N2/PPP2PPP/R1BQK2R d2,f4,g5,h6: + figcaption The c1 bishop can't go to e3 (mutual attack with Bc5). + +p + | Exception: the king is attacked as usual. That is to say, a piece gives + | check even if capturing the king would lead to an immobilization. + | This corresponds to "type B" as described + a(href="https://echekk.fr/spip.php?page=article&id_article=280") + | on this page + | . diff --git a/client/src/translations/rules/Isardam/es.pug b/client/src/translations/rules/Isardam/es.pug new file mode 100644 index 00000000..feed4c5c --- /dev/null +++ b/client/src/translations/rules/Isardam/es.pug @@ -0,0 +1,22 @@ +p.boxed + | Están prohibidos las jugadas que provoquen una inmovilización Madrasi. + +p + | En la variante + a(href="/#/variants/Madrasi") Madrasi + | , las piezas se paralizan cuando observan una pieza enemiga de misma + | naturaleza. En Isardam, los movimientos que llevaron a tales situaciones + | son prohibidos. + +figure.diagram-container + .diagram + | fen:rnbq1rk1/ppp2ppp/3p1n2/2b1p3/2B1P3/2NP1N2/PPP2PPP/R1BQK2R d2,f4,g5,h6: + figcaption El alfil c1 no puede ir a e3 (ataque mutuo con Bc5). + +p + | Excepción: el rey es atacado como de costumbre. Es decir, una pieza + | da jaque incluso si la captura del rey lleva a la inmovilización. + | Esto corresponde al "tipo B" descrito + a(href="https://echekk.fr/spip.php?page=article&id_article=280") + | en esta página + | . diff --git a/client/src/translations/rules/Isardam/fr.pug b/client/src/translations/rules/Isardam/fr.pug new file mode 100644 index 00000000..5782aeef --- /dev/null +++ b/client/src/translations/rules/Isardam/fr.pug @@ -0,0 +1,22 @@ +p.boxed + | Les coups menant à une immobilisation Madrasi sont interdits. + +p + | Dans la variante + a(href="/#/variants/Madrasi") Madrasi + | , les pièces sont paralysées quand elles observent une pièce ennemie de + | même nature. À Isardam, les coups menant à de telles situations + | sont prohibés. + +figure.diagram-container + .diagram + | fen:rnbq1rk1/ppp2ppp/3p1n2/2b1p3/2B1P3/2NP1N2/PPP2PPP/R1BQK2R d2,f4,g5,h6: + figcaption Le fou c1 ne peut aller en e3 (attaque mutuelle avec Bc5). + +p + | Exception : le roi est attaqué comme d'habitude. C'est-à-dire qu'une pièce + | donne échec même si la capture du roi mènerait à une immobilisation. + | Cela correspond au "type B" décrit + a(href="https://echekk.fr/spip.php?page=article&id_article=280") + | sur cette page + | . diff --git a/client/src/translations/rules/Pacosako/en.pug b/client/src/translations/rules/Pacosako/en.pug index 088b9ff3..51383e06 100644 --- a/client/src/translations/rules/Pacosako/en.pug +++ b/client/src/translations/rules/Pacosako/en.pug @@ -70,24 +70,40 @@ p. p Promotion occur when any pawn (in union or not) reaches its final rank. p. - Attacks on the king are ignored in this implementation: you can run - or remain into "check". So, castling conditions are quite permissive. - Also, if you form an union with your king but end dancing with the - other king on the other end of the chain, the game is a draw. - span.warning This does not follow (at all) the official rules. + Castling is forbidden if a direct or "chained" attack exists on any square + in the king's path. However, direct or chained attacks on the king are + otherwise ignored: you can run or remain into "check". figure.diagram-container - .diagram.diag12 - | fen:rnbq1r2/1ppppp1k/p6p/4P1OP/1PPP3c/3B4/P2V1PP1/R2QK1N1: - .diagram.diag22 - | fen:rnbq1r2/1ppppp1k/p7/4P1dP/1PPPn2c/3B4/P2V1PP1/R2QK1N1: + .diagram + | fen:r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/2dDP3/P2B1PP1/R3K1NR: figcaption. - Left: Bd3(+) can be covered by Right: h6xg5 (releasing the knight), Ne4. + Black cannot castle because of the chain Bxc3, c3xb5, b5xb4, b4xf8 + +h3 Non-official rules p. - Canceling an union move is forbidden. For example if a bishop is - dancing with a queen, and makes the move e5 to g3, the other player cannot - move it back to e5 just after. This is also non-official. + I added some small changes, globally to complicate defense. + These are not in the official rules. + +ul + li. + Canceling an union move is forbidden. For example if a bishop is + dancing with a queen, and makes the move e5 to g3, the other player + cannot move it back to e5 just after. + li. + Pawns can advance two squares only if they never moved (by themselves + or as part of an union). + li. + If you form an union with your king but end dancing with the + other king on the other end of the chain, the game is a draw. + +figure.diagram-container + .diagram.diag12 + | fen:r2qk2r/pbpe1pp1/np5p/2b1p3/4P1t1/2N3K1/PPPP1PPP/R1B3NR: + .diagram.diag22 + | fen:r2qX2r/pbpf1pp1/np5p/2b1p3/4P1u1/2N5/PPPP1PPP/R1B3NR: + figcaption Before and after Kxg4, g4xd7, d7xe8 1/2 h3 More information diff --git a/client/src/translations/rules/Pacosako/es.pug b/client/src/translations/rules/Pacosako/es.pug index 787dc48f..618f38d4 100644 --- a/client/src/translations/rules/Pacosako/es.pug +++ b/client/src/translations/rules/Pacosako/es.pug @@ -75,23 +75,38 @@ p. Los ataques al rey se ignoran en esta implementación: puede ir o permanecer en "jaque". Por lo tanto, las condiciones de enroque son encontrar relajado. - Si formas una unión con tu rey pero terminas bailando con - el rey oponente en el otro extremo de la cadena, el juego se empata. - span.warning Esto no sigue las reglas oficiales (en absoluto). figure.diagram-container - .diagram.diag12 - | fen:rnbq1r2/1ppppp1k/p6p/4P1OP/1PPP3c/3B4/P2V1PP1/R2QK1N1: - .diagram.diag22 - | fen:rnbq1r2/1ppppp1k/p7/4P1dP/1PPPn2c/3B4/P2V1PP1/R2QK1N1: + .diagram + | fen:r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/2dDP3/P2B1PP1/R3K1NR: figcaption. - Izquierda: Bd3(+) puede ser bloqueado por - Derecha: h6xg5 (liberando al caballo), Ne4. + Las negras no pueden enroquar a causa de la cadena + Bxc3, c3xb5, b5xb4, b4xf8 + +h3 Reglas no oficiales p. - Está prohibido cancelar una jugada de unión. Por ejemplo, si un alfil - baila con una reina y hace un movimiento de e5 a g3, el otro jugador no - puede lo reemplace en e5 inmediatamente después. Esto tampoco es oficial. + Hice algunas adiciones, en general para complicar la defensa. + No forman parte de las reglas oficiales. + +ul + li. + Está prohibido cancelar una jugada de unión. Por ejemplo, si un alfil + baila con una reina y hace un movimiento de e5 a g3, el otro jugador no + puede lo reemplace en e5 inmediatamente después. + li. + Los peones pueden avanzar dos espacios solo si nunca han + movido (por sí mismos o como miembro de una unión). + li. + Si formas una unión con tu rey pero terminas bailando con + el rey oponente en el otro extremo de la cadena, el juego se empata. + +figure.diagram-container + .diagram.diag12 + | fen:r2qk2r/pbpe1pp1/np5p/2b1p3/4P1t1/2N3K1/PPPP1PPP/R1B3NR: + .diagram.diag22 + | fen:r2qX2r/pbpf1pp1/np5p/2b1p3/4P1u1/2N5/PPPP1PPP/R1B3NR: + figcaption Antes y despues Kxg4, g4xd7, d7xe8 1/2 h3 Más información diff --git a/client/src/translations/rules/Pacosako/fr.pug b/client/src/translations/rules/Pacosako/fr.pug index 553684cf..332ca898 100644 --- a/client/src/translations/rules/Pacosako/fr.pug +++ b/client/src/translations/rules/Pacosako/fr.pug @@ -71,26 +71,42 @@ p. atteint sa dernière rangée. p. - Les attaques sur le roi sont ignorées dans cette implémentation : vous - pouvez aller ou rester en "échec". Ainsi, les conditions du roque se - retrouvent assouplie. - Si vous formez une union avec votre roi mais terminez par danser avec - le roi adverse à l'autre bout de la chaîne, la partie est nulle. - span.warning Cela ne suit pas (du tout) les règles officielles. + Le roque est interdit si une attaque directe ou "chaînée" existe sur + n'importe quelle case sur le chemin du roi. Cependant, les attaques sur + le roi (directes ou chaînées) sont ignorées le reste du temps : vous + pouvez vous mettre ou rester en "échec". figure.diagram-container - .diagram.diag12 - | fen:rnbq1r2/1ppppp1k/p6p/4P1OP/1PPP3c/3B4/P2V1PP1/R2QK1N1: - .diagram.diag22 - | fen:rnbq1r2/1ppppp1k/p7/4P1dP/1PPPn2c/3B4/P2V1PP1/R2QK1N1: + .diagram + | fen:r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/2dDP3/P2B1PP1/R3K1NR: figcaption. - Gauche : Bd3(+) peut être paré par - Droite : h6xg5 (libérant le cavalier), Ne4. + Les noirs ne peuvent pas roquer à cause de la chaîne + Bxc3, c3xb5, b5xb4, b4xf8 + +h3 Règles non-officielles p. - Annuler un coup d'union est interdit. Par exemple, si un fou danse avec - une dame, et effectue un déplacement de e5 en g3, l'autre joueur ne peut - pas la replacer en e5 immédiatement après. Ceci est également non-officiel. + J'ai effectué quelques ajouts, globalement pour compliquer la défense. + Ils ne font pas partie des règles officielles. + +ul + li. + Annuler un coup d'union est interdit. Par exemple, si un fou danse avec + une dame, et effectue un déplacement de e5 en g3, l'autre joueur ne peut + pas la replacer en e5 immédiatement après. + li. + Les pions peuvent avancer de deux cases seulement s'ils n'ont jamais + bougé (par eux-mêmes ou comme membre d'une union). + li. + Si vous formez une union avec votre roi mais terminez par danser avec + le roi adverse à l'autre bout de la chaîne, la partie est nulle. + +figure.diagram-container + .diagram.diag12 + | fen:r2qk2r/pbpe1pp1/np5p/2b1p3/4P1t1/2N3K1/PPPP1PPP/R1B3NR: + .diagram.diag22 + | fen:r2qX2r/pbpf1pp1/np5p/2b1p3/4P1u1/2N5/PPPP1PPP/R1B3NR: + figcaption Avant et après Kxg4, g4xd7, d7xe8 1/2 h3 Plus d'information diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 9c68b678..fad590cf 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -399,6 +399,7 @@ p. "Freecapture", "Gridolina", "Hamilton", + "Isardam", "Magnetic", "Pacosako", "Parachute", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 0f7ac86b..2b30fc32 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -410,6 +410,7 @@ p. "Freecapture", "Gridolina", "Hamilton", + "Isardam", "Magnetic", "Pacosako", "Parachute", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 53fcc16f..876fa496 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -409,6 +409,7 @@ p. "Freecapture", "Gridolina", "Hamilton", + "Isardam", "Magnetic", "Pacosako", "Parachute", diff --git a/client/src/variants/Isardam.js b/client/src/variants/Isardam.js new file mode 100644 index 00000000..4ca9a856 --- /dev/null +++ b/client/src/variants/Isardam.js @@ -0,0 +1,76 @@ +import { ChessRules } from "@/base_rules"; + +export class IsardamRules extends ChessRules { + + isImmobilized(sq) { + const oppCol = V.GetOppCol(this.getColor(sq[0], sq[1])); + const piece = this.getPiece(sq[0], sq[1]); + let steps = []; + switch (piece) { + // NOTE: cannot use super.isAttackedByXXX + // because it would call the redefined isAttackedBySlideNJump + // => Infinite recursive calls. + case V.PAWN: { + const forward = (oppCol == 'w' ? 1 : -1); + steps = [[forward, 1], [forward, -1]]; + break; + } + case V.ROOK: + case V.BISHOP: + case V.KNIGHT: + steps = V.steps[piece]; + break; + case V.KING: + case V.QUEEN: + steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + break; + } + return super.isAttackedBySlideNJump( + sq, oppCol, piece, steps, [V.KING, V.PAWN, V.KNIGHT].includes(piece)) + } + + getPotentialMovesFrom([x, y]) { + return ( + super.getPotentialMovesFrom([x, y]).filter(m => { + let res = true; + this.play(m); + if (this.isImmobilized([m.end.x, m.end.y])) res = false; + else { + // Check discovered attacks + for (let step of V.steps[V.BISHOP].concat(V.steps[V.ROOK])) { + let allowedPieces = [V.QUEEN]; + if (step[0] == 0 || step[1] == 0) allowedPieces.push(V.ROOK); + else allowedPieces.push(V.BISHOP); + let [i, j] = [m.start.x + step[0], m.start.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)) { + const meet = { c: this.getColor(i, j), p: this.getPiece(i, j) }; + if (allowedPieces.includes(meet.p)) { + // Search in the other direction + [i, j] = [m.start.x - step[0], m.start.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) == meet.p && + this.getColor(i, j) != meet.c + ) { + res = false; + break; + } + } + } + } + } + this.undo(m); + return res; + }) + ); + } + +}; diff --git a/client/src/variants/Pacosako.js b/client/src/variants/Pacosako.js index bf847199..5eb96b66 100644 --- a/client/src/variants/Pacosako.js +++ b/client/src/variants/Pacosako.js @@ -29,7 +29,8 @@ export class PacosakoRules extends ChessRules { w: ['b', 'q'], x: ['b', 'k'], y: ['q', 'q'], - z: ['q', 'k'] + z: ['q', 'k'], + '_': ['k', 'k'] }; } @@ -37,16 +38,17 @@ export class PacosakoRules extends ChessRules { 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', '_']; 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']++; - else if (kingSymb.some(s => row[i] == s.toUpperCase())) kings['K']++; + // Not "else if", if two kings dancing together + if (kingSymb.some(s => row[i] == s.toUpperCase())) kings['K']++; } else { const num = parseInt(row[i], 10); @@ -98,15 +100,18 @@ 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', '_']; 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 (kingSymb.includes(c)) - this.kingPos["b"] = [i, k]; - else if (kingSymb.some(s => c == s.toUpperCase())) - this.kingPos["w"] = [i, k]; + 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 + if (kingSymb.some(s => c == s.toUpperCase())) + this.kingPos["w"] = [i, k]; + } else { const num = parseInt(fenRows[i].charAt(j), 10); if (!isNaN(num)) k += num - 1; @@ -141,6 +146,33 @@ export class PacosakoRules extends ChessRules { return true; } + static IsGoodFlags(flags) { + // 4 for castle + 16 for pawns + return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/); + } + + setFlags(fenflags) { + super.setFlags(fenflags); //castleFlags + this.pawnFlags = { + w: [...Array(8)], //pawns can move 2 squares? + b: [...Array(8)] + }; + const flags = fenflags.substr(4); //skip first 4 letters, for castle + for (let c of ["w", "b"]) { + for (let i = 0; i < 8; i++) + this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1"; + } + } + + aggregateFlags() { + return [this.castleFlags, this.pawnFlags]; + } + + disaggregateFlags(flags) { + this.castleFlags = flags[0]; + this.pawnFlags = flags[1]; + } + getUmove(move) { if ( move.vanish.length == 1 && @@ -161,8 +193,17 @@ export class PacosakoRules extends ChessRules { } static GenRandInitFen(randomness) { - // Add empty umove - return ChessRules.GenRandInitFen(randomness) + " -"; + // Add 16 pawns flags + empty umove: + return ChessRules.GenRandInitFen(randomness) + .slice(0, -2) + "1111111111111111 - -"; + } + + getFlagsFen() { + let fen = super.getFlagsFen(); + // Add pawns flags + for (let c of ["w", "b"]) + for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0"); + return fen; } getUmoveFen() { @@ -322,10 +363,28 @@ export class PacosakoRules extends ChessRules { this.board[x][y] = this.turn + piece; } let baseMoves = []; + const c = this.turn; switch (piece || this.getPiece(x, y)) { - case V.PAWN: - baseMoves = this.getPotentialPawnMoves([x, y]); + case V.PAWN: { + const firstRank = (c == 'w' ? 7 : 0); + baseMoves = this.getPotentialPawnMoves([x, y]).filter(m => { + // Skip forbidden 2-squares jumps (except from first rank) + // Also skip unions capturing en-passant (not allowed). + return ( + ( + m.start.x == firstRank || + Math.abs(m.end.x - m.start.x) == 1 || + this.pawnFlags[c][m.start.y] + ) + && + ( + this.board[x][y].charAt(1) == V.PAWN || + m.start.y == m.end.y + ) + ); + }); break; + } case V.ROOK: baseMoves = this.getPotentialRookMoves([x, y]); break; @@ -345,7 +404,6 @@ export class PacosakoRules extends ChessRules { // When a pawn in an union reaches final rank with a non-standard // promotion move: apply promotion anyway let moves = []; - const c = this.turn; const oppCol = V.GetOppCol(c); const oppLastRank = (c == 'w' ? 7 : 0); baseMoves.forEach(m => { @@ -405,23 +463,6 @@ export class PacosakoRules extends ChessRules { return moves; } - getPotentialKingMoves(sq) { - let moves = this.getSlideNJumpMoves( - sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), - "oneStep" - ); - const c = this.turn; - const oppCol = V.GetOppCol(c); - if ( - !this.isAttacked(this.kingPos[c], oppCol) && - this.castleFlags[c].some(v => v < V.size.y) - ) { - moves = moves.concat(super.getCastleMoves(sq, null, true)); - } - return moves; - } - getEpSquare(moveOrSquare) { if (typeof moveOrSquare === "string") { const square = moveOrSquare; @@ -451,6 +492,7 @@ export class PacosakoRules extends ChessRules { !!m1 && !(ChessRules.PIECES.includes(m2.appear[0].p)) && m2.vanish.length == 1 && + !m2.released && m1.start.x == m2.end.x && m1.end.x == m2.start.x && m1.start.y == m2.end.y && @@ -458,10 +500,158 @@ export class PacosakoRules extends ChessRules { ); } - // Do not consider checks for now (TODO) - underCheck() { + getCastleMoves([x, y]) { + const c = this.getColor(x, y); + const oppCol = V.GetOppCol(c); + let moves = []; + const finalSquares = [ [2, 3], [6, 5] ]; + castlingCheck: for (let castleSide = 0; castleSide < 2; castleSide++) { + if (this.castleFlags[c][castleSide] >= 8) continue; + const rookPos = this.castleFlags[c][castleSide]; + + // Nothing on the path of the king ? + const finDist = finalSquares[castleSide][0] - y; + let step = finDist / Math.max(1, Math.abs(finDist)); + let i = y; + let kingSquares = [y]; + do { + if ( + ( + this.board[x][i] != V.EMPTY && + (this.getColor(x, i) != c || ![y, rookPos].includes(i)) + ) + ) { + continue castlingCheck; + } + i += step; + kingSquares.push(i); + } while (i != finalSquares[castleSide][0]); + // No checks on the path of the king ? + if (this.isAttacked(kingSquares, oppCol)) continue castlingCheck; + + // Nothing on the path to the rook? + step = castleSide == 0 ? -1 : 1; + for (i = y + step; i != rookPos; i += step) { + if (this.board[x][i] != V.EMPTY) continue castlingCheck; + } + + // Nothing on final squares, except maybe king and castling rook? + for (i = 0; i < 2; i++) { + if ( + finalSquares[castleSide][i] != rookPos && + this.board[x][finalSquares[castleSide][i]] != V.EMPTY && + ( + finalSquares[castleSide][i] != y || + this.getColor(x, finalSquares[castleSide][i]) != c + ) + ) { + continue castlingCheck; + } + } + + moves.push( + new Move({ + appear: [ + new PiPo({ + x: x, + y: finalSquares[castleSide][0], + p: V.KING, + c: c + }), + new PiPo({ + x: x, + y: finalSquares[castleSide][1], + p: V.ROOK, + c: c + }) + ], + vanish: [ + // King might be initially disguised (Titan...) + new PiPo({ x: x, y: y, p: V.KING, c: c }), + new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) + ], + end: + Math.abs(y - rookPos) <= 2 + ? { x: x, y: rookPos } + : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } + }) + ); + } + + return moves; + } + + isAttacked_aux(files, color, positions, fromSquare, released) { + // "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)) + // Start of an infinite loop: exit + return false; + positions.push(newPos); + const rank = (color == 'w' ? 0 : 7); + const moves = this.getPotentialMovesFrom(fromSquare); + if (moves.some(m => m.end.x == rank && files.includes(m.end.y))) + // Found an attack! + return true; + for (let m of moves) { + if (!!m.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); + this.undo(m); + if (res) return true; + } + } return false; } + + isAttacked(files, color) { + const rank = (color == 'w' ? 0 : 7); + // Since it's too difficult (impossible?) to search from the square itself, + // let's adopt a suboptimal but working strategy: find all attacks. + const c = this.turn; + // Artificial turn change is required: + this.turn = color; + let res = false; + outerLoop: for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + // Attacks must start from a normal piece, not an union. + // Therefore, the following test is correct. + if ( + this.board[i][j] != V.EMPTY && + // Do not start with king (irrelevant, and lead to infinite calls) + [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes( + this.board[i][j].charAt(1)) && + this.board[i][j].charAt(0) == color + ) { + // Try from here. + const moves = this.getPotentialMovesFrom([i, j]); + if (moves.some(m => m.end.x == rank && files.includes(m.end.y))) { + res = true; + break outerLoop; + } + for (let m of moves) { + if (!!m.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); + this.undo(m); + if (res) break outerLoop; + } + } + } + } + } + this.turn = c; + return res; + } + + // Do not consider checks, except to forbid castling getCheckSquares() { return []; } @@ -489,13 +679,29 @@ export class PacosakoRules extends ChessRules { postPlay(move) { if (move.vanish.length == 0) - // A piece released just moved. Cannot be the king. + // A released piece just moved. Cannot be the king. return; const c = move.vanish[0].c; const piece = move.vanish[0].p; if (piece == V.KING) this.kingPos[c] = [move.appear[0].x, move.appear[0].y]; this.updateCastleFlags(move, piece); + if ( + [1, 6].includes(move.start.x) && + move.vanish.length >= 1 && + move.appear.length == 1 + ) { + // 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; + } + } } undo(move) { diff --git a/server/db/populate.sql b/server/db/populate.sql index 7f4a7a3d..216dd66f 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -64,6 +64,7 @@ insert or ignore into Variants (name, description) values ('Hoppelpoppel', 'Knibis and Bisknis'), ('Horde', 'A pawns cloud'), ('Interweave', 'Interweaved colorbound teams'), + ('Isardam', 'No paralyzed pieces'), ('Janggi', 'Korean Chess'), ('Kinglet', 'Protect your pawns'), ('Knightmate', 'Mate the knight'), diff --git a/server/db/queryGameStat.sql b/server/db/queryGameStat.sql new file mode 100644 index 00000000..c1720902 --- /dev/null +++ b/server/db/queryGameStat.sql @@ -0,0 +1,4 @@ +select name,total +from GameStat g + join variants v on g.vid = v.id +where total > 0; -- 2.44.0