From: Benjamin Auder Date: Mon, 18 May 2020 16:29:24 +0000 (+0200) Subject: Add Forward + Doubleorda variants X-Git-Url: https://git.auder.net/images/assets/doc/%7B%7B%20path%28%27mixstore_static_policy%27%29%20%7D%7D?a=commitdiff_plain;h=0b8bd1214d662f6b1964f0807eda114ed1cac3c4;p=vchess.git Add Forward + Doubleorda variants --- diff --git a/TODO b/TODO index d0cca4a4..0698ddfa 100644 --- a/TODO +++ b/TODO @@ -3,9 +3,6 @@ Also: if new live game starts in background, "new game" notify OK but not first On smartphone for Teleport, Chakart, Weiqi and some others: option "confirm moves on touch screen" (=> comme pour corr) + option "confirm moves in corr games"? -https://www.chessvariants.com/diffmove.dir/checkers.html --> "Forward" -in 1974 by Hans Multhopp - Clorange: Clockwork Orange Chess (Fergus Duniho, 1999). https://www.chessvariants.com/other.dir/clockworkorange.html diff --git a/client/public/images/pieces/Forward/bc.svg b/client/public/images/pieces/Forward/bc.svg new file mode 120000 index 00000000..b30a26ad --- /dev/null +++ b/client/public/images/pieces/Forward/bc.svg @@ -0,0 +1 @@ +../Alice/bc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/bl.svg b/client/public/images/pieces/Forward/bl.svg new file mode 120000 index 00000000..a10d9e0a --- /dev/null +++ b/client/public/images/pieces/Forward/bl.svg @@ -0,0 +1 @@ +../Alice/bl.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/bo.svg b/client/public/images/pieces/Forward/bo.svg new file mode 120000 index 00000000..1200186b --- /dev/null +++ b/client/public/images/pieces/Forward/bo.svg @@ -0,0 +1 @@ +../Alice/bo.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/bs.svg b/client/public/images/pieces/Forward/bs.svg new file mode 120000 index 00000000..e8cf23a8 --- /dev/null +++ b/client/public/images/pieces/Forward/bs.svg @@ -0,0 +1 @@ +../Alice/bs.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/bt.svg b/client/public/images/pieces/Forward/bt.svg new file mode 120000 index 00000000..c517549b --- /dev/null +++ b/client/public/images/pieces/Forward/bt.svg @@ -0,0 +1 @@ +../Alice/bt.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/bu.svg b/client/public/images/pieces/Forward/bu.svg new file mode 120000 index 00000000..09e6ea3e --- /dev/null +++ b/client/public/images/pieces/Forward/bu.svg @@ -0,0 +1 @@ +../Alice/bu.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/wc.svg b/client/public/images/pieces/Forward/wc.svg new file mode 120000 index 00000000..d23af91d --- /dev/null +++ b/client/public/images/pieces/Forward/wc.svg @@ -0,0 +1 @@ +../Alice/wc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/wl.svg b/client/public/images/pieces/Forward/wl.svg new file mode 120000 index 00000000..51c1893a --- /dev/null +++ b/client/public/images/pieces/Forward/wl.svg @@ -0,0 +1 @@ +../Alice/wl.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/wo.svg b/client/public/images/pieces/Forward/wo.svg new file mode 120000 index 00000000..4a85712d --- /dev/null +++ b/client/public/images/pieces/Forward/wo.svg @@ -0,0 +1 @@ +../Alice/wo.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/ws.svg b/client/public/images/pieces/Forward/ws.svg new file mode 120000 index 00000000..659b2de0 --- /dev/null +++ b/client/public/images/pieces/Forward/ws.svg @@ -0,0 +1 @@ +../Alice/ws.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/wt.svg b/client/public/images/pieces/Forward/wt.svg new file mode 120000 index 00000000..447fc4fe --- /dev/null +++ b/client/public/images/pieces/Forward/wt.svg @@ -0,0 +1 @@ +../Alice/wt.svg \ No newline at end of file diff --git a/client/public/images/pieces/Forward/wu.svg b/client/public/images/pieces/Forward/wu.svg new file mode 120000 index 00000000..c1403b33 --- /dev/null +++ b/client/public/images/pieces/Forward/wu.svg @@ -0,0 +1 @@ +../Alice/wu.svg \ No newline at end of file diff --git a/client/public/images/pieces/Orda/bf.svg b/client/public/images/pieces/Orda/bf.svg new file mode 100644 index 00000000..01aa1fe5 --- /dev/null +++ b/client/public/images/pieces/Orda/bf.svg @@ -0,0 +1,188 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Orda/wa.svg b/client/public/images/pieces/Orda/wa.svg new file mode 100644 index 00000000..5bb0aa55 --- /dev/null +++ b/client/public/images/pieces/Orda/wa.svg @@ -0,0 +1,139 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Orda/wf.svg b/client/public/images/pieces/Orda/wf.svg new file mode 120000 index 00000000..d5ee68db --- /dev/null +++ b/client/public/images/pieces/Orda/wf.svg @@ -0,0 +1 @@ +../Schess/wh.svg \ No newline at end of file diff --git a/client/public/images/pieces/Orda/wk.svg b/client/public/images/pieces/Orda/wk.svg new file mode 100644 index 00000000..bd3d9488 --- /dev/null +++ b/client/public/images/pieces/Orda/wk.svg @@ -0,0 +1,231 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Orda/wl.svg b/client/public/images/pieces/Orda/wl.svg new file mode 100644 index 00000000..f000df2d --- /dev/null +++ b/client/public/images/pieces/Orda/wl.svg @@ -0,0 +1,138 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Orda/wp.svg b/client/public/images/pieces/Orda/wp.svg new file mode 120000 index 00000000..3a15545c --- /dev/null +++ b/client/public/images/pieces/Orda/wp.svg @@ -0,0 +1 @@ +../wp.svg \ No newline at end of file diff --git a/client/src/base_rules.js b/client/src/base_rules.js index e70b9de8..ee13d5eb 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -766,6 +766,7 @@ export const ChessRules = class ChessRules { const [sizeX, sizeY] = [V.size.x, V.size.y]; const pawnShiftX = V.PawnSpecs.directions[color]; const firstRank = (color == "w" ? sizeX - 1 : 0); + const forward = (color == 'w' ? -1 : 1); // Pawn movements in shiftX direction: const getPawnMoves = (shiftX) => { @@ -773,7 +774,7 @@ export const ChessRules = class ChessRules { // NOTE: next condition is generally true (no pawn on last rank) if (x + shiftX >= 0 && x + shiftX < sizeX) { if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward + // One square forward (or backward) this.addPawnMoves([x, y], [x + shiftX, y], moves, promotions); // Next condition because pawns on 1st rank can generally jump if ( @@ -784,7 +785,10 @@ export const ChessRules = class ChessRules { (color == 'b' && x <= V.PawnSpecs.initShift['b']) ) ) { - if (this.board[x + 2 * shiftX][y] == V.EMPTY) { + if ( + shiftX == forward && + this.board[x + 2 * shiftX][y] == V.EMPTY + ) { // Two squares jump moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); if ( @@ -811,13 +815,13 @@ export const ChessRules = class ChessRules { ); } if ( - V.PawnSpecs.captureBackward && + V.PawnSpecs.captureBackward && shiftX == forward && x - shiftX >= 0 && x - shiftX < V.size.x && this.board[x - shiftX][y + shiftY] != V.EMPTY && this.canTake([x, y], [x - shiftX, y + shiftY]) ) { this.addPawnMoves( - [x, y], [x + shiftX, y + shiftY], + [x, y], [x - shiftX, y + shiftY], moves, promotions ); } diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 0e39df71..78e112cc 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -157,6 +157,7 @@ export const translations = { // Variants boxes: "64 pieces on the board": "64 pieces on the board", + "A Clockwork Orange": "A Clockwork Orange", "A pawns cloud": "A pawns cloud", "A wizard in the corner": "A wizard in the corner", "Absorb powers": "Absorb powers", @@ -210,10 +211,12 @@ export const translations = { "Mate the knight": "Mate the knight", "Meet the Mammoth": "Meet the Mammoth", "Middle battle": "Middle battle", - "Mongolian Horde": "Mongolian Horde", + "Mongolian Horde (v1)": "Mongolian Horde (v1)", + "Mongolian Horde (v2)": "Mongolian Horde (v2)", "Move like a knight (v1)": "Move like a knight (v1)", "Move like a knight (v2)": "Move like a knight (v2)", "Move under cover": "Move under cover", + "Moving forward": "Moving forward", "Neverending rows": "Neverending rows", "No-check mode": "No-check mode", "Non-conformism and utopia": "Non-conformism and utopia", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 4388c5a5..0c7fca06 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -157,6 +157,7 @@ export const translations = { // Variants boxes: "64 pieces on the board": "64 piezas en el tablero", + "A Clockwork Orange": "Naranja Mecánica", "A pawns cloud": "Une nube de peones", "A wizard in the corner": "Un mago en la esquina", "Absorb powers": "Absorber poderes", @@ -210,10 +211,12 @@ export const translations = { "Mate the knight": "Matar el caballo", "Meet the Mammoth": "Conoce al Mamut", "Middle battle": "Batalla media", - "Mongolian Horde": "Horda mongol", + "Mongolian Horde (v1)": "Horda mongol (v1)", + "Mongolian Horde (v2)": "Horda mongol (v2)", "Move like a knight (v1)": "Moverse como un caballo (v1)", "Move like a knight (v2)": "Moverse como un caballo (v2)", "Move under cover": "Ir bajo cubierta", + "Moving forward": "Ir adelante", "Neverending rows": "Filas interminables", "No-check mode": "Modo sin jaque", "Non-conformism and utopia": "No-conformismo y utopía", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 77e94e68..e6b1c480 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -157,6 +157,7 @@ export const translations = { // Variants boxes: "64 pieces on the board": "64 pièces sur l'échiquier", + "A Clockwork Orange": "Orange Mécanique", "A pawns cloud": "Une nuée de pions", "A wizard in the corner": "Un sorcier dans le coin", "Absorb powers": "Absorber les pouvoirs", @@ -210,10 +211,12 @@ export const translations = { "Mate the knight": "Matez le cavalier", "Meet the Mammoth": "Rencontrez le Mammouth", "Middle battle": "Bataille du milieu", - "Mongolian Horde": "Horde mongole", + "Mongolian Horde (v1)": "Horde mongole (v1)", + "Mongolian Horde (v2)": "Horde mongole (v2)", "Move like a knight (v1)": "Bouger comme un cavalier (v1)", "Move like a knight (v2)": "Bouger comme un cavalier (v2)", "Move under cover": "Avancez à couvert", + "Moving forward": "Aller de l'avant", "Neverending rows": "Rangées sans fin", "No-check mode": "Mode sans échec", "Non-conformism and utopia": "Non-conformisme et utopie", diff --git a/client/src/translations/rules/Clorange/en.pug b/client/src/translations/rules/Clorange/en.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Clorange/en.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Clorange/es.pug b/client/src/translations/rules/Clorange/es.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Clorange/es.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Clorange/fr.pug b/client/src/translations/rules/Clorange/fr.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Clorange/fr.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Doubleorda/en.pug b/client/src/translations/rules/Doubleorda/en.pug new file mode 100644 index 00000000..62ef1539 --- /dev/null +++ b/client/src/translations/rules/Doubleorda/en.pug @@ -0,0 +1,23 @@ +p.boxed + | Pieces move generally like a knight, and capture differently. + +figure.diagram-container + .diagram + | fen:lhafkahl/8/pppppppp/8/8/PPPPPPPP/8/LHAFKAHL: + figcaption Deterministic initial position + +p + | Most pieces move and capture as described in + a(href="/#/variants/Orda") Orda + | . They all belong to the Mongolian army: let's say a rebellion occured :) + +p The new piece is a falcon, moving like a queen and capturing like a knight. + +figure.diagram-container + .diagram + | fen:8/1P6/8/3F2p1/3Pp3/2p1p3/8/8 d6,d7,d8,a5,b5,c5,a2,b3,c4,e5,f5,c6,e6,f7,g8,c3,e3: + figcaption Movements of the falcon + +p. + Same as in Orda, win by checkmate or "campmate" when the king reaches + the last rank. diff --git a/client/src/translations/rules/Doubleorda/es.pug b/client/src/translations/rules/Doubleorda/es.pug new file mode 100644 index 00000000..a6141b23 --- /dev/null +++ b/client/src/translations/rules/Doubleorda/es.pug @@ -0,0 +1,27 @@ +p.boxed + | Las piezas generalmente se mueven como un caballo, + | y capturan de manera diferente. + +figure.diagram-container + .diagram + | fen:lhaykahl/8/pppppppp/8/8/PPPPPPPP/8/LHAYKAHL: + figcaption Posición determinista inicial + +p + | Las piezas se mueven y capturan como se describe en + a(href="/#/variants/Orda") Orda + | . Todas pertenecen al ejército mongol: digamos que una rebelión + | tuvo lugar :) + +p. + La nueva pieza es un halcón que se mueve como una dama, + y captura como un caballo. + +figure.diagram-container + .diagram + | fen:8/1P6/8/3F2p1/3Pp3/2p1p3/8/8 d6,d7,d8,a5,b5,c5,a2,b3,c4,e5,f5,c6,e6,f7,g8,c3,e3: + figcaption Movimientos de halcón + +p. + En cuanto a Orda, gana por jaque mate o "campmate" cuando el rey + llega a la última fila. diff --git a/client/src/translations/rules/Doubleorda/fr.pug b/client/src/translations/rules/Doubleorda/fr.pug new file mode 100644 index 00000000..7e257927 --- /dev/null +++ b/client/src/translations/rules/Doubleorda/fr.pug @@ -0,0 +1,27 @@ +p.boxed + | Les pièces se déplacent en général comme un cavalier, + | et capturent différemment. + +figure.diagram-container + .diagram + | fen:lhaykahl/8/pppppppp/8/8/PPPPPPPP/8/LHAYKAHL: + figcaption Position initiale déterministe + +p + | Les pièces se déplacent et capturent comme décrit dans + a(href="/#/variants/Orda") Orda + | . Elles appartiennent toutes à l'armée mongole : disons qu'une rébellion + | a eu lieu :) + +p. + La nouvelle pièce est un faucon, se déplaçant comme une dame + et capturant comme un cavalier. + +figure.diagram-container + .diagram + | fen:8/1P6/8/3F2p1/3Pp3/2p1p3/8/8 d6,d7,d8,a5,b5,c5,a2,b3,c4,e5,f5,c6,e6,f7,g8,c3,e3: + figcaption Déplacements du faucon + +p. + De même que pour Orda, gagnez par échec et mat ou "campmate" quand le roi + atteint la dernière rangée. diff --git a/client/src/translations/rules/Forward/en.pug b/client/src/translations/rules/Forward/en.pug new file mode 100644 index 00000000..223cd3e4 --- /dev/null +++ b/client/src/translations/rules/Forward/en.pug @@ -0,0 +1,27 @@ +p.boxed + | Pieces only move forward, + | until they reach the last rank where they promote. + +p. + Knights and bishops must move strictly forward, as in Draughts game. + Rooks, queen and king can also move laterally, on the same rank. + Pawns move as usual and are promoted into bidirectional pawns, + able to move and capture both ways. + +figure.diagram-container + .diagram.diag12 + | fen:1qnrkrbb/pppppp1p/1n4p1/8/3B1P2/8/PPPPP1PP/RBKQNR1N c5,b6,e5,f6,g7,h8: + .diagram.diag22 + | fen:1qnrkrbC/pppppp1p/1n4p1/8/5P2/8/PPPPP1PP/RBKQNR1N: + figcaption Before and after 1.Bxh8 + +p Promoted pieces appear in yellow (for white) and red (for black). + +h3 Source + +p + a(href="https://www.chessvariants.com/diffmove.dir/checkers.html") + | Checkers Chess + |  on chessvariants.com. + +p Inventor: Hans Multhopp (1974) diff --git a/client/src/translations/rules/Forward/es.pug b/client/src/translations/rules/Forward/es.pug new file mode 100644 index 00000000..1207e9a3 --- /dev/null +++ b/client/src/translations/rules/Forward/es.pug @@ -0,0 +1,32 @@ +p.boxed + | Las piezas siempre avanzan. + | Una vez que se alcanza la última fila, son promovidos. + +p. + Los caballos y los alfiles deben avanzar estrictamente, + como las damas. + Las torres, la dama y el rey también pueden moverse lateralmente, en el + misma fila. + Los peones se mueven como de costumbre y son promovidos a peones + bidireccional, capaz de moverse y capturar en ambas direcciones. + +figure.diagram-container + .diagram.diag12 + | fen:1qnrkrbb/pppppp1p/1n4p1/8/3B1P2/8/PPPPP1PP/RBKQNR1N c5,b6,e5,f6,g7,h8: + .diagram.diag22 + | fen:1qnrkrbC/pppppp1p/1n4p1/8/5P2/8/PPPPP1PP/RBKQNR1N: + figcaption Antes y después 1.Bxh8 + +p. + Las piezas promocionadas aparecen en amarillo (para las blancas) + y en rojo (para las negras). + +h3 Fuente + +p + | La + a(href="https://www.chessvariants.com/diffmove.dir/checkers.html") + | variante Checkers + |  en chessvariants.com. + +p Inventor: Hans Multhopp (1974) diff --git a/client/src/translations/rules/Forward/fr.pug b/client/src/translations/rules/Forward/fr.pug new file mode 100644 index 00000000..09024a90 --- /dev/null +++ b/client/src/translations/rules/Forward/fr.pug @@ -0,0 +1,32 @@ +p.boxed + | Les pièces se déplacent toujours vers l'avant. + | Une fois la dernière rangée atteinte, elles sont promues. + +p. + Les cavaliers et les fous doivent se déplacer strictement en avant, + comme aux Dames. + Les tours, la dame et le roi peuvent aussi évoluer latéralement, sur la + même rangée. + Les pions se déplacent comme d'habitude et sont promus en des pions + bidirectionnels, pouvant se déplacer et capturer dans les deux sens. + +figure.diagram-container + .diagram.diag12 + | fen:1qnrkrbb/pppppp1p/1n4p1/8/3B1P2/8/PPPPP1PP/RBKQNR1N c5,b6,e5,f6,g7,h8: + .diagram.diag22 + | fen:1qnrkrbC/pppppp1p/1n4p1/8/5P2/8/PPPPP1PP/RBKQNR1N: + figcaption Before and after 1.Bxh8 + +p. + Les pièces promues apparaissent en jaune (pour les blancs) + et en rouge (pour les noirs) + +h3 Source + +p + | La + a(href="https://www.chessvariants.com/diffmove.dir/checkers.html") + | variante Checkers + |  sur chessvariants.com. + +p Inventeur : Hans Multhopp (1974) diff --git a/client/src/translations/rules/Rampage/en.pug b/client/src/translations/rules/Rampage/en.pug index a7ddca75..1e2aed20 100644 --- a/client/src/translations/rules/Rampage/en.pug +++ b/client/src/translations/rules/Rampage/en.pug @@ -7,6 +7,7 @@ p. that is to say squares attacked by more friendly pieces than enemy pieces. The king in check may only move as in orthodox chess, but "rampage" moves of other pieces are allowed to cover the check. + Pawns cannot promote with a rampage move. p. Attacks on the same row, file or diagonal are cumulative. So, diff --git a/client/src/translations/rules/Rampage/es.pug b/client/src/translations/rules/Rampage/es.pug index 4fedc77f..6f143825 100644 --- a/client/src/translations/rules/Rampage/es.pug +++ b/client/src/translations/rules/Rampage/es.pug @@ -8,6 +8,7 @@ p. mismo lado que las piezas opuestas. El rey en jaque solo puede hacer movimientos normales, pero las jugadas "rampage" de las otras piezas pueden contrarrestar el jaque. + Los peones no pueden ser promovidos a través de un movimiento "rampage". p. Los ataques a la misma columna, fila o diagonal son acumulativos. Entonces diff --git a/client/src/translations/rules/Rampage/fr.pug b/client/src/translations/rules/Rampage/fr.pug index d0a57f5f..3319a648 100644 --- a/client/src/translations/rules/Rampage/fr.pug +++ b/client/src/translations/rules/Rampage/fr.pug @@ -8,6 +8,7 @@ p. même camp que de pièces adverses. Le roi en échec ne peut effectuer que des coups normaux, mais les coups "rampage" des autres pièces sont autorisés pour parer l'échec. + Les pions ne peuvent pas être promus via un coup "rampage". p. Les attaques sur la même colonne, rangée ou diagonale se cumulent. Ainsi, @@ -19,6 +20,7 @@ figure.diagram-container | fen:k7/p7/2n1r3/8/8/8/4R2P/4Q2K e5: figcaption Pas de coups de repositionnement vers e5. + h3 Source p diff --git a/client/src/variants/Clorange.js b/client/src/variants/Clorange.js new file mode 100644 index 00000000..a9991316 --- /dev/null +++ b/client/src/variants/Clorange.js @@ -0,0 +1,239 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; + +export class ClorangeRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + // TODO: pawns reaching last rank promote normally? Seems better + { promotions: [V.PAWN] } + ); + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // 5) Check reserves + if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{20,20}$/)) + return false; + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { reserve: fenParts[5] } + ); + } + + static GenRandInitFen(randomness) { + // Capturing and non-capturing reserves: + return ChessRules.GenRandInitFen(randomness) + " 00000000000000000000"; + } + + getFen() { + return super.getFen() + " " + this.getReserveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getReserveFen(); + } + + getReserveFen() { + let counts = new Array(10); + for ( + let i = 0; + i < V.PIECES.length - 1; + i++ //-1: no king reserve + ) { + // TODO: adapt + counts[i] = this.reserve["w"][V.PIECES[i]]; + counts[5 + i] = this.reserve["b"][V.PIECES[i]]; + } + return counts.join(""); + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + const fenParsed = V.ParseFen(fen); + // Also init reserves (used by the interface to show landable pieces) + // TODO: adapt + this.reserve = { + w: { + [V.PAWN]: parseInt(fenParsed.reserve[0]), + [V.ROOK]: parseInt(fenParsed.reserve[1]), + [V.KNIGHT]: parseInt(fenParsed.reserve[2]), + [V.BISHOP]: parseInt(fenParsed.reserve[3]), + [V.QUEEN]: parseInt(fenParsed.reserve[4]) + }, + b: { + [V.PAWN]: parseInt(fenParsed.reserve[5]), + [V.ROOK]: parseInt(fenParsed.reserve[6]), + [V.KNIGHT]: parseInt(fenParsed.reserve[7]), + [V.BISHOP]: parseInt(fenParsed.reserve[8]), + [V.QUEEN]: parseInt(fenParsed.reserve[9]) + } + }; + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; + return this.board[i][j].charAt(1); + } + + getReservePpath(index, color) { + return color + V.RESERVE_PIECES[index]; + } + + static get NON_VIOLENT() { + return ['s', 'u', 'o', 'c', 't', 'l']; + } + + // Ordering on reserve pieces + static get RESERVE_PIECES() { + return ChessRules.PIECES.concat(V.NON_VIOLENT); + } + + getReserveMoves([x, y]) { + const color = this.turn; + const p = V.RESERVE_PIECES[y]; + if (this.reserve[color][p] == 0) return []; + let moves = []; + const pawnShift = p == V.PAWN ? 1 : 0; + for (let i = pawnShift; i < V.size.x - pawnShift; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] == V.EMPTY) { + let mv = new Move({ + appear: [ + new PiPo({ + x: i, + y: j, + c: color, + p: p + }) + ], + vanish: [], + start: { x: x, y: y }, //a bit artificial... + end: { x: i, y: j } + }); + moves.push(mv); + } + } + } + return moves; + } + + // TODO: adapt all below: + getPotentialMovesFrom([x, y]) { + if (x >= V.size.x) { + // Reserves, outside of board: x == sizeX(+1) + return this.getReserveMoves([x, y]); + } + // Standard moves + return super.getPotentialMovesFrom([x, y]); + } + + getPotentialPawnMoves([x, y]) { + + let moves = super.getPotentialPawnMoves([x, y]); + // Remove pawns on 8th rank ("fallen"): + const color = this.turn; + const lastRank = (color == "w" ? 0 : V.size.x - 1); + moves.forEach(m => { + if (m.appear[0].x == lastRank) m.appear.pop(); + }); + return moves; + } + + getAllValidMoves() { + let moves = super.getAllPotentialMoves(); + const color = this.turn; + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + moves = moves.concat( + this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) + ); + } + return this.filterValid(moves); + } + + atLeastOneMove() { + if (!super.atLeastOneMove()) { + // Search one reserve move + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + let moves = this.filterValid( + this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) + ); + if (moves.length > 0) return true; + } + return false; + } + return true; + } + + canTake([x1, y1], [x2, y2]) { + // Self-captures allowed, except for the king: + return this.getPiece(x2, y2) != V.KING; + } + + prePlay(move) { + super.prePlay(move); + // Skip castle: + if (move.vanish.length == 2 && move.appear.length == 2) return; + const color = this.turn; + if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; + else if (move.vanish.length == 2 && move.vanish[1].c == color) + // Self-capture + this.reserve[color][move.vanish[1].p]++; + } + + postUndo(move) { + super.postUndo(move); + if (move.vanish.length == 2 && move.appear.length == 2) return; + const color = this.turn; + if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; + else if (move.vanish.length == 2 && move.vanish[1].c == color) + this.reserve[color][move.vanish[1].p]--; + } + + static get SEARCH_DEPTH() { + return 2; + } + + evalPosition() { + let evaluation = super.evalPosition(); + // Add reserves: + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + const p = V.RESERVE_PIECES[i]; + evaluation += this.reserve["w"][p] * V.VALUES[p]; + evaluation -= this.reserve["b"][p] * V.VALUES[p]; + } + return evaluation; + } + + getNotation(move) { + const finalSquare = V.CoordsToSquare(move.end); + if (move.vanish.length > 0) { + if (move.appear.length > 0) { + // Standard move + return super.getNotation(move); + } else { + // Pawn fallen: capturing or not + let res = ""; + if (move.vanish.length == 2) + res += V.CoordToColumn(move.start.y) + "x"; + return res + finalSquare; + } + } + // Rebirth: + const piece = + move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; + return piece + "@" + V.CoordsToSquare(move.end); + } +}; diff --git a/client/src/variants/Doubleorda.js b/client/src/variants/Doubleorda.js new file mode 100644 index 00000000..290ab23d --- /dev/null +++ b/client/src/variants/Doubleorda.js @@ -0,0 +1,148 @@ +import { ChessRules } from "@/base_rules"; +import { OrdaRules } from "@/variants/Orda"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +export class DoubleordaRules extends OrdaRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { promotions: [V.LANCER, V.ARCHER, V.KHESHIG, V.FALCON] } + ); + } + + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + getPpath(b) { + return "Orda/" + b; + } + + static GenRandInitFen(randomness) { + if (randomness == 0) + return "lhafkahl/8/pppppppp/8/8/PPPPPPPP/8/LHAFKAHL w 0 ah -"; + + let pieces = { w: new Array(8), b: new Array(8) }; + // Shuffle pieces on first (and last rank if randomness == 2) + for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w']; + break; + } + + let positions = ArrayFun.range(8); + + let randIndex = 2 * randInt(4); + const bishop1Pos = positions[randIndex]; + let randIndex_tmp = 2 * randInt(4) + 1; + const bishop2Pos = positions[randIndex_tmp]; + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); + + randIndex = randInt(6); + const knight1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(5); + const knight2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + randIndex = randInt(4); + const queenPos = positions[randIndex]; + positions.splice(randIndex, 1); + + const rook1Pos = positions[0]; + const kingPos = positions[1]; + const rook2Pos = positions[2]; + + pieces[c][rook1Pos] = "l"; + pieces[c][knight1Pos] = "h"; + pieces[c][bishop1Pos] = "a"; + pieces[c][queenPos] = "f"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "a"; + pieces[c][knight2Pos] = "h"; + pieces[c][rook2Pos] = "l"; + } + return ( + pieces["b"].join("") + + "/8/pppppppp/8/8/PPPPPPPP/8/" + + pieces["w"].join("").toUpperCase() + + " w 0" + ); + } + + static get FALCON() { + return 'f'; + } + + static get PIECES() { + return [V.LANCER, V.ARCHER, V.KHESHIG, V.FALCON, V.KING]; + } + + getPotentialMovesFrom(sq) { + switch (this.getPiece(sq[0], sq[1])) { + case V.PAWN: + return super.getPotentialPawnMoves(sq); + case V.LANCER: + return super.getPotentialLancerMoves(sq); + case V.ARCHER: + return super.getPotentialArcherMoves(sq); + case V.KHESHIG: + return super.getPotentialKheshigMoves(sq); + case V.FALCON: + return this.getPotentialFalconMoves(sq); + case V.KING: + return super.getPotentialKingMoves(sq) + } + return []; //never reached + } + + getPotentialFalconMoves(sq) { + const onlyMoves = this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + null, + { onlyMove: true } + ); + const onlyTakes = this.getSlideNJumpMoves( + sq, + V.steps[V.KNIGHT], + "oneStep", + { onlyTake: true } + ); + return onlyMoves.concat(onlyTakes); + } + + isAttacked(sq, color) { + return ( + super.isAttackedByPawn(sq, color) || + super.isAttackedByLancer(sq, color) || + super.isAttackedByKheshig(sq, color) || + super.isAttackedByArcher(sq, color) || + this.isAttackedByFalcon(sq, color) || + super.isAttackedByKing(sq, color) + ); + } + + isAttackedByFalcon(sq, color) { + return this.isAttackedBySlideNJump( + sq, color, V.FALCON, V.steps[V.KNIGHT], "oneStep"); + } + + static get VALUES() { + return { + p: 1, + f: 7, + a: 4, + h: 7, + l: 4, + k: 1000 + }; + } +}; diff --git a/client/src/variants/Forward.js b/client/src/variants/Forward.js new file mode 100644 index 00000000..5c5b641d --- /dev/null +++ b/client/src/variants/Forward.js @@ -0,0 +1,143 @@ +import { ChessRules } from "@/base_rules"; + +export class ForwardRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { + bidirectional: true, + captureBackward: true, + promotions: [V.PAWN] + } + ); + } + + static get PROMOTED() { + return ['s', 'u', 'o', 'c', 't', 'l']; + } + + static get PIECES() { + return ChessRules.PIECES.concat(V.PROMOTED); + } + + getPpath(b) { + return (V.PROMOTED.includes(b[1]) ? "Forward/" : "") + b; + } + + scanKings(fen) { + this.INIT_COL_KING = { w: -1, b: -1 }; + // Squares of white and black king: + this.kingPos = { w: [-1, -1], b: [-1, -1] }; + const fenRows = V.ParseFen(fen).position.split("/"); + const startRow = { 'w': V.size.x - 1, 'b': 0 }; + for (let i = 0; i < fenRows.length; i++) { + let k = 0; //column index on board + for (let j = 0; j < fenRows[i].length; j++) { + switch (fenRows[i].charAt(j)) { + case "k": + case "l": + this.kingPos["b"] = [i, k]; + this.INIT_COL_KING["b"] = k; + break; + case "K": + case "L": + this.kingPos["w"] = [i, k]; + this.INIT_COL_KING["w"] = k; + break; + default: { + const num = parseInt(fenRows[i].charAt(j)); + if (!isNaN(num)) k += num - 1; + } + } + k++; + } + } + } + + getPotentialMovesFrom(sq) { + const piece = this.getPiece(sq[0], sq[1]); + if (V.PROMOTED.includes(piece)) { + switch (piece) { + case 's': + return ( + super.getPotentialPawnMoves(sq) + // Promoted pawns back on initial rank don't jump 2 squares: + .filter(m => Math.abs(m.end.x - m.start.x) == 1) + ); + case 'u': return super.getPotentialRookMoves(sq); + case 'o': return super.getPotentialKnightMoves(sq); + case 'c': return super.getPotentialBishopMoves(sq); + case 't': return super.getPotentialQueenMoves(sq); + case 'l': return super.getPotentialKingMoves(sq); + } + } + // Unpromoted piece: only go forward + const color = this.turn; + let moves = + super.getPotentialMovesFrom(sq) + .filter(m => { + const delta = m.end.x - m.start.x; + return ((color == 'w' && delta <= 0) || (color == 'b' && delta >= 0)); + }); + // Apply promotions: + const lastRank = (color == 'w' ? 0 : 7); + moves.forEach(m => { + if (m.end.x == lastRank) { + const pIdx = ChessRules.PIECES.findIndex(p => p == m.appear[0].p); + m.appear[0].p = V.PROMOTED[pIdx]; + } + }); + return moves; + } + + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { + const pIdx = ChessRules.PIECES.findIndex(p => p == piece); + const ppiece = V.PROMOTED[pIdx]; + const forward = (color == 'w' ? -1 : 1); + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { + rx += step[0]; + ry += step[1]; + } + if (V.OnBoard(rx, ry) && this.getColor(rx, ry) == color) { + const pieceR = this.getPiece(rx, ry); + if ( + pieceR == ppiece || + (pieceR == piece && (step[0] == 0 || -step[0] == forward)) + ) { + return true; + } + } + } + return false; + } + + postPlay(move) { + super.postPlay(move); + if (move.appear[0].p == "l") + this.kingPos[move.appear[0].c] = [move.appear[0].x, move.appear[0].y]; + } + + postUndo(move) { + super.postUndo(move); + if (move.appear[0].p == "l") + this.kingPos[this.turn] = [move.start.x, move.start.y]; + } + + static get VALUES() { + return Object.assign( + { + s: 2, + u: 8, + o: 5, + c: 5, + t: 15, + l: 1500 + }, + ChessRules.VALUES + ); + } +}; diff --git a/client/src/variants/Orda.js b/client/src/variants/Orda.js index afb736a7..1ff4898a 100644 --- a/client/src/variants/Orda.js +++ b/client/src/variants/Orda.js @@ -143,7 +143,7 @@ export class OrdaRules extends ChessRules { 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; + if (!!oneStep) continue outerLoop; i += step[0]; j += step[1]; } diff --git a/client/src/variants/Rampage.js b/client/src/variants/Rampage.js index 2fe96db6..79b8b956 100644 --- a/client/src/variants/Rampage.js +++ b/client/src/variants/Rampage.js @@ -62,13 +62,19 @@ export class RampageRules extends ChessRules { return moves; // Remember current final squares to not add moves twice: const destinations = {}; + const lastRank = (color == 'w' ? 0 : 7); + const piece = this.getPiece(x, y); moves.forEach(m => destinations[m.end.x + "_" + m.end.y] = true); for (let i=0; i<8; i++) { for (let j=0; j<8; j++) { if (this.board[i][j] == V.EMPTY && !destinations[i + "_" + j]) { const sa = this.sumAttacks([i, j]); - if ((color == 'w' && sa > 0) || (color == 'b' && sa < 0)) + if ( + ((color == 'w' && sa > 0) || (color == 'b' && sa < 0)) && + (piece != V.PAWN || i != lastRank) + ) { moves.push(this.getBasicMove([x, y], [i, j])); + } } } } diff --git a/client/src/variants/Switching.js b/client/src/variants/Switching.js index 229c7b35..b940d0a6 100644 --- a/client/src/variants/Switching.js +++ b/client/src/variants/Switching.js @@ -76,6 +76,7 @@ export class SwitchingRules extends ChessRules { this.castleFlags[a.c] = [V.size.y, V.size.y]; } }); + const firstRank = (move.vanish[0].c == 'w' ? 7 : 0); for (let coords of [move.start, move.end]) { if ( Object.keys(firstRank).includes(coords.x) && @@ -106,7 +107,7 @@ export class SwitchingRules extends ChessRules { return super.getAllPotentialMoves().filter(m => { return ( m.appear.length == 1 || - (move.appear[0].p == V.KING && move.appear[1].p == V.ROOK) || + (m.appear[0].p == V.KING && m.appear[1].p == V.ROOK) || (m.appear[1].x <= m.vanish[1].x && m.appear[1].y <= m.vanish[1].y) ); }); diff --git a/server/db/populate.sql b/server/db/populate.sql index 29595571..703297ae 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -33,6 +33,7 @@ insert or ignore into Variants (name, description) values ('Checkless', 'No-check mode'), ('Chess960', 'Standard rules'), ('Circular', 'Run forward'), + ('Clorange', 'A Clockwork Orange'), ('Colorbound', 'The colorbound clobberers'), ('Coregal', 'Two royal pieces'), ('Coronation', 'Long live the Queen'), @@ -42,11 +43,13 @@ insert or ignore into Variants (name, description) values ('Doublearmy', '64 pieces on the board'), ('Doublemove1', 'Double moves (v1)'), ('Doublemove2', 'Double moves (v2)'), + ('Doubleorda', 'Mongolian Horde (v2)'), ('Dynamo', 'Push and pull'), ('Eightpieces', 'Each piece is unique'), ('Enpassant', 'Capture en passant'), ('Extinction', 'Capture all of a kind'), ('Football', 'Score a goal'), + ('Forward', 'Moving forward'), ('Freecapture', 'Capture both colors'), ('Grand', 'Big board'), ('Grasshopper', 'Long jumps over pieces'), @@ -69,7 +72,7 @@ insert or ignore into Variants (name, description) values ('Monochrome', 'All of the same color'), ('Monster', 'White move twice'), ('Omega', 'A wizard in the corner'), - ('Orda', 'Mongolian Horde'), + ('Orda', 'Mongolian Horde (v1)'), ('Pacifist1', 'Convert & support (v1)'), ('Pacifist2', 'Convert & support (v2)'), ('Parachute', 'Landing on the board'),