From: Benjamin Auder Date: Tue, 12 Jan 2021 01:20:32 +0000 (+0100) Subject: Add Capablanca + Musketeer Chess X-Git-Url: https://git.auder.net/DESCRIPTION?a=commitdiff_plain;h=3208c66750e6a677b407267c44c35a395208c196;p=vchess.git Add Capablanca + Musketeer Chess --- diff --git a/TODO b/TODO index 4707a60c..734ab154 100644 --- a/TODO +++ b/TODO @@ -18,9 +18,6 @@ https://www.chessvariants.com/incinf.dir/bario.html https://www.bario-chess-checkers-chessphotography-spaceart.de/ https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg -https://musketeerchess.net/games/musketeer/index.php Attention règle de promotion + SVG / PNG -https://musketeerchess.net/games/cerebral/rules/rules.php : from Titan, should be easy - Non-chess: gomoku, avalam, draughts, draughts8 Yoté https://fr.wikipedia.org/wiki/Yot%C3%A9 http://www.zillionsofgames.com/cgi-bin/zilligames/submissions.cgi/92187?do=show;id=960 diff --git a/client/public/images/pieces/Musketeer/SOURCE b/client/public/images/pieces/Musketeer/SOURCE new file mode 100644 index 00000000..32c03465 --- /dev/null +++ b/client/public/images/pieces/Musketeer/SOURCE @@ -0,0 +1,7 @@ +https://www.flaticon.com/free-icon/spider_1234574?related_item_id=1235131&term=spider +https://www.flaticon.com/free-icon/tarantula_3012674?related_item_id=3012893&term=spider +https://www.svgrepo.com/svg/231793/fortress +https://www.svgrepo.com/svg/231846/fortress +https://commons.wikimedia.org/wiki/File:Chess_Ult45.svg +https://commons.wikimedia.org/wiki/File:Chess_Udt45.svg +https://en.everybodywiki.com/File:W-_-B-Leopard-Diagram.svg diff --git a/client/public/images/pieces/Musketeer/ba.svg b/client/public/images/pieces/Musketeer/ba.svg new file mode 100644 index 00000000..584f5151 --- /dev/null +++ b/client/public/images/pieces/Musketeer/ba.svg @@ -0,0 +1,92 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/bc.svg b/client/public/images/pieces/Musketeer/bc.svg new file mode 120000 index 00000000..2c609081 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bc.svg @@ -0,0 +1 @@ +ba.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bd.svg b/client/public/images/pieces/Musketeer/bd.svg new file mode 100644 index 00000000..0d15479c --- /dev/null +++ b/client/public/images/pieces/Musketeer/bd.svg @@ -0,0 +1,61 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/be.svg b/client/public/images/pieces/Musketeer/be.svg new file mode 120000 index 00000000..4595ef89 --- /dev/null +++ b/client/public/images/pieces/Musketeer/be.svg @@ -0,0 +1 @@ +../Shako/be.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bf.svg b/client/public/images/pieces/Musketeer/bf.svg new file mode 100644 index 00000000..be83f160 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bf.svg @@ -0,0 +1,132 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/bh.svg b/client/public/images/pieces/Musketeer/bh.svg new file mode 120000 index 00000000..3a672eb8 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bh.svg @@ -0,0 +1 @@ +../Schess/bh.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bj.svg b/client/public/images/pieces/Musketeer/bj.svg new file mode 100644 index 00000000..ec7b5fc8 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bj.svg @@ -0,0 +1,113 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/bl.svg b/client/public/images/pieces/Musketeer/bl.svg new file mode 120000 index 00000000..a5a5fe5a --- /dev/null +++ b/client/public/images/pieces/Musketeer/bl.svg @@ -0,0 +1 @@ +bj.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bm.svg b/client/public/images/pieces/Musketeer/bm.svg new file mode 100644 index 00000000..04164887 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bm.svg @@ -0,0 +1,97 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/bo.svg b/client/public/images/pieces/Musketeer/bo.svg new file mode 120000 index 00000000..0c757e27 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bo.svg @@ -0,0 +1 @@ +bm.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bs.svg b/client/public/images/pieces/Musketeer/bs.svg new file mode 100644 index 00000000..bba410cc --- /dev/null +++ b/client/public/images/pieces/Musketeer/bs.svg @@ -0,0 +1,102 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/bt.svg b/client/public/images/pieces/Musketeer/bt.svg new file mode 120000 index 00000000..4ab8f86b --- /dev/null +++ b/client/public/images/pieces/Musketeer/bt.svg @@ -0,0 +1 @@ +bs.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bu.svg b/client/public/images/pieces/Musketeer/bu.svg new file mode 100644 index 00000000..c1205e41 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bu.svg @@ -0,0 +1,92 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/bv.svg b/client/public/images/pieces/Musketeer/bv.svg new file mode 120000 index 00000000..f1403ed8 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bv.svg @@ -0,0 +1 @@ +bu.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bw.svg b/client/public/images/pieces/Musketeer/bw.svg new file mode 120000 index 00000000..a8dd04b7 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bw.svg @@ -0,0 +1 @@ +../Shako/bc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/bx.svg b/client/public/images/pieces/Musketeer/bx.svg new file mode 100644 index 00000000..02b08945 --- /dev/null +++ b/client/public/images/pieces/Musketeer/bx.svg @@ -0,0 +1,14 @@ + + + + + Unicorn + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/by.svg b/client/public/images/pieces/Musketeer/by.svg new file mode 100644 index 00000000..f9b413bf --- /dev/null +++ b/client/public/images/pieces/Musketeer/by.svg @@ -0,0 +1,144 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wa.svg b/client/public/images/pieces/Musketeer/wa.svg new file mode 100644 index 00000000..38f87a2e --- /dev/null +++ b/client/public/images/pieces/Musketeer/wa.svg @@ -0,0 +1,119 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wc.svg b/client/public/images/pieces/Musketeer/wc.svg new file mode 120000 index 00000000..d17eb13a --- /dev/null +++ b/client/public/images/pieces/Musketeer/wc.svg @@ -0,0 +1 @@ +wa.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/wd.svg b/client/public/images/pieces/Musketeer/wd.svg new file mode 100644 index 00000000..e8bdf9dd --- /dev/null +++ b/client/public/images/pieces/Musketeer/wd.svg @@ -0,0 +1,73 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/we.svg b/client/public/images/pieces/Musketeer/we.svg new file mode 120000 index 00000000..d642197d --- /dev/null +++ b/client/public/images/pieces/Musketeer/we.svg @@ -0,0 +1 @@ +../Shako/we.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/wf.svg b/client/public/images/pieces/Musketeer/wf.svg new file mode 100644 index 00000000..3e80ae00 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wf.svg @@ -0,0 +1,153 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wh.svg b/client/public/images/pieces/Musketeer/wh.svg new file mode 120000 index 00000000..d5ee68db --- /dev/null +++ b/client/public/images/pieces/Musketeer/wh.svg @@ -0,0 +1 @@ +../Schess/wh.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/wj.svg b/client/public/images/pieces/Musketeer/wj.svg new file mode 100644 index 00000000..97e92bbf --- /dev/null +++ b/client/public/images/pieces/Musketeer/wj.svg @@ -0,0 +1,117 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wl.svg b/client/public/images/pieces/Musketeer/wl.svg new file mode 120000 index 00000000..155f1799 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wl.svg @@ -0,0 +1 @@ +wj.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/wm.svg b/client/public/images/pieces/Musketeer/wm.svg new file mode 100644 index 00000000..7f087be0 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wm.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wo.svg b/client/public/images/pieces/Musketeer/wo.svg new file mode 120000 index 00000000..a9c2cd14 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wo.svg @@ -0,0 +1 @@ +wm.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/ws.svg b/client/public/images/pieces/Musketeer/ws.svg new file mode 100644 index 00000000..a2b8cdc4 --- /dev/null +++ b/client/public/images/pieces/Musketeer/ws.svg @@ -0,0 +1,189 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wt.svg b/client/public/images/pieces/Musketeer/wt.svg new file mode 120000 index 00000000..f1c400f5 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wt.svg @@ -0,0 +1 @@ +ws.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/wu.svg b/client/public/images/pieces/Musketeer/wu.svg new file mode 100644 index 00000000..feef69ce --- /dev/null +++ b/client/public/images/pieces/Musketeer/wu.svg @@ -0,0 +1,121 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wv.svg b/client/public/images/pieces/Musketeer/wv.svg new file mode 120000 index 00000000..da776a46 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wv.svg @@ -0,0 +1 @@ +wu.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/ww.svg b/client/public/images/pieces/Musketeer/ww.svg new file mode 120000 index 00000000..8eeec8ed --- /dev/null +++ b/client/public/images/pieces/Musketeer/ww.svg @@ -0,0 +1 @@ +../Shako/wc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Musketeer/wx.svg b/client/public/images/pieces/Musketeer/wx.svg new file mode 100644 index 00000000..644d6e82 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wx.svg @@ -0,0 +1,13 @@ + + + + + Unicorn + + + + + + + + diff --git a/client/public/images/pieces/Musketeer/wy.svg b/client/public/images/pieces/Musketeer/wy.svg new file mode 100644 index 00000000..95e4b127 --- /dev/null +++ b/client/public/images/pieces/Musketeer/wy.svg @@ -0,0 +1,134 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index c849626d..0a68127f 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -713,7 +713,7 @@ export const ChessRules = class ChessRules { let j = y + step[1]; while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { 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/translations/about/en.pug b/client/src/translations/about/en.pug index cfd3ca9f..1e8d475d 100644 --- a/client/src/translations/about/en.pug +++ b/client/src/translations/about/en.pug @@ -46,6 +46,7 @@ h3 Related links a(href="https://greenchess.net/") greenchess.net a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com a(href="https://glukkazan.github.io/") Dagaz demo + server + a(href="https://www.jocly.com/#/games") jocly.com a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr @@ -55,6 +56,8 @@ h3 Related links a(href="https://www.facebook.com/groups/592562551198628") A Facebook group a(href="http://www.zillions-of-games.com/") zillions-of-games.com a(href="https://en.wikipedia.org/wiki/Fairy_chess_piece") Fairy chess pieces + a(href="http://gambiter.com/chess/variants/Fairy_chess_piece.html") + | Another list a(href="http://www.pion.ch/echecs/liste_variantes.php") pion.ch a(href="https://www.jatektan.hu/_2018_vissza/2011_ig/uj2001/isakk1.html") | List of variants diff --git a/client/src/translations/about/es.pug b/client/src/translations/about/es.pug index 3805605f..f58ef192 100644 --- a/client/src/translations/about/es.pug +++ b/client/src/translations/about/es.pug @@ -45,6 +45,7 @@ h3 Enlaces relacionados a(href="https://greenchess.net/") greenchess.net a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com a(href="https://glukkazan.github.io/") Dagaz demo + servidor + a(href="https://www.jocly.com/#/games") jocly.com a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr @@ -55,6 +56,8 @@ h3 Enlaces relacionados a(href="http://www.zillions-of-games.com/") zillions-of-games.com a(href="https://en.wikipedia.org/wiki/Fairy_chess_piece") | Piezas de ajedrez magicas + a(href="http://gambiter.com/chess/variants/Fairy_chess_piece.html") + | Una otra lista a(href="http://www.pion.ch/echecs/liste_variantes.php") pion.ch a(href="https://www.jatektan.hu/_2018_vissza/2011_ig/uj2001/isakk1.html") | Listado de variantes diff --git a/client/src/translations/about/fr.pug b/client/src/translations/about/fr.pug index 39e22b44..b9f4ec70 100644 --- a/client/src/translations/about/fr.pug +++ b/client/src/translations/about/fr.pug @@ -46,6 +46,7 @@ h3 Liens connexes a(href="https://greenchess.net/") greenchess.net a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com a(href="https://glukkazan.github.io/") Dagaz demo + serveur + a(href="https://www.jocly.com/#/games") jocly.com a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr @@ -56,6 +57,8 @@ h3 Liens connexes a(href="http://www.zillions-of-games.com/") zillions-of-games.com a(href="https://en.wikipedia.org/wiki/Fairy_chess_piece") | Pièces d'échecs féériques + a(href="http://gambiter.com/chess/variants/Fairy_chess_piece.html") + | Une autre liste a(href="http://www.pion.ch/echecs/liste_variantes.php") pion.ch a(href="https://www.jatektan.hu/_2018_vissza/2011_ig/uj2001/isakk1.html") | Liste de variantes diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 10ac4f86..1839c669 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -179,6 +179,7 @@ export const translations = { "Board upside down": "Board upside down", "Both sides of the mirror": "Both sides of the mirror", "Burmese Chess": "Burmese Chess", + "Capablanca Chess": "Capablanca Chess", "Capture all of a kind": "Capture all of a kind", "Capture and release hostages": "Capture and release hostages", "Capture both colors": "Capture both colors", @@ -250,6 +251,7 @@ export const translations = { "Move under cover": "Move under cover", "Moving forward": "Moving forward", "Neverending rows": "Neverending rows", + "New fairy pieces": "New fairy pieces", "No paralyzed pieces": "No paralyzed pieces", "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 7ae7098b..322661c8 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -179,6 +179,7 @@ export const translations = { "Board upside down": "Tablero al revés", "Both sides of the mirror": "Ambos lados del espejo", "Burmese Chess": "Ajedrez birmano", + "Capablanca Chess": "Ajedrez Capablanca", "Capture all of a kind": "Capturar todo del mismo tipo", "Capture and release hostages": "Captura y libera a los rehenes", "Capture both colors": "Captura ambos colores", @@ -250,6 +251,7 @@ export const translations = { "Move under cover": "Ir bajo cubierta", "Moving forward": "Ir adelante", "Neverending rows": "Filas interminables", + "New fairy pieces": "Nuevas piezas magicas", "No paralyzed pieces": "No piezas paralizadas", "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 f905ab3a..94a36189 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -179,6 +179,7 @@ export const translations = { "Board upside down": "Échiquier à l'envers", "Both sides of the mirror": "Les deux côté du miroir", "Burmese Chess": "Échecs birmans", + "Capablanca Chess": "Échecs Capablanca", "Capture all of a kind": "Capturez tout d'un même type", "Capture and release hostages": "Capturez et libérez les otages", "Capture both colors": "Capturer les deux couleurs", @@ -250,6 +251,7 @@ export const translations = { "Move under cover": "Avancez à couvert", "Moving forward": "Aller de l'avant", "Neverending rows": "Rangées sans fin", + "New fairy pieces": "Nouvelles pièces féériques", "No paralyzed pieces": "Pas de pièces paralysées", "No-check mode": "Mode sans échec", "Non-conformism and utopia": "Non-conformisme et utopie", diff --git a/client/src/translations/rules/Capablanca/en.pug b/client/src/translations/rules/Capablanca/en.pug new file mode 100644 index 00000000..c725003d --- /dev/null +++ b/client/src/translations/rules/Capablanca/en.pug @@ -0,0 +1,28 @@ +p.boxed + | Extended board with two new pieces. + +p. + Capablanca Chess was created by World Chess Champion José Raúl Capablanca + in the 1920s. The game is played on a 10 x 8 board and adds + two new hybrid pieces: the Princess (S) and the Empress (E). + +figure.diagram-container + .diagram + | fen:rnsbqkbenr/pppppppppp/91/91/91/91/PPPPPPPPPP/RNSBQKBENR: + figcaption Initial deterministic position. + +p Movements of the hybrid pieces: +ul + li Princess = Bishop + Knight. + li Empress = Rook + Knight. + +p. + For castling, the king then moves three squares instead of two. + Pawns may promote to the Princess and Empress as well. + +h3 More information + +p + | The variant is playable on + a(href="https://www.pychess.org/variant/capablanca") pychess-variants + | . See also the Wikipedia page. diff --git a/client/src/translations/rules/Capablanca/es.pug b/client/src/translations/rules/Capablanca/es.pug new file mode 100644 index 00000000..8f3b8f67 --- /dev/null +++ b/client/src/translations/rules/Capablanca/es.pug @@ -0,0 +1,29 @@ +p.boxed + | Tablero de ajedrez extendido con dos piezas nuevas. + +p. + El Ajedrez Capablanca fue inventado por el ex campeón mundial + José Raúl Capablanca en la década de 1920. Se juegan sobre un tablero + 10 x 8 e introducir dos nuevas piezas híbridas: + la Princesa (S) y la Emperatriz (E). + +figure.diagram-container + .diagram + | fen:rnsbqkbenr/pppppppppp/91/91/91/91/PPPPPPPPPP/RNSBQKBENR: + figcaption Posición inicial determinista. + +p Desplazamiento de piezas híbridas: +ul + li Princesa = Alfil + Caballo. + li Emperatriz = Torre + Caballo. + +p. + Durante el enroque, el rey mueve tres casillas en lugar de dos. + Los peones también pueden ser promovidos a princesa o emperatriz. + +h3 Más información + +p + | La variante se puede jugar en + a(href="https://www.pychess.org/variant/capablanca") pychess-variants + | . Consulte también la página de Wikipedia. diff --git a/client/src/translations/rules/Capablanca/fr.pug b/client/src/translations/rules/Capablanca/fr.pug new file mode 100644 index 00000000..5f3999fa --- /dev/null +++ b/client/src/translations/rules/Capablanca/fr.pug @@ -0,0 +1,29 @@ +p.boxed + | Échiquier étendu avec deux nouvelles pièces. + +p. + Les Échecs Capablanca furent inventés par l'ancien champion du monde + José Raúl Capablanca dans les années 1920. Ils se jouent sur un échiquier + 10 x 8, et introduisent deux nouvelles pièces hybrides : + la Princesse (S) et l'Impératrice (E). + +figure.diagram-container + .diagram + | fen:rnsbqkbenr/pppppppppp/91/91/91/91/PPPPPPPPPP/RNSBQKBENR: + figcaption Position initiale déterministe. + +p Déplacement des pièces hybrides : +ul + li Princesse = Fou + Cavalier. + li Impératrice = Tour + Cavalier. + +p. + Lors du roque, le roi se déplace de trois cases au lieu de deux. + Les pions peuvent être promus en Princess ou Impératrice également. + +h3 Plus d'information + +p + | La variante est jouable sur + a(href="https://www.pychess.org/variant/capablanca") pychess-variants + | . Voir aussi la page Wikipedia. diff --git a/client/src/translations/rules/Musketeer/en.pug b/client/src/translations/rules/Musketeer/en.pug new file mode 100644 index 00000000..e90cbe9b --- /dev/null +++ b/client/src/translations/rules/Musketeer/en.pug @@ -0,0 +1,91 @@ +p.boxed + | Two new pieces are introduced out of seven available. + +p + | Musketeer Chess was invented by Zied Haddad in 2011. + | You can read the rules of this game and order fairy pieces sets on the + a(href="https://musketeerchess.net/home/index.html") official website + | . The corresponding + a(href="https://www.jocly.com/#/games") playing area + |  allows to play (really) many games, with nice 3-D pieces. It seems + | that for now you can only play against a AI, but this will change. + +figure.showPieces.text-center + img(src="/images/pieces/Musketeer/wd.svg") + img(src="/images/pieces/Musketeer/ww.svg") + img(src="/images/pieces/Musketeer/wx.svg") + img(src="/images/pieces/Musketeer/we.svg") + img(src="/images/pieces/Musketeer/wh.svg") + img(src="/images/pieces/Musketeer/wf.svg") + img(src="/images/pieces/Musketeer/wy.svg") + figcaption New pieces for Musketeer Chess. + +h3 Rules summary + +p. + At the very first turn of the game, White must choose a piece which will + be later introduced into play. + Play this move from the reserve below the board to any free square. + Then, Black must do the same with another of the remaining pieces. + +p. + At the second move, White brings the first chosen piece (still from the + reserve) to any location on its first rank. + Then Black do the same for this first piece, + and on the third turn a location for the second selected piece is decided. + +p. + The game now proceeds normally, following orthodox chess rules. + However, when a piece where you placed an extra figure is moved for the + first time, the new piece comes into play on the starting square — + as in Seirawan Chess. + +figure.diagram-container + .diagram.diag12 + | fen:rnbs2or/ppp2ppp/3bp3/3pN3/k2P3P/6P1/PPP1PP2/RMBQKC1R: + .diagram.diag22 + | fen:rnbs2or/ppp2ppp/3bp3/3pN3/k2P3P/2N3P1/PPP1PP2/RXBQKC1R: + figcaption Before and after Nc3, a Unicorn appears on b1: double check. + +h3 New pieces movements + +p + | They are detailed for example on the + a(href="https://www.chessvariants.com/rules/musketeer-chess") + | chessvariants page + | , but let's sum up their behavior here as well. + | "Bishop[X]" indicates a piece moving like a limited range Bishop, + | restricted to X squares in any diagonal direction. + +ul + li Leopard (L) = Knight + Bishop[2] + li Cannon (C) = King + Wide Knight + Dabbabah + li Unicorn (U) = Knight + Camel + li Elephant (E) = King + Alfil + Dabbabh + li Hawk (H) = Alfil + Tripper + li Fortress (F) = Narrow Knight + Dabbabah + Bishop[3] + li Spider (S) = Knight + Dabbabah + Bishop[2] + +p With +ul + li Alfil = two squares leaper in diagonal + li Dabbabah = 2 squares leaper orthogonally + li + a(href="http://gambiter.com/chess/variants/Tripper_chess.html") Tripper + |  = 3 squares leaper diagonally + li. + Camel = extended Knight, + 3 squares in one direction and then 1 to the side + li Wide Knight and Narrow Knight: restricted Knight, see the image below. + +figure.diagram-container + .diagram.diag12 + | fen:8/8/8/3N4/8/8/8/8 b6,b4,f6,f4: + .diagram.diag22 + | fen:8/8/8/8/4N3/8/8/8/8 d6,f6,d2,f2: + figcaption Movements of the Wide Knight (left) and Narrow Knight (right). + +p. + Note: the Dragon, Chancellor and Archbishop were removed, because these + pieces are already seen in several other variants, and, they are a bit + too powerful — especially the Dragon. diff --git a/client/src/translations/rules/Musketeer/es.pug b/client/src/translations/rules/Musketeer/es.pug new file mode 100644 index 00000000..3a1534d4 --- /dev/null +++ b/client/src/translations/rules/Musketeer/es.pug @@ -0,0 +1,94 @@ +p.boxed + | Se introducen dos nuevas piezas entre las siete disponibles. + +p + | El Ajedrez Mosquetero fue inventado por Zied Haddad en 2011. + | Puedes leer las reglas y ordenar juegos de piezas magicas en el + a(href="https://musketeerchess.net/home/index.html") sitio oficial + | . La + a(href="https://www.jocly.com/#/games") zona de juego + | & nbsp;correspondiente permite de jugar (realmente) muchos juegos, + | con bonitas piezas en 3-D. Parece limitado a partidos contra la IA, + | pero eso cambiará. + +figure.showPieces.text-center + img(src="/images/pieces/Musketeer/wd.svg") + img(src="/images/pieces/Musketeer/ww.svg") + img(src="/images/pieces/Musketeer/wx.svg") + img(src="/images/pieces/Musketeer/we.svg") + img(src="/images/pieces/Musketeer/wh.svg") + img(src="/images/pieces/Musketeer/wf.svg") + img(src="/images/pieces/Musketeer/wy.svg") + figcaption Nuevas piezas para el Ajedrez Mosquetero. + +h3 Resumen de reglas + +p. + En el primer movimiento del juego, las blancas deben elegir una pieza que + más tarde se introducirá en el juego. + Juega este movimiento desde la reserva debajo del tablero hasta + una casilla vacía. Entonces las negras deben hacer lo mismo con otra pieza + entre los que quedan. + +p. + En el segundo movimiento, las blancas traen la primera pieza seleccionada + (siempre desde la reserva) a cualquier casilla de su primer + fila. Entonces las negras hacen lo mismo con su primera pieza, + y en la tercera ronda se elige una ubicación para la segunda pieza + seleccionada. + +p. + El juego procede entonces normalmente, siguiendo las reglas del + Ajedrez ortodoxo. Sin embargo, cuando una pieza donde ha colocado + una unidad adicional se mueve por la primera vez, la pieza nueva + entra en juego en la casilla inicial — como en el Ajedrez Seirawan. + +figure.diagram-container + .diagram.diag12 + | fen:rnbs2or/ppp2ppp/3bp3/3pN3/k2P3P/6P1/PPP1PP2/RMBQKC1R: + .diagram.diag22 + | fen:rnbs2or/ppp2ppp/3bp3/3pN3/k2P3P/2N3P1/PPP1PP2/RXBQKC1R: + figcaption Antes y después de Nc3, aparece un Unicornio en b1: doble jaque. + +h3 Desplazamiento de piezas nuevas + +p + | Se detallan, por ejemplo, en la + a(href="https://www.chessvariants.com/rules/musketeer-chess") + | página chessvariants + | , pero aquí también resumimos su comportamiento. + | "Alfil[X]" indica una pieza que se mueve como un Alfil a un alcance + | limitado, restringido a X casillas en cualquier dirección diagonal. + +ul + li Leopardo (L) = Caballo + Alfil [2] + li Canon (C) = Rey + Wide Knight + Dabbabah + li Unicornio (U) = Caballo + Camello + li Elefante (E) = Rey + Alfil + Dabbabah + li Halcón (H) = Alfil + Tripper + li Fortaleza (F) = Narrow Knight + Dabbabah + Alfil[3] + li Araña (S) = Caballo + Dabbabah + Alfil[2] + +p Con +ul + li Alfil = saltar 2 casillas en diagonal + li Dabbabah = saltar 2 casillas ortogonalmente + li + a(href="http://gambiter.com/chess/variants/Tripper_chess.html") Tripper + |  = saltar 3 casillas en diagonal + li Camel = Caballo acostado, 3 casillas en una dirección y luego 1 en el lado + li. + Wide Knight y Narrow Knight: + Caballo restringido, ver imagen a continuación. + +figure.diagram-container + .diagram.diag12 + | fen:8/8/8/3N4/8/8/8/8 b6,b4,f6,f4: + .diagram.diag22 + | fen:8/8/8/8/4N3/8/8/8/8 d6,f6,d2,f2: + figcaption Movimientos de Wide Knight (izquierda) y Narrow Knight (derecha). + +p. + Nota: se han eliminado el Dragón, el Canciller y el Arzobispo, ya que estas + piezas ya están presentes en otras variantes, y, son un poco + demasiado poderoso — especialmente el Dragón. diff --git a/client/src/translations/rules/Musketeer/fr.pug b/client/src/translations/rules/Musketeer/fr.pug new file mode 100644 index 00000000..de221b21 --- /dev/null +++ b/client/src/translations/rules/Musketeer/fr.pug @@ -0,0 +1,95 @@ +p.boxed + | Deux nouvelles pièces sont introduites parmi sept disponibles. + +p + | Les Échecs Mousquetaires furent inventés par Zied Haddad en 2011. + | Vous pouvez lire les règles et commander + | des jeux de pièces féériques sur le + a(href="https://musketeerchess.net/home/index.html") site officiel + | . La + a(href="https://www.jocly.com/#/games") zone de jeu + |  correspondante permet de jouer à (vraiment) beaucoup de jeux, + | avec de jolies pièces 3-D. Elle semble limitée à des matchs contre l'IA, + | mais cela va changer. + +figure.showPieces.text-center + img(src="/images/pieces/Musketeer/wd.svg") + img(src="/images/pieces/Musketeer/ww.svg") + img(src="/images/pieces/Musketeer/wx.svg") + img(src="/images/pieces/Musketeer/we.svg") + img(src="/images/pieces/Musketeer/wh.svg") + img(src="/images/pieces/Musketeer/wf.svg") + img(src="/images/pieces/Musketeer/wy.svg") + figcaption Nouvelles pièces pour les Échecs Mousquetaires. + +h3 Résumé des règles + +p. + Au tout premier coup de la partie, les blancs doivent choisir une pièce qui + sera plus tard introduite dans le jeu. + Jouez ce coup depuis la réserve sous l'échiquier vers une case vide. + Ensuite, les noirs doivent faire de même avec une autre pièce + parmi celles qui restent. + +p. + Au second coup, les blancs amènent la première pièce sélectionnée + (toujours depuis la réserve) vers n'importe quelle case de leur première + rangée. Ensuite les noirs procèdent de même pour leur première pièce, + et au troisième tour un emplacement est choisi pour la seconde pièce + sélectionnée. + +p. + Le jeu se déroule alors normalement, suivant les règles des + échecs orthodoxes. Cependant, quand une pièce où vous avez placé une + unité supplémentaire se déplace pour la première fois, la nouvelle pièce + entre en jeu sur la case de départ — comme aux Échecs Seirawan. + +figure.diagram-container + .diagram.diag12 + | fen:rnbs2or/ppp2ppp/3bp3/3pN3/k2P3P/6P1/PPP1PP2/RMBQKC1R: + .diagram.diag22 + | fen:rnbs2or/ppp2ppp/3bp3/3pN3/k2P3P/2N3P1/PPP1PP2/RXBQKC1R: + figcaption Avant et après Nc3, une Licorne apparaît en b1 : échec double. + +h3 Déplacement des nouvelles pièces + +p + | Ils sont détaillés par exemple sur la + a(href="https://www.chessvariants.com/rules/musketeer-chess") + | page chessvariants + | , mais on résume leur comportement ici aussi. + | "Fou[X]" indique une pièce se déplaçant comme un fou à portée limitée, + | restreint à X cases dans n'importe quelle direction diagonale. + +ul + li Léopard (L) = Cavalier + Fou[2] + li Canon (C) = Roi + Wide Knight + Dabbabah + li Licorne (U) = Cavalier + Chameau + li Éléphant (E) = Roi + Alfil + Dabbabah + li Faucon (H) = Alfil + Tripper + li Forteresse (F) = Narrow Knight + Dabbabah + Fou[3] + li Araignée (S) = Cavalier + Dabbabah + Fou[2] + +p Avec +ul + li Alfil = saute de 2 cases en diagonale + li Dabbabah = saute de 2 cases orthogonalement + li + a(href="http://gambiter.com/chess/variants/Tripper_chess.html") Tripper + |  = saute de 3 cases en diagonale + li Chameau = Cavalier allongé, 3 cases dans une direction puis 1 sur le côté + li. + Wide Knight et narrow Knight : + Cavalier restreint, voir l'image ci-dessous. + +figure.diagram-container + .diagram.diag12 + | fen:8/8/8/3N4/8/8/8/8 b6,b4,f6,f4: + .diagram.diag22 + | fen:8/8/8/8/4N3/8/8/8/8 d6,f6,d2,f2: + figcaption Déplacements du Wide Knight (gauche) et Narrow Knight (droite). + +p. + Note: le Dragon, le Chancelier et l'Archevêque ont été retirés, car ces + pièces sont déjà présentes dans d'autres variantes, et, elles sont un peu + trop puissantes — surtout le Dragon. diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index f77c4b15..7d788bdf 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -397,6 +397,7 @@ p. "Avalanche", "Bicolour", "Brotherhood", + "Capablanca", "Castle", "Crossing", "Doublearmy", @@ -411,6 +412,7 @@ p. "Magnetic", "Maharajah", "Mesmer", + "Musketeer", "Otage", "Pacosako", "Parachute", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index e3a287fb..9ba6c2d9 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -408,6 +408,7 @@ p. "Avalanche", "Bicolour", "Brotherhood", + "Capablanca", "Castle", "Crossing", "Doublearmy", @@ -422,6 +423,7 @@ p. "Magnetic", "Maharajah", "Mesmer", + "Musketeer", "Otage", "Pacosako", "Parachute", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index cd1a6e40..82a38424 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -407,6 +407,7 @@ p. "Avalanche", "Bicolour", "Brotherhood", + "Capablanca", "Castle", "Crossing", "Doublearmy", @@ -421,6 +422,7 @@ p. "Magnetic", "Maharajah", "Mesmer", + "Musketeer", "Otage", "Pacosako", "Parachute", diff --git a/client/src/variants/Capablanca.js b/client/src/variants/Capablanca.js new file mode 100644 index 00000000..ec1e299a --- /dev/null +++ b/client/src/variants/Capablanca.js @@ -0,0 +1,184 @@ +import { ChessRules } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +export class CapablancaRules extends ChessRules { + + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { + promotions: + ChessRules.PawnSpecs.promotions + .concat([V.EMPRESS, V.PRINCESS]) + } + ); + } + + getPpath(b) { + return ([V.EMPRESS, V.PRINCESS].includes(b[1]) ? "Capablanca/" : "") + b; + } + + static get size() { + return { x: 8, y: 10 }; + } + + // Rook + knight: + static get EMPRESS() { + return "e"; + } + + // Bishop + knight + static get PRINCESS() { + return "s"; + } + + static get PIECES() { + return ChessRules.PIECES.concat([V.EMPRESS, V.PRINCESS]); + } + + getPotentialMovesFrom([x, y]) { + switch (this.getPiece(x, y)) { + case V.EMPRESS: + return this.getPotentialEmpressMoves([x, y]); + case V.PRINCESS: + return this.getPotentialPrincessMoves([x, y]); + default: + return super.getPotentialMovesFrom([x, y]); + } + } + + getPotentialEmpressMoves(sq) { + return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); + } + + getPotentialPrincessMoves(sq) { + return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); + } + + isAttacked(sq, color) { + return ( + super.isAttacked(sq, color) || + this.isAttackedByEmpress(sq, color) || + this.isAttackedByPrincess(sq, color) + ); + } + + isAttackedByEmpress(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.EMPRESS, V.steps[V.ROOK]) || + this.isAttackedBySlideNJump( + sq, + color, + V.EMPRESS, + V.steps[V.KNIGHT], + "oneStep" + ) + ); + } + + isAttackedByPrincess(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.PRINCESS, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump( + sq, + color, + V.PRINCESS, + V.steps[V.KNIGHT], + "oneStep" + ) + ); + } + + static get VALUES() { + return Object.assign( + { s: 5, e: 7 }, + ChessRules.VALUES + ); + } + + static get SEARCH_DEPTH() { + return 2; + } + + static GenRandInitFen(randomness) { + if (randomness == 0) { + return ( + "rnsbqkbenr/pppppppppp/91/91/91/91/PPPPPPPPPP/RNSBQKBENR w 0 ajaj -" + ); + } + + let pieces = { w: new Array(10), b: new Array(10) }; + let flags = ""; + for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w']; + flags += flags; + break; + } + + let positions = ArrayFun.range(10); + + // Get random squares for bishops + let randIndex = 2 * randInt(5); + const bishop1Pos = positions[randIndex]; + // The second bishop must be on a square of different color + let randIndex_tmp = 2 * randInt(5) + 1; + const bishop2Pos = positions[randIndex_tmp]; + // Remove chosen squares + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); + + // Get random square for empress + randIndex = randInt(8); + const empressPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random square for princess + randIndex = randInt(7); + const princessPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random squares for knights + randIndex = randInt(6); + const knight1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(5); + const knight2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random square for queen + randIndex = randInt(4); + const queenPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Now rooks + king positions are fixed: + const rook1Pos = positions[0]; + const kingPos = positions[1]; + const rook2Pos = positions[2]; + + // Finally put the shuffled pieces in the board array + pieces[c][rook1Pos] = "r"; + pieces[c][knight1Pos] = "n"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][empressPos] = "e"; + pieces[c][princessPos] = "s"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; + flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos); + } + return ( + pieces["b"].join("") + "/pppppppppp/91/91/91/91/PPPPPPPPPP/" + + pieces["w"].join("").toUpperCase() + " w 0 " + flags + " - -" + ); + } + +}; diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index 5dc355f4..1f3e8613 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -264,39 +264,39 @@ export class GrandRules extends ChessRules { // Get random squares for bishops let randIndex = 2 * randInt(4); - let bishop1Pos = positions[randIndex]; + const bishop1Pos = positions[randIndex]; // The second bishop must be on a square of different color let randIndex_tmp = 2 * randInt(4) + 1; - let bishop2Pos = positions[randIndex_tmp]; + const bishop2Pos = positions[randIndex_tmp]; // Remove chosen squares positions.splice(Math.max(randIndex, randIndex_tmp), 1); positions.splice(Math.min(randIndex, randIndex_tmp), 1); // Get random squares for knights randIndex = randInt(6); - let knight1Pos = positions[randIndex]; + const knight1Pos = positions[randIndex]; positions.splice(randIndex, 1); randIndex = randInt(5); - let knight2Pos = positions[randIndex]; + const knight2Pos = positions[randIndex]; positions.splice(randIndex, 1); // Get random square for queen randIndex = randInt(4); - let queenPos = positions[randIndex]; + const queenPos = positions[randIndex]; positions.splice(randIndex, 1); // ...random square for marshall randIndex = randInt(3); - let marshallPos = positions[randIndex]; + const marshallPos = positions[randIndex]; positions.splice(randIndex, 1); // ...random square for cardinal randIndex = randInt(2); - let cardinalPos = positions[randIndex]; + const cardinalPos = positions[randIndex]; positions.splice(randIndex, 1); // King position is now fixed, - let kingPos = positions[0]; + const kingPos = positions[0]; // Finally put the shuffled pieces in the board array pieces[c][knight1Pos] = "n"; diff --git a/client/src/variants/Musketeer.js b/client/src/variants/Musketeer.js new file mode 100644 index 00000000..7fa39302 --- /dev/null +++ b/client/src/variants/Musketeer.js @@ -0,0 +1,769 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; +import { randInt } from "@/utils/alea"; + +export class MusketeerRules extends ChessRules { + + // Extra pieces get strange letters because many taken by combinations below + static get LEOPARD() { + return "d"; + } + static get CANNON() { + return "w"; + } + static get UNICORN() { + return "x"; + } + static get ELEPHANT() { + return "e"; + } + static get HAWK() { + return "h"; + } + static get FORTRESS() { + return "f"; + } + static get SPIDER() { + return "y"; + } + + static get RESERVE_PIECES() { + return ( + [V.LEOPARD, V.CANNON, V.UNICORN, V.ELEPHANT, + V.HAWK, V.FORTRESS, V.SPIDER] + ); + } + + static get PIECES() { + return ChessRules.PIECES.concat(V.RESERVE_PIECES); + } + + // Decode if normal piece, or + piece1 or piece2 + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; + const piece = this.board[i][j].charAt(1); + if (V.PIECES.includes(piece)) return piece; + // Augmented piece: + switch (piece) { + case 'a': + case 'c': + return 'b'; + case 'j': + case 'l': + return 'k'; + case 'm': + case 'o': + return 'n'; + case 's': + case 't': + return 'q'; + case 'u': + case 'v': + return 'r'; + } + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + // Code: a/c = bishop + piece1/piece2 j/l for king, + // m/o for knight, s/t for queen, u/v for rook + static get AUGMENTED_PIECES() { + return [ + 'a', + 'c', + 'j', + 'l', + 'm', + 'o', + 's', + 't', + 'u', + 'v' + ]; + } + + getPpath(b) { + return (ChessRules.PIECES.includes(b[1]) ? "" : "Musketeer/") + b; + } + + getReservePpath(index, color) { + return "Musketeer/" + color + V.RESERVE_PIECES[index]; + } + + // Decode above notation into additional piece + getExtraPiece(symbol) { + if (['a','j','m','s','u'].includes(symbol)) + return this.extraPieces[0]; + return this.extraPieces[1]; + } + + // Inverse operation: augment piece + getAugmented(piece) { + const p1 = [2, 3].includes(this.movesCount); + switch (piece) { + case V.ROOK: return (p1 ? 'u' : 'v'); + case V.KNIGHT: return (p1 ? 'm' : 'o'); + case V.BISHOP: return (p1 ? 'a' : 'c'); + case V.QUEEN: return (p1 ? 's' : 't'); + case V.KING: return (p1 ? 'j' : 'l'); + } + return '_'; //never reached + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // 5) Check extra pieces + if (!fenParsed.extraPieces) return false; + // Not exact matching (would need to look at movesCount), but OK for now + if (!fenParsed.extraPieces.match(/^[dwxejfy-]{2,2}$/)) return false; + return true; + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + let kings = { "w": 0, "b": 0 }; + const allPiecesCodes = V.PIECES.concat(V.AUGMENTED_PIECES); + const kingBlackCodes = ['j','k','l']; + const kingWhiteCodes = ['J','K','L']; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (kingBlackCodes.includes(row[i])) kings['b']++; + else if (kingWhiteCodes.includes(row[i])) kings['w']++; + if (allPiecesCodes.includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num)) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + // Both kings should be on board, only one of each color: + if (Object.values(kings).some(v => v != 1)) return false; + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { extraPieces: fenParts[5] } + ); + } + + static GenRandInitFen(randomness) { + return ChessRules.GenRandInitFen(randomness) + " --"; + } + + getFen() { + return super.getFen() + " " + this.extraPieces.join(""); + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + // Extra pieces may not be defined yet (thus '-') + this.extraPieces = V.ParseFen(fen).extraPieces.split(""); + // At early stages, also init reserves + if (this.movesCount <= 5) { + const condShow = (piece) => { + if (this.movesCount == 0) return true; + if (this.movesCount == 1) return piece != this.extraPieces[0]; + if (this.movesCount <= 3) return this.extraPiece.includes(piece); + return this.extraPiece[1] == piece; + } + this.reserve = { w : {}, b: {} }; + for (let c of ['w', 'b']) { + V.RESERVE_PIECES.forEach(p => + this.reserve[c][p] = condShow(p) ? 1 : 0); + } + } + } + + // Kings may be augmented: + scanKings(fen) { + this.kingPos = { w: [-1, -1], b: [-1, -1] }; + const rows = V.ParseFen(fen).position.split("/"); + for (let i = 0; i < rows.length; i++) { + let k = 0; //column index on board + for (let j = 0; j < rows[i].length; j++) { + const piece = rows[i].charAt(j); + if (['j','k','l'].includes(piece.toLowerCase())) { + const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b'); + this.kingPos[color] = [i, k]; + } + else { + const num = parseInt(rows[i].charAt(j), 10); + if (!isNaN(num)) k += num - 1; + } + k++; + } + } + } + + getReserveMoves([x, y]) { + const color = this.turn; + const p = V.RESERVE_PIECES[y]; + if ( + this.reserve[color][p] == 0 || + ([2, 3].includes(this.movesCount) && p != this.extraPieces[0]) || + ([4, 5].includes(this.movesCount) && p != this.extraPieces[1]) + ) { + return []; + } + let moves = []; + const iIdx = + (this.movesCount <= 1 ? [2, 3, 4, 5] : [color == 'w' ? 7 : 0]); + const mappingAppear = [ [3, 4], [3, 3], [4, 4], [4, 3] ]; + for (let i of iIdx) { + for (let j = 0; j < V.size.y; j++) { + if ( + (this.movesCount <= 1 && this.board[i][j] == V.EMPTY) || + ( + this.movesCount >= 2 && + ChessRules.PIECES.includes(this.board[i][j].charAt(1)) + ) + ) { + const [appearX, appearY] = + this.movesCount <= 1 + ? mappingAppear[this.movesCount] + : [i, j]; + const pOnBoard = + (this.movesCount >= 2 ? this.board[i][j].charAt(1) : ''); + let mv = new Move({ + appear: [ + new PiPo({ + x: appearX, + y: appearY, + c: color, + p: (this.movesCount <= 1 ? p : this.getAugmented(pOnBoard)) + }) + ], + vanish: [], + start: { x: x, y: y }, //a bit artificial... + end: { x: i, y: j } + }); + if (this.movesCount >= 2) + mv.vanish.push(new PiPo({ x: i, y: j, c: color, p: pOnBoard })) + moves.push(mv); + } + } + } + return moves; + } + + // Assumption: movesCount >= 6 + getPotentialMovesFrom([x, y]) { + // Standard moves. If piece not in usual list, new piece appears. + const initialPiece = this.getPiece(x, y); + if (V.RESERVE_PIECES.includes(initialPiece)) { + switch (initialPiece) { + case V.LEOPARD: return this.getPotentialLeopardMoves([x, y]); + case V.CANNON: return this.getPotentialCannonMoves([x, y]); + case V.UNICORN: return this.getPotentialUnicornMoves([x, y]); + case V.ELEPHANT: return this.getPotentialElephantMoves([x, y]); + case V.HAWK: return this.getPotentialHawkMoves([x, y]); + case V.FORTRESS: return this.getPotentialFortressMoves([x, y]); + case V.SPIDER: return this.getPotentialSpiderMoves([x, y]); + } + return []; //never reached + } + // Following is mostly copy-paste from Titan Chess (TODO?) + let moves = []; + if (initialPiece == V.PAWN) { + const promotions = + ChessRules.PawnSpecs.promotions.concat(this.extraPieces); + moves = super.getPotentialPawnMoves([x, y], promotions); + } + else moves = super.getPotentialMovesFrom([x, y]); + const color = this.turn; + if ( + ((color == 'w' && x == 7) || (color == "b" && x == 0)) && + V.AUGMENTED_PIECES.includes(this.board[x][y][1]) + ) { + const newPiece = this.getExtraPiece(this.board[x][y][1]); + moves.forEach(m => { + m.appear[0].p = initialPiece; + m.appear.push( + new PiPo({ + p: newPiece, + c: color, + x: x, + y: y + }) + ); + }); + moves.forEach(m => { + if (m.vanish.length <= 1) return; + const [vx, vy] = [m.vanish[1].x, m.vanish[1].y]; + if ( + m.appear.length >= 2 && //3 if the king was also augmented + m.vanish.length == 2 && + m.vanish[1].c == color && + V.AUGMENTED_PIECES.includes(this.board[vx][vy][1]) + ) { + // Castle, rook is an "augmented piece" + m.appear[1].p = V.ROOK; + m.appear.push( + new PiPo({ + p: this.getExtraPiece(this.board[vx][vy][1]), + c: color, + x: vx, + y: vy + }) + ); + } + }); + } + return moves; + } + + getSlideNJumpMoves([x, y], steps, nbSteps) { + let moves = []; + outerLoop: for (let step of steps) { + let i = x + step[0]; + let j = y + step[1]; + let stepCounter = 1; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + if ( + !!nbSteps && + // Next condition to remain compatible with super method + (isNaN(parseInt(nbSteps, 10)) || nbSteps >= stepCounter) + ) { + continue outerLoop; + } + i += step[0]; + j += step[1]; + stepCounter++; + } + if (V.OnBoard(i, j) && this.canTake([x, y], [i, j])) + moves.push(this.getBasicMove([x, y], [i, j])); + } + return moves; + } + + // All types of leaps used here: + static get Leap2Ortho() { + return [ [-2, 0], [0, -2], [2, 0], [0, 2] ]; + } + static get Leap2Diago() { + return [ [-2, -2], [-2, 2], [2, -2], [2, 2] ]; + } + static get Leap3Ortho() { + return [ [-3, 0], [0, -3], [3, 0], [0, 3] ]; + } + static get Leap3Diago() { + return [ [-3, -3], [-3, 3], [3, -3], [3, 3] ]; + } + static get CamelSteps() { + return [ + [-3, -1], [-3, 1], [-1, -3], [-1, 3], + [1, -3], [1, 3], [3, -1], [3, 1] + ]; + } + static get VerticalKnight() { + return [ [-2, -1], [-2, 1], [2, -1], [2, 1] ]; + } + static get HorizontalKnight() { + return [ [-1, -2], [-1, 2], [1, -2], [1, 2] ]; + } + + getPotentialLeopardMoves(sq) { + return ( + this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 2) + .concat(super.getPotentialKnightMoves(sq)) + ); + } + + getPotentialCannonMoves(sq) { + const steps = + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + .concat(V.Leap2Ortho).concat(V.HorizontalKnight); + return super.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + getPotentialUnicornMoves(sq) { + return ( + super.getPotentialKnightMoves(sq) + .concat(super.getSlideNJumpMoves(sq, V.CamelSteps, "oneStep")) + ); + } + + getPotentialElephantMoves(sq) { + const steps = + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + .concat(V.Leap2Ortho) + .concat(V.Leap2Diago); + return super.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + getPotentialHawkMoves(sq) { + const steps = + V.Leap2Ortho.concat(V.Leap2Diago) + .concat(V.Leap3Ortho).concat(V.Leap3Diago); + return super.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + getPotentialFortressMoves(sq) { + const steps = V.Leap2Ortho.concat(V.VerticalKnight) + return ( + super.getSlideNJumpMoves(sq, steps, "oneStep") + .concat(this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 3)) + ); + } + + getPotentialSpiderMoves(sq) { + const steps = V.Leap2Ortho.concat(V.steps[V.KNIGHT]) + return ( + super.getSlideNJumpMoves(sq, steps, "oneStep") + .concat(this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 2)) + ); + } + + getPossibleMovesFrom([x, y]) { + if (this.movesCount <= 5) + return (x >= V.size.x ? this.getReserveMoves([x, y]) : []); + return super.getPossibleMovesFrom([x, y]); + } + + getAllValidMoves() { + if (this.movesCount >= 6) return super.getAllValidMoves(); + let moves = []; + 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 moves; + } + + atLeastOneMove() { + if (this.movesCount <= 5) return true; + return super.atLeastOneMove(); + } + + isAttacked(sq, color) { + if (super.isAttacked(sq, color)) return true; + if ( + this.extraPieces.includes(V.LEOPARD) && + this.isAttackedByLeopard(sq, color) + ) { + return true; + } + if ( + this.extraPieces.includes(V.CANNON) && + this.isAttackedByCannon(sq, color) + ) { + return true; + } + if ( + this.extraPieces.includes(V.UNICORN) && + this.isAttackedByUnicorn(sq, color) + ) { + return true; + } + if ( + this.extraPieces.includes(V.ELEPHANT) && + this.isAttackedByElephant(sq, color) + ) { + return true; + } + if ( + this.extraPieces.includes(V.HAWK) && + this.isAttackedByHawk(sq, color) + ) { + return true; + } + if ( + this.extraPieces.includes(V.FORTRESS) && + this.isAttackedByFortress(sq, color) + ) { + return true; + } + if ( + this.extraPieces.includes(V.SPIDER) && + this.isAttackedBySpider(sq, color) + ) { + return true; + } + return false; + } + + // Modify because of the limiyted steps options of some of the pieces here + isAttackedBySlideNJump([x, y], color, piece, steps, nbSteps) { + if (!!nbSteps && isNaN(parseInt(nbSteps, 10))) nbSteps = 1; + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + let stepCounter = 1; + while ( + V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && + (!nbSteps || stepCounter < nbSteps) + ) { + rx += step[0]; + ry += step[1]; + stepCounter++; + } + if ( + V.OnBoard(rx, ry) && + this.board[rx][ry] != V.EMPTY && + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color + ) { + return true; + } + } + return false; + } + + isAttackedByLeopard(sq, color) { + return ( + super.isAttackedBySlideNJump( + sq, color, V.LEOPARD, V.steps[V.KNIGHT], "oneStep") || + this.isAttackedBySlideNJump(sq, color, V.LEOPARD, V.steps[V.BISHOP], 2) + ); + } + + isAttackedByCannon(sq, color) { + const steps = + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + .concat(V.Leap2Ortho).concat(V.HorizontalKnight); + return super.isAttackedBySlideNJump(sq, color, V.CANNON, steps, "oneStep"); + } + + isAttackedByUnicorn(sq, color) { + const steps = V.steps[V.KNIGHT].concat(V.CamelSteps) + return ( + super.isAttackedBySlideNJump(sq, color, V.UNICORN, steps, "oneStep") + ); + } + + isAttackedByElephant(sq, color) { + const steps = + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + .concat(V.Leap2Ortho) + .concat(V.Leap2Diago); + return ( + super.isAttackedBySlideNJump(sq, color, V.ELEPHANT, steps, "oneStep") + ); + } + + isAttackedByHawk(sq, color) { + const steps = + V.Leap2Ortho.concat(V.Leap2Diago) + .concat(V.Leap3Ortho).concat(V.Leap3Diago); + return super.isAttackedBySlideNJump(sq, color, V.HAWK, steps, "oneStep"); + } + + isAttackedByFortress(sq, color) { + const steps = V.Leap2Ortho.concat(V.VerticalKnight) + return ( + super.isAttackedBySlideNJump(sq, color, V.FORTRESS, steps, "oneStep") || + this.isAttackedBySlideNJump(sq, color, V.FORTRESS, V.steps[V.BISHOP], 3) + ); + } + + isAttackedBySpider(sq, color) { + const steps = V.Leap2Ortho.concat(V.steps[V.KNIGHT]) + return ( + super.isAttackedBySlideNJump(sq, color, V.SPIDER, steps, "oneStep") || + this.isAttackedBySlideNJump(sq, color, V.SPIDER, V.steps[V.BISHOP], 2) + ); + } + + getCheckSquares() { + if (this.movesCount <= 6) return []; + return super.getCheckSquares(); + } + + // At movesCount == 0,1: show full reserves [minus chosen piece1] + // At movesCount == 2,3: show reserve with only 2 selected pieces + // At movesCount == 4,5: show reserve with only piece2 + // Then, no reserve. + postPlay(move) { + if (this.movesCount > 6) super.postPlay(move); + else { + switch (this.movesCount) { + case 1: + this.reserve['w'][move.appear[0].p]--; + this.reserve['b'][move.appear[0].p]--; + this.extraPieces[0] = move.appear[0].p; + break; + case 2: + this.extraPieces[1] = move.appear[0].p; + for (let p of V.RESERVE_PIECES) { + const resVal = (this.extraPieces.includes(p) ? 1 : 0); + this.reserve['w'][p] = resVal; + this.reserve['b'][p] = resVal; + } + break; + case 3: + this.reserve['w'][this.extraPieces[0]]--; + break; + case 4: + this.reserve['b'][this.extraPieces[0]]--; + break; + case 5: + this.reserve['w'][this.extraPieces[1]]--; + break; + case 6: + this.reserve = null; + this.board[3][3] = ""; + this.board[3][4] = ""; + break; + } + } + } + + postUndo(move) { + if (this.movesCount >= 6) super.postUndo(move); + else { + switch (this.movesCount) { + case 0: + this.reserve['w'][move.appear[0].p]++; + this.reserve['b'][move.appear[0].p]++; + this.extraPieces[0] = '-'; + break; + case 1: + this.extraPieces[1] = '-'; + for (let p of V.RESERVE_PIECES) { + const resVal = (p != this.extraPieces[0] ? 1 : 0); + this.reserve['w'][p] = resVal; + this.reserve['b'][p] = resVal; + } + break; + case 2: + this.reserve['w'][this.extraPieces[0]]++; + break; + case 3: + this.reserve['b'][this.extraPieces[0]]++; + break; + case 4: + this.reserve['w'][this.extraPieces[1]]++; + break; + case 5: + this.reserve = { w: {}, b: {} }; + for (let c of ['w', 'b']) + V.RESERVE_PIECES.forEach(p => this.reserve[c][p] = 0); + this.reserve['b'][this.extraPieces[1]] = 1; + this.board[3][3] = 'b' + this.extraPieces[1]; + this.board[3][4] = 'w' + this.extraPieces[0]; + break; + } + } + } + + getComputerMove() { + if (this.movesCount >= 6) return super.getComputerMove(); + // Choose a move at random + const moves = this.getAllValidMoves(); + return moves[randInt(moves.length)]; + } + + static get SEARCH_DEPTH() { + return 2; + } + + evalPosition() { + let evaluation = 0; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] != V.EMPTY) { + const sign = this.getColor(i, j) == "w" ? 1 : -1; + const piece = this.getPiece(i, j); + evaluation += sign * V.VALUES[piece]; + const symbol = this.board[i][j][1]; + if (V.AUGMENTED_PIECES.includes(symbol)) { + const extraPiece = this.getExtraPiece(symbol); + evaluation += sign * V.VALUES[extraPiece] + } + } + } + } + return evaluation; + } + + static get VALUES() { + return Object.assign( + { + d: 6.7, + w: 7.5, + x: 5.6, + e: 6.3, + h: 5.5, + f: 7.6, + y: 8.15 + }, + ChessRules.VALUES + ); + } + + static get ExtraDictionary() { + return { + [V.LEOPARD]: { prefix: 'L', name: "Leopard" }, + [V.CANNON]: { prefix: 'C', name: "Cannon" }, + [V.UNICORN]: { prefix: 'U', name: "Unicorn" }, + [V.ELEPHANT]: { prefix: 'E', name: "Elephant" }, + [V.HAWK]: { prefix: 'H', name: "Hawk" }, + [V.FORTRESS]: { prefix: 'F', name: "Fortress" }, + [V.SPIDER]: { prefix: 'S', name: "Spider" } + } + } + + getNotation(move) { + if (this.movesCount <= 5) { + if (this.movesCount <= 1) + return V.ExtraDictionary[move.appear[0].p].name; + // Put something on the board: + return ( + V.ExtraDictionary[V.RESERVE_PIECES[move.start.y]].prefix + + "@" + V.CoordsToSquare(move.end) + ); + } + let notation = ""; + if ( + V.AUGMENTED_PIECES.includes(move.vanish[0].p) || + ( + move.vanish.length >= 2 && + V.AUGMENTED_PIECES.includes(move.vanish[1].p) + ) + ) { + // Simplify move before calling super.getNotation() + let smove = JSON.parse(JSON.stringify(move)); + if (ChessRules.PIECES.includes(move.vanish[0].p)) { + // Castle with an augmented rook + smove.appear.pop(); + smove.vanish[1].p = smove.appear[1].p; + } + else { + // Moving an augmented piece + smove.appear.pop(); + smove.vanish[0].p = smove.appear[0].p; + if ( + smove.vanish.length == 2 && + smove.vanish[0].c == smove.vanish[1].c && + V.AUGMENTED_PIECES.includes(move.vanish[1].p) + ) { + // Castle with an augmented rook + smove.appear.pop(); + smove.vanish[1].p = smove.appear[1].p; + } + } + notation = super.getNotation(smove); + } + // Else, more common case: + notation = super.getNotation(move); + const pieceSymbol = notation.charAt(0).toLowerCase(); + if (move.vanish[0].p != V.PAWN && V.RESERVE_PIECES.includes(pieceSymbol)) + notation = V.ExtraDictionary[pieceSymbol].prefix + notation.substr(1); + return notation; + } + +}; diff --git a/client/src/variants/Titan.js b/client/src/variants/Titan.js index e9b0594f..9e651b0d 100644 --- a/client/src/variants/Titan.js +++ b/client/src/variants/Titan.js @@ -145,8 +145,8 @@ export class TitanRules extends ChessRules { const initialPiece = this.getPiece(x, y); const color = this.turn; if ( - V.AUGMENTED_PIECES.includes(this.board[x][y][1]) && - ((color == 'w' && x == 7) || (color == "b" && x == 0)) + ((color == 'w' && x == 7) || (color == "b" && x == 0)) && + V.AUGMENTED_PIECES.includes(this.board[x][y][1]) ) { const newPiece = this.getExtraPiece(this.board[x][y][1]); moves.forEach(m => { @@ -160,28 +160,28 @@ export class TitanRules extends ChessRules { }) ); }); + moves.forEach(m => { + if (m.vanish.length <= 1) return; + const [vx, vy] = [m.vanish[1].x, m.vanish[1].y]; + if ( + m.appear.length >= 2 && //3 if the king was also augmented + m.vanish.length == 2 && + m.vanish[1].c == color && + V.AUGMENTED_PIECES.includes(this.board[vx][vy][1]) + ) { + // Castle, rook is an "augmented piece" + m.appear[1].p = V.ROOK; + m.appear.push( + new PiPo({ + p: this.getExtraPiece(this.board[vx][vy][1]), + c: color, + x: vx, + y: vy + }) + ); + } + }); } - moves.forEach(m => { - if (m.vanish.length <= 1) return; - const [vx, vy] = [m.vanish[1].x, m.vanish[1].y]; - if ( - m.appear.length >= 2 && //3 if the king was also augmented - m.vanish.length == 2 && - m.vanish[1].c == color && - V.AUGMENTED_PIECES.includes(this.board[vx][vy][1]) - ) { - // Castle, rook is an "augmented piece" - m.appear[1].p = V.ROOK; - m.appear.push( - new PiPo({ - p: this.getExtraPiece(this.board[vx][vy][1]), - c: color, - x: vx, - y: vy - }) - ); - } - }); return moves; } diff --git a/server/db/populate.sql b/server/db/populate.sql index be0f09bc..b6d0bf78 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -33,6 +33,7 @@ insert or ignore into Variants (name, description) values ('Bishopawns', 'Bishop versus pawns'), ('Brotherhood', 'Friendly pieces'), ('Cannibal', 'Capture powers'), + ('Capablanca', 'Capablanca Chess'), ('Capture', 'Mandatory captures'), ('Castle', 'Win by castling long'), ('Checkered1', 'Shared pieces (v1)'), @@ -94,6 +95,7 @@ insert or ignore into Variants (name, description) values ('Minixiangqi', 'Xiangqi 7 x 7'), ('Monochrome', 'All of the same color'), ('Monster', 'White move twice'), + ('Musketeer', 'New fairy pieces'), ('Omega', 'A wizard in the corner'), ('Orda', 'Mongolian Horde (v1)'), ('Ordamirror', 'Mongolian Horde (v2)'),