From e023d74715d327ebd7623079cb778b0def0a1464 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sun, 17 Jan 2021 01:10:26 +0100 Subject: [PATCH] Add Fusion and Selfabsorption --- client/public/images/pieces/Fusion/bc.svg | 1 + client/public/images/pieces/Fusion/bd.svg | 1 + client/public/images/pieces/Fusion/bf.svg | 1 + client/public/images/pieces/Fusion/bg.svg | 1 + client/public/images/pieces/Fusion/bm.svg | 1 + client/public/images/pieces/Fusion/wc.svg | 1 + client/public/images/pieces/Fusion/wd.svg | 1 + client/public/images/pieces/Fusion/wf.svg | 1 + client/public/images/pieces/Fusion/wg.svg | 1 + client/public/images/pieces/Fusion/wm.svg | 1 + client/src/translations/en.js | 2 + client/src/translations/es.js | 2 + client/src/translations/fr.js | 2 + client/src/translations/rules/Fusion/en.pug | 84 ++++ client/src/translations/rules/Fusion/es.pug | 98 ++++ client/src/translations/rules/Fusion/fr.pug | 90 ++++ .../translations/rules/Selfabsorption/en.pug | 11 + .../translations/rules/Selfabsorption/es.pug | 11 + .../translations/rules/Selfabsorption/fr.pug | 11 + client/src/translations/variants/en.pug | 2 + client/src/translations/variants/es.pug | 2 + client/src/translations/variants/fr.pug | 2 + client/src/variants/Absorption.js | 9 +- client/src/variants/Fusion.js | 437 ++++++++++++++++++ client/src/variants/Selfabsorption.js | 67 +++ server/db/populate.sql | 2 + 26 files changed, 839 insertions(+), 3 deletions(-) create mode 120000 client/public/images/pieces/Fusion/bc.svg create mode 120000 client/public/images/pieces/Fusion/bd.svg create mode 120000 client/public/images/pieces/Fusion/bf.svg create mode 120000 client/public/images/pieces/Fusion/bg.svg create mode 120000 client/public/images/pieces/Fusion/bm.svg create mode 120000 client/public/images/pieces/Fusion/wc.svg create mode 120000 client/public/images/pieces/Fusion/wd.svg create mode 120000 client/public/images/pieces/Fusion/wf.svg create mode 120000 client/public/images/pieces/Fusion/wg.svg create mode 120000 client/public/images/pieces/Fusion/wm.svg create mode 100644 client/src/translations/rules/Fusion/en.pug create mode 100644 client/src/translations/rules/Fusion/es.pug create mode 100644 client/src/translations/rules/Fusion/fr.pug create mode 100644 client/src/translations/rules/Selfabsorption/en.pug create mode 100644 client/src/translations/rules/Selfabsorption/es.pug create mode 100644 client/src/translations/rules/Selfabsorption/fr.pug create mode 100644 client/src/variants/Fusion.js create mode 100644 client/src/variants/Selfabsorption.js diff --git a/client/public/images/pieces/Fusion/bc.svg b/client/public/images/pieces/Fusion/bc.svg new file mode 120000 index 00000000..1200186b --- /dev/null +++ b/client/public/images/pieces/Fusion/bc.svg @@ -0,0 +1 @@ +../Alice/bo.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/bd.svg b/client/public/images/pieces/Fusion/bd.svg new file mode 120000 index 00000000..78e54f8d --- /dev/null +++ b/client/public/images/pieces/Fusion/bd.svg @@ -0,0 +1 @@ +../Perfect/bs.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/bf.svg b/client/public/images/pieces/Fusion/bf.svg new file mode 120000 index 00000000..b30a26ad --- /dev/null +++ b/client/public/images/pieces/Fusion/bf.svg @@ -0,0 +1 @@ +../Alice/bc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/bg.svg b/client/public/images/pieces/Fusion/bg.svg new file mode 120000 index 00000000..09e6ea3e --- /dev/null +++ b/client/public/images/pieces/Fusion/bg.svg @@ -0,0 +1 @@ +../Alice/bu.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/bm.svg b/client/public/images/pieces/Fusion/bm.svg new file mode 120000 index 00000000..d3aaacf7 --- /dev/null +++ b/client/public/images/pieces/Fusion/bm.svg @@ -0,0 +1 @@ +../Perfect/be.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/wc.svg b/client/public/images/pieces/Fusion/wc.svg new file mode 120000 index 00000000..4a85712d --- /dev/null +++ b/client/public/images/pieces/Fusion/wc.svg @@ -0,0 +1 @@ +../Alice/wo.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/wd.svg b/client/public/images/pieces/Fusion/wd.svg new file mode 120000 index 00000000..b0eb8b97 --- /dev/null +++ b/client/public/images/pieces/Fusion/wd.svg @@ -0,0 +1 @@ +../Perfect/ws.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/wf.svg b/client/public/images/pieces/Fusion/wf.svg new file mode 120000 index 00000000..d23af91d --- /dev/null +++ b/client/public/images/pieces/Fusion/wf.svg @@ -0,0 +1 @@ +../Alice/wc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/wg.svg b/client/public/images/pieces/Fusion/wg.svg new file mode 120000 index 00000000..c1403b33 --- /dev/null +++ b/client/public/images/pieces/Fusion/wg.svg @@ -0,0 +1 @@ +../Alice/wu.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fusion/wm.svg b/client/public/images/pieces/Fusion/wm.svg new file mode 120000 index 00000000..f47feb0e --- /dev/null +++ b/client/public/images/pieces/Fusion/wm.svg @@ -0,0 +1 @@ +../Perfect/we.svg \ No newline at end of file diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 9501efe2..90a4fa15 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -215,6 +215,8 @@ export const translations = { "Four new pieces": "Four new pieces", "Free initial setup": "Free initial setup", "Friendly pieces": "Friendly pieces", + "Fusion pieces (v1)": "Fusion pieces (v1)", + "Fusion pieces (v2)": "Fusion pieces (v2)", "In the shadow": "In the shadow", "Interweaved colorbound teams": "Interweaved colorbound teams", "General's Chess": "General's Chess", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 152ee64c..650aea92 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -215,6 +215,8 @@ export const translations = { "Four new pieces": "Quatro nuevas piezas", "Free initial setup": "Posición inicial libre", "Friendly pieces": "Piezas amistosas", + "Fusion pieces (v1)": "Fusionar piezas (v1)", + "Fusion pieces (v2)": "Fusionar piezas (v2)", "In the shadow": "En la sombra", "Interweaved colorbound teams": "Equipos unicolores entrelazados", "General's Chess": "Ajedrez de los Generales", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 2c6809a4..5a04fab5 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -215,6 +215,8 @@ export const translations = { "Four new pieces": "Quatre nouvelles pièces", "Free initial setup": "Position initiale libre", "Friendly pieces": "Pièces amies", + "Fusion pieces (v1)": "Fusionner les pièces (v1)", + "Fusion pieces (v2)": "Fusionner les pièces (v2)", "In the shadow": "Dans l'ombre", "Interweaved colorbound teams": "Équipes unicolores entremêlées", "General's Chess": "Échecs des Généraux", diff --git a/client/src/translations/rules/Fusion/en.pug b/client/src/translations/rules/Fusion/en.pug new file mode 100644 index 00000000..f2a25724 --- /dev/null +++ b/client/src/translations/rules/Fusion/en.pug @@ -0,0 +1,84 @@ +p.boxed + | Pieces can fusion and also be split under certain circumstances. + +p Fusion Chess is played like FIDE Chess with the following exceptions: + +h3 Fusion + +p. + A non-royal simple piece (Knight, Bishop, or Rook) may combine with + another simple piece on the same side (including a King) by moving + onto its square. + +p The combined piece moves as either of the two pieces just combined: +ul + li King + Bishop = Pontiff + li King + Rook = Dragon King + li King + Knight = Cavalier King + li Bishop + Rook = Queen + li Bishop + Knight = Paladin + li Rook + Knight = Marshall + +p A piece may not combine with +ul + li another piece of the same type, + li a piece belonging to the opponent, + li a compound piece. + +p. + A King may not move to combine any piece, though other another + piece may move to combine with its King. + +figure.diagram-container + .diagram.diag12 + | fen:r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPPBPPP/RNBQK2R: + .diagram.diag22 + | fen:r1bqkbm1/pp1ppppp/2n5/2p5/4P3/5D2/PPPP1PPP/RNBQK2R: + figcaption Before and after moves BxNf3 and RxNg8. + +h3 Fission + +p. + An unattacked compound piece may split by moving one + of its components, under its own powers of movement, to an empty square. +ul + li A Rook which separates from a piece must move away as a Rook moves. + li A Bishop which separates from a piece must move away as a Bishop moves. + li A Knight which separates from a piece must move away as a Knight moves. + li A King which separates from a piece must move away as a King moves. +p The compound piece is replaced by the component which doesn't move away. + +h3 Other details + +p Any piece that has participated in fission or fusion may no longer castle. + +p Pawns may promote to Rook, Bishop, or Knight only. + +p. + The object is to checkmate your opponent's current royal piece, which may + be a King, Pontiff, Dragon King, or Cavalier King. + +p. + No royal piece may move through check when moving more than one space. + For the Cavalier King, the Knight move is understood as beginning + orthogonally and turning diagonally or vice versa. If both spaces + it might pass over to reach a square are checked, it cannot move there. + However, this does not limit its ability to check or attack a piece. + +figure.diagram-container + .diagram + | fen:r3br2/c2p4/1pd2pqp/p1B5/1D1P1QP1/8/PPP1P2P/2RG2N1 c8: + +p. + On the diagram above, the Cavalier King only non-splitting move as a Knight + is to c8. Indeed, a6 and b6 are under attack. b8 as well, but not b7. + +h3 More information + +p + | See the + a(href="https://www.chessvariants.com/other.dir/fusion.html") + | chessvariants page + | . + +p Inventor: Fergus Duniho (1999) diff --git a/client/src/translations/rules/Fusion/es.pug b/client/src/translations/rules/Fusion/es.pug new file mode 100644 index 00000000..de85107b --- /dev/null +++ b/client/src/translations/rules/Fusion/es.pug @@ -0,0 +1,98 @@ +p.boxed + | Las piezas pueden fusionarse y también dividirse en + | ciertas circunstancias. + +p. + Ajedrez Fusión se juega como el juego habitual, + con las siguientes excepciones: + +h3 Fusión + +p. + Una sola pieza no real (Alfil, Caballo o Torre) se puede combinar con + otra pieza del mismo lado (incluido el Rey) moviéndose a su casilla. + +p La pieza combinada evoluciona como una u otra de las piezas unidas: +ul + li Rey + Alfil = Pontífice + li Rey + Torre = Rey Dragón + li Rey + Caballo = Rey Caballero + li Alfil + Torre = Dama + li Alfil + Caballo = Paladín + li Torre + Caballo = Mariscal + +p Una pieza no se puede combinar con +ul + li otra pieza del mismo tipo, + li una pieza que pertenece al oponente, + li una pieza compuesta. + +p. + Un Rey no puede moverse para combinar con una pieza, pero una + pieza puede realizar el desplazamiento que lleva a la fusión. + +figure.diagram-container + .diagram.diag12 + | fen:r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPPBPPP/RNBQK2R: + .diagram.diag22 + | fen:r1bqkbm1/pp1ppppp/2n5/2p5/4P3/5D2/PPPP1PPP/RNBQK2R: + figcaption Antes y después de los movimientos BxNf3 y RxNg8. + +h3 Fisión + +p. + Una pieza compuesta no atacada puede partirse moviendo uno de sus + componentes, según su tipo de movimiento, a un cuadrado vacío. +ul + li. + Una Torre que se separa de una pieza debe alejarse mediante + un movimiento de Torre. + li. + Un Alfil que se separa de una pieza debe alejarse después de un + movimiento de Alfil. + li. + Un Caballo que se separa de una pieza debe + alejarse después de un movimiento de Caballo. + li. + Un Rey que se separa de una pieza debe alejarse mediante + un movimiento de Rey. +p La pieza compuesta se reemplaza por el componente no inicial. + +h3 Otros detalles + +p. + Cualquier pieza que haya participado en una fisión o fusión + ya no puede enrocarse. + +p Los peones solo se promueven a Torre, Alfil o Caballo. + +p. + El objetivo es matar a la actual pieza real opuesta, esta + puede ser un Rey, Pontífice, Rey Dragón o Rey Caballo. + +p. + Una pieza real que se mueva más allá de un cuadrado no debe pasar + plazas atacadas. Respecto al Rey Caballero, un movimiento de Caballo + se entiende como un primer paso ortogonal antes de un giro diagonal, + o viceversa. Si los dos espacios que se pueden cruzar para alcanzar + una casilla está bajo control, no puede ir a esa casilla. + Sin embargo, eso no le impide dar jaque o atacar una pieza. + +figure.diagram-container + .diagram + | fen:r3br2/c2p4/1pd2pqp/p1B5/1D1P1QP1/8/PPP1P2P/2RG2N1 c8: + +p. + En el diagrama anterior, el único movimiento que no se separa del Rey + Caballero es hacia c8. De hecho, a6 y b6 son atacados. + b8 también, pero no b7. + +h3 Más información + +p + | Ver la + a(href="https://www.chessvariants.com/other.dir/fusion.html") + | página chessvariants + | . + +p Inventor: Fergus Duniho (1999) diff --git a/client/src/translations/rules/Fusion/fr.pug b/client/src/translations/rules/Fusion/fr.pug new file mode 100644 index 00000000..3339f77f --- /dev/null +++ b/client/src/translations/rules/Fusion/fr.pug @@ -0,0 +1,90 @@ +p.boxed + | Les pièces peuvent fusionner et également être divisées dans + | certaines circonstances. + +p. + Les Échecs Fusion se jouent comme le jeu habituel, + aux exceptions suivantes près : + +h3 Fusion + +p. + Une pièce non royale simple (Fou, Cavalier, ou Tour) peut se combiner avec + une autre pièce simple du même camp (incluant le Roi) en se déplaçant vers + sa case. + +p La pièce combinée évolue comme l'une ou l'autre des pièces réunies : +ul + li Roi + Fou = Pontife + li Roi + Tour = Roi Dragon + li Roi + Cavalier = Roi Cavalier + li Fou + Tour = Dame + li Fou + Cavalier = Paladin + li Tour + Cavalier = Maréchal + +p Une pièce ne peut pas être combinée avec +ul + li une autre pièce du même type, + li une pièce appartenant à l'adversaire, + li une pièce composée. + +p. + Un Roi ne peut pas se déplacer pour se combiner avec une pièce, mais une + pièce peut effectuer le déplacement menant à la fusion. + +figure.diagram-container + .diagram.diag12 + | fen:r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPPBPPP/RNBQK2R: + .diagram.diag22 + | fen:r1bqkbm1/pp1ppppp/2n5/2p5/4P3/5D2/PPPP1PPP/RNBQK2R: + figcaption Avant et après les coup BxNf3 et RxNg8. + +h3 Fission + +p. + Une pièce composée non attaquée peut se diviser en déplaçant une de ses + composantes, selon son type de mouvement, vers une case vide. +ul + li Une Tour se séparant d'une pièce doit s'éloigner via un coup de Tour. + li Un Fou se séparant d'une pièce doit s'éloigner suivant un coup de Fou. + li. + Un Cavalier se séparant d'une pièce doit + s'éloigner suivant un coup de Cavalier. + li Un Roi se séparant d'une pièce doit s'éloigner via un coup de Roi. +p La pièce composée est remplacée par la composante ne partant pas. + +h3 Autres détails + +p Toute pièce ayant participé à une fission ou fusion ne peut plus roquer. + +p Les pions sont promus en Tour, Fou ou Cavalier seulement. + +p. + L'objectif est de mater la pièce adverse actuellement royale, celle-ci + pouvant être un Roi, un Pontife, Un Roi Dragon ou Roi Cavalier. + +p. + Une pièce royale se déplaçant au-delà d'une case ne doit pas passer par + des cases attaquées. Concernant le Roi Cavalier, un coup de Cavalier + s'entend comme un premier pas orthogonal avant un virage diagonal, + ou vice-versa. Si les deux espaces pouvant être traversés afin d'atteindre + une case sont en échec, il ne peut pas se rendre sur cette case. + Cependant, cela ne l'empêche pas de donner échec ou d'attaquer une pièce. + +figure.diagram-container + .diagram + | fen:r3br2/c2p4/1pd2pqp/p1B5/1D1P1QP1/8/PPP1P2P/2RG2N1 c8: + +p. + Sur le diagramme ci-dessus, le seul coup non séparant du Roi Cavalier est + vers c8. En effet, a6 et b6 sont attaquées. b8 aussi, mais pas b7. + +h3 Plus d'information + +p + | Voir la + a(href="https://www.chessvariants.com/other.dir/fusion.html") + | page chessvariants + | . + +p Inventeur : Fergus Duniho (1999) diff --git a/client/src/translations/rules/Selfabsorption/en.pug b/client/src/translations/rules/Selfabsorption/en.pug new file mode 100644 index 00000000..943773c1 --- /dev/null +++ b/client/src/translations/rules/Selfabsorption/en.pug @@ -0,0 +1,11 @@ +p.boxed + | Capture your own pieces to fusion their power. + +p + | Capturing a rook, knight, bishop or queen of your color with a piece + | from this list but of a different nature is allowed. + | The result adds up the pieces' abilities, as in + a(href="/#/variants/Absorption") Absorption + |  variant. + +p This was suggested by Vincent Rothuis in 2020. diff --git a/client/src/translations/rules/Selfabsorption/es.pug b/client/src/translations/rules/Selfabsorption/es.pug new file mode 100644 index 00000000..e2f91754 --- /dev/null +++ b/client/src/translations/rules/Selfabsorption/es.pug @@ -0,0 +1,11 @@ +p.boxed + | Captura tus propias piezas para fusionar sus poderes. + +p + | Puede capturar una torre, un caballo, un alfil o una dama de su + | color con una pieza de este listado pero de una naturaleza diferente. + | El resultado suma los movimientos de las piezas, como en la variante + a(href="/#/variants/Absorption") Absorption + | . + +p Esto fue sugerido por Vincent Rothuis en 2020. diff --git a/client/src/translations/rules/Selfabsorption/fr.pug b/client/src/translations/rules/Selfabsorption/fr.pug new file mode 100644 index 00000000..a03d3da2 --- /dev/null +++ b/client/src/translations/rules/Selfabsorption/fr.pug @@ -0,0 +1,11 @@ +p.boxed + | Capturez vos propres pièces pour fusionner leurs pouvoirs. + +p + | Vous pouvez capturer une tour, un cavalier, un fou ou une dame de votre + | couleur avec une pièce de cette liste mais de nature différente. + | Le résultat somme les déplacements des pièces, comme dans la variante + a(href="/#/variants/Absorption") Absorption + | . + +p Ceci a été suggéré par Vincent Rothuis en 2020. diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index aea01ff7..542c2d48 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -446,6 +446,7 @@ p. "Bicolour", "Evolution", "Forward", + "Fusion", "Gridolina", "Hamilton", "Kingsmaker", @@ -453,6 +454,7 @@ p. "Refusal", "Relayup", "Rollerball", + "Selfabsorption", "Takenmake", "Wormhole" ] diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index e087a42e..09fd1169 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -456,6 +456,7 @@ p. "Bicolour", "Evolution", "Forward", + "Fusion", "Gridolina", "Hamilton", "Kingsmaker", @@ -463,6 +464,7 @@ p. "Refusal", "Relayup", "Rollerball", + "Selfabsorption", "Takenmake", "Wormhole" ] diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index bb640db0..144ad2df 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -454,6 +454,7 @@ p. "Bicolour", "Evolution", "Forward", + "Fusion", "Gridolina", "Hamilton", "Kingsmaker", @@ -461,6 +462,7 @@ p. "Refusal", "Relayup", "Rollerball", + "Selfabsorption", "Takenmake", "Wormhole" ] diff --git a/client/src/variants/Absorption.js b/client/src/variants/Absorption.js index c6e1de74..33c83517 100644 --- a/client/src/variants/Absorption.js +++ b/client/src/variants/Absorption.js @@ -91,10 +91,13 @@ export class AbsorptionRules extends ChessRules { ); }); moves.forEach(m => { - if (m.vanish.length == 2) { + if ( + m.vanish.length == 2 && + m.appear.length == 1 && + piece != m.vanish[1].p + ) { // Augment pieces abilities in case of captures - const piece2 = m.vanish[1].p; - if (piece != piece2) m.appear[0].p = V.Fusion(piece, piece2); + m.appear[0].p = V.Fusion(piece, m.vanish[1].p); } }); return moves; diff --git a/client/src/variants/Fusion.js b/client/src/variants/Fusion.js new file mode 100644 index 00000000..2ead9ad7 --- /dev/null +++ b/client/src/variants/Fusion.js @@ -0,0 +1,437 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; + +export class FusionRules extends ChessRules { + + static get PawnSpecs() { + return ( + Object.assign( + { promotions: [V.ROOK, V.KNIGHT, V.BISHOP] }, + ChessRules.PawnSpecs + ) + ); + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + let kings = { "k": 0, "K": 0 }; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (['K', 'F', 'G', 'C'].includes(row[i])) kings['K']++; + else if (['k', 'f', 'g', 'c'].includes(row[i])) kings['k']++; + if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num) || num <= 0) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + if (Object.values(kings).some(v => v != 1)) return false; + return true; + } + + scanKings(fen) { + this.kingPos = { w: [-1, -1], b: [-1, -1] }; + const fenRows = V.ParseFen(fen).position.split("/"); + for (let i = 0; i < fenRows.length; i++) { + let k = 0; + for (let j = 0; j < fenRows[i].length; j++) { + const ch_ij = fenRows[i].charAt(j); + if (['k', 'f', 'g', 'c'].includes(ch_ij)) + this.kingPos["b"] = [i, k]; + else if (['K', 'F', 'G', 'C'].includes(ch_ij)) + this.kingPos["w"] = [i, k]; + else { + const num = parseInt(fenRows[i].charAt(j), 10); + if (!isNaN(num)) k += num - 1; + } + k++; + } + } + } + + canTake([x1, y1], [x2, y2]) { + if (this.getColor(x1, y1) !== this.getColor(x2, y2)) return true; + const p1 = this.getPiece(x1, y1); + const p2 = this.getPiece(x2, y2); + return ( + p1 != p2 && + [V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) && + [V.KING, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2) + ); + } + + getPpath(b) { + if ([V.BN, V.RN, V.KB, V.KR, V.KN].includes(b[1])) return "Fusion/" + b; + return b; + } + + // Three new pieces: rook+knight, bishop+knight and queen+knight + static get RN() { + // Marshall + return 'm'; + } + static get BN() { + // Paladin + return 'd'; + } + static get KB() { + // Pontiff + return 'f'; + } + static get KR() { + // Dragon King + return 'g'; + } + static get KN() { + // Cavalier King + return 'c'; + } + + static get PIECES() { + return ChessRules.PIECES.concat([V.RN, V.BN, V.KB, V.KR, V.KN]); + } + + static Fusion(p1, p2) { + if (p2 == V.KING) { + switch (p1) { + case V.ROOK: return V.KR; + case V.BISHOP: return V.KB; + case V.KNIGHT: return V.KN; + } + } + if ([p1, p2].includes(V.KNIGHT)) { + if ([p1, p2].includes(V.ROOK)) return V.RN; + return V.BN; + } + // Only remaining combination is rook + bishop = queen + return V.QUEEN; + } + + getPotentialMovesFrom(sq) { + let moves = []; + const piece = this.getPiece(sq[0], sq[1]); + switch (piece) { + case V.RN: + moves = + super.getPotentialRookMoves(sq).concat( + super.getPotentialKnightMoves(sq)).concat( + this.getFissionMoves(sq)); + break; + case V.BN: + moves = + super.getPotentialBishopMoves(sq).concat( + super.getPotentialKnightMoves(sq)).concat( + this.getFissionMoves(sq)); + break; + case V.KN: + moves = + super.getPotentialKingMoves(sq).concat( + this.getPotentialKingAsKnightMoves(sq)).concat( + this.getFissionMoves(sq)); + break; + case V.KB: + moves = + super.getPotentialKingMoves(sq).concat( + this.getPotentialKingAsBishopMoves(sq)).concat( + this.getFissionMoves(sq)); + break; + case V.KR: + moves = + super.getPotentialKingMoves(sq).concat( + this.getPotentialKingAsRookMoves(sq)).concat( + this.getFissionMoves(sq)); + break; + case V.QUEEN: + moves = + super.getPotentialQueenMoves(sq).concat(this.getFissionMoves(sq)); + break; + default: + moves = super.getPotentialMovesFrom(sq); + break; + } + moves.forEach(m => { + if ( + m.vanish.length == 2 && + m.appear.length == 1 && + m.vanish[0].c == m.vanish[1].c + ) { + // Augment pieces abilities in case of self-captures + m.appear[0].p = V.Fusion(piece, m.vanish[1].p); + } + }); + return moves; + } + + getSlideNJumpMoves_fission([x, y], moving, staying, steps, oneStep) { + let moves = []; + const c = this.getColor(x, y); + outerLoop: for (let step of steps) { + let i = x + step[0]; + let j = y + step[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push( + new Move({ + appear: [ + new PiPo({ x: i, y: j, c: c, p: moving }), + new PiPo({ x: x, y: y, c: c, p: staying }), + ], + vanish: [ + new PiPo({ x: x, y: y, c: c, p: this.getPiece(x, y) }) + ] + }) + ); + if (!!oneStep) continue outerLoop; + i += step[0]; + j += step[1]; + } + } + return moves; + } + + getFissionMoves(sq) { + // Square attacked by opponent? + const color = this.getColor(sq[0], sq[1]); + const oppCol = V.GetOppCol(color); + if (this.isAttacked(sq, oppCol)) return []; + // Ok, fission a priori valid + const kSteps = V.steps[V.BISHOP].concat(V.steps[V.BISHOP]); + switch (this.getPiece(sq[0], sq[1])) { + case V.BN: + return ( + this.getSlideNJumpMoves_fission( + sq, V.BISHOP, V.KNIGHT, V.steps[V.BISHOP]) + .concat(this.getSlideNJumpMoves_fission( + sq, V.KNIGHT, V.BISHOP, V.steps[V.KNIGHT], "oneStep")) + ); + case V.RN: + return ( + this.getSlideNJumpMoves_fission( + sq, V.ROOK, V.KNIGHT, V.steps[V.ROOK]) + .concat(this.getSlideNJumpMoves_fission( + sq, V.KNIGHT, V.ROOK, V.steps[V.KNIGHT], "oneStep")) + ); + case V.KN: + return ( + this.getSlideNJumpMoves_fission(sq, V.KING, V.KNIGHT, kSteps) + .concat(this.getSlideNJumpMoves_fission( + sq, V.KNIGHT, V.KING, V.steps[V.KNIGHT], "oneStep")) + ); + case V.KB: + return ( + this.getSlideNJumpMoves_fission(sq, V.KING, V.BISHOP, kSteps) + .concat(this.getSlideNJumpMoves_fission( + sq, V.BISHOP, V.KING, V.steps[V.BISHOP])) + ); + case V.KR: + return ( + this.getSlideNJumpMoves_fission(sq, V.KING, V.ROOK, kSteps) + .concat(this.getSlideNJumpMoves_fission( + sq, V.ROOK, V.KING, V.steps[V.ROOK])) + ); + case V.QUEEN: + return ( + this.getSlideNJumpMoves_fission( + sq, V.BISHOP, V.ROOK, V.steps[V.BISHOP]) + .concat(this.getSlideNJumpMoves_fission( + sq, V.ROOK, V.BISHOP, V.steps[V.ROOK])) + ); + } + } + + intermediateSquaresFromKnightStep(step) { + if (step[0] == 2) return [ [1, 0], [1, step[1]] ]; + if (step[0] == -2) return [ [-1, 0], [-1, step[1]] ]; + if (step[1] == 2) return [ [0, 1], [step[1], 1] ]; + // step[1] == -2: + return [ [0, -1], [step[1], -1] ]; + } + + getPotentialKingAsKnightMoves([x, y]) { + const oppCol = V.GetOppCol(this.turn); + let moves = []; + let intermediateOk = {}; + for (let s of V.steps[V.KNIGHT]) { + const [i, j] = [x + s[0], y + s[1]]; + if (!V.OnBoard(i, j) || this.board[i][j] != V.EMPTY) continue; + const iSq = this.intermediateSquaresFromKnightStep(s); + let moveOk = false; + for (let sq of iSq) { + const key = sq[0] + "_" + sq[1]; + if (Object.keys(intermediateOk).includes(key)) { + if (intermediateOk[key]) moveOk = true; + } + else { + moveOk = !this.isAttacked([x + sq[0], y + sq[1]], oppCol); + intermediateOk[key] = moveOk; + } + if (moveOk) break; + } + if (moveOk) moves.push(this.getBasicMove([x, y], [i, j])); + } + return moves; + } + + getPotentialKingMovesAsSlider([x, y], slider) { + const oppCol = V.GetOppCol(this.turn); + let moves = []; + for (let s of V.steps[slider]) { + let [i, j] = [x + s[0], y + s[1]]; + if ( + !V.OnBoard(i, j) || + this.board[i][j] != V.EMPTY || + this.isAttacked([i, j], oppCol) + ) { + continue; + } + i += s[0]; + j += s[1]; + while ( + V.OnBoard(i, j) && + this.board[i][j] == V.EMPTY && + // TODO: this test will be done twice (also in filterValid()) + !this.isAttacked([i, j], oppCol) + ) { + moves.push(this.getBasicMove([x, y], [i, j])); + i += s[0]; + j += s[1]; + } + } + return moves; + } + + getPotentialKingAsBishopMoves(sq) { + return this.getPotentialKingMovesAsSlider(sq, V.BISHOP); + } + + getPotentialKingAsRookMoves(sq) { + return this.getPotentialKingMovesAsSlider(sq, V.ROOK); + } + + isAttacked(sq, color) { + return ( + super.isAttacked(sq, color) || + this.isAttackedByBN(sq, color) || + this.isAttackedByRN(sq, color) || + this.isAttackedByKN(sq, color) || + this.isAttackedByKB(sq, color) || + this.isAttackedByKR(sq, color) + ); + } + + isAttackedByBN(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.BN, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump( + sq, color, V.BN, V.steps[V.KNIGHT], "oneStep") + ); + } + + isAttackedByRN(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.RN, V.steps[V.ROOK]) || + this.isAttackedBySlideNJump( + sq, color, V.RN, V.steps[V.KNIGHT], "oneStep") + ); + } + + isAttackedByKN(sq, color) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return ( + this.isAttackedBySlideNJump(sq, color, V.KN, steps, "oneStep") || + this.isAttackedBySlideNJump( + sq, color, V.KN, V.steps[V.KNIGHT], "oneStep") + ); + } + + isAttackedByKB(sq, color) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return ( + this.isAttackedBySlideNJump(sq, color, V.KB, steps, "oneStep") || + this.isAttackedBySlideNJump(sq, color, V.KB, V.steps[V.BISHOP]) + ); + } + + isAttackedByKR(sq, color) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return ( + this.isAttackedBySlideNJump(sq, color, V.KR, steps, "oneStep") || + this.isAttackedBySlideNJump(sq, color, V.KR, V.steps[V.ROOK]) + ); + } + + updateCastleFlags(move, piece) { + const c = V.GetOppCol(this.turn); + const firstRank = (c == "w" ? V.size.x - 1 : 0); + const oppCol = this.turn; + const oppFirstRank = V.size.x - 1 - firstRank; + if (piece == V.KING) + this.castleFlags[c] = [V.size.y, V.size.y]; + else if ( + move.start.x == firstRank && //our rook moves? + this.castleFlags[c].includes(move.start.y) + ) { + const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } + // Check move endpoint: if my king or any rook position, flags off + if (move.end.x == this.kingPos[c][0] && move.end.y == this.kingPos[c][1]) + this.castleFlags[c] = [V.size.y, V.size.y]; + else if ( + move.end.x == firstRank && + this.castleFlags[c].includes(move.end.y) + ) { + const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } + else if ( + move.end.x == oppFirstRank && + this.castleFlags[oppCol].includes(move.end.y) + ) { + const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); + this.castleFlags[oppCol][flagIdx] = V.size.y; + } + } + + postPlay(move) { + const c = V.GetOppCol(this.turn); + const piece = move.appear[0].p; + if ([V.KING, V.KN, V.KB, V.KR].includes(piece)) + this.kingPos[c] = [move.appear[0].x, move.appear[0].y]; + this.updateCastleFlags(move, piece); + } + + postUndo(move) { + const c = this.getColor(move.start.x, move.start.y); + if ([V.KING, V.KN, V.KB, V.KR].includes(move.appear[0].p)) + this.kingPos[c] = [move.start.x, move.start.y]; + } + + static get VALUES() { + // Values such that sum of values = value of sum + return Object.assign( + { m: 8, d: 6, f: 1003, g: 1005, c: 1003 }, + ChessRules.VALUES + ); + } + + getNotation(move) { + if (move.appear.length == 2 && move.vanish.length == 1) { + // Fission (because no capture in this case) + return ( + move.appear[0].p.toUpperCase() + V.CoordsToSquare(move.end) + + "/f:" + move.appear[1].p.toUpperCase() + V.CoordsToSquare(move.start) + ); + } + let notation = super.getNotation(move); + if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p) + // Fusion (not from a pawn: handled in ChessRules) + notation += "=" + move.appear[0].p.toUpperCase(); + return notation; + } + +}; diff --git a/client/src/variants/Selfabsorption.js b/client/src/variants/Selfabsorption.js new file mode 100644 index 00000000..edfa09f5 --- /dev/null +++ b/client/src/variants/Selfabsorption.js @@ -0,0 +1,67 @@ +import { ChessRules } from "@/base_rules"; +import { AbsorptionRules } from "@/variants/Absorption"; + +export class SelfabsorptionRules extends AbsorptionRules { + + canTake([x1, y1], [x2, y2]) { + if (this.getColor(x1, y1) !== this.getColor(x2, y2)) return true; + const p1 = this.getPiece(x1, y1); + const p2 = this.getPiece(x2, y2); + return ( + p1 != p2 && + [V.QUEEN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) && + [V.QUEEN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2) + ); + } + + getPotentialMovesFrom(sq) { + let moves = []; + const piece = this.getPiece(sq[0], sq[1]); + switch (piece) { + case V.RN: + moves = + super.getPotentialRookMoves(sq).concat( + super.getPotentialKnightMoves(sq)); + break; + case V.BN: + moves = + super.getPotentialBishopMoves(sq).concat( + super.getPotentialKnightMoves(sq)); + break; + case V.QN: + moves = + super.getPotentialQueenMoves(sq).concat( + super.getPotentialKnightMoves(sq)); + break; + case V.PAWN: + moves = super.getPotentialPawnMoves(sq); + break; + case V.ROOK: + moves = super.getPotentialRookMoves(sq); + break; + case V.KNIGHT: + moves = super.getPotentialKnightMoves(sq); + break; + case V.BISHOP: + moves = super.getPotentialBishopMoves(sq); + break; + case V.QUEEN: + moves = super.getPotentialQueenMoves(sq); + break; + case V.KING: + moves = super.getPotentialKingMoves(sq); + break; + } + moves.forEach(m => { + if ( + m.vanish.length == 2 && m.appear.length == 1 && + piece != m.vanish[1].p && m.vanish[0].c == m.vanish[1].c + ) { + // Augment pieces abilities in case of self-captures + m.appear[0].p = V.Fusion(piece, m.vanish[1].p); + } + }); + return moves; + } + +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 122224d1..5322ce99 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -66,6 +66,7 @@ insert or ignore into Variants (name, description) values ('Freecapture', 'Capture both colors'), ('Fugue', 'Baroque Music'), ('Fullcavalry', 'Lancers everywhere'), + ('Fusion', 'Fusion pieces (v1)'), ('Grand', 'Big board'), ('Grasshopper', 'Long jumps over pieces'), ('Gridolina', 'Jump the borders'), @@ -128,6 +129,7 @@ insert or ignore into Variants (name, description) values ('Rugby', 'Transform an essay'), ('Schess', 'Seirawan-Harper Chess'), ('Screen', 'Free initial setup'), + ('Selfabsorption', 'Fusion pieces (v2)'), ('Shako', 'Non-conformism and utopia'), ('Shatranj', 'Ancient rules'), ('Shogi', 'Japanese Chess'), -- 2.44.0