From 6b7b2cf720e6255e4da0dc34fee363c456346a58 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Thu, 12 Mar 2020 12:11:22 +0100 Subject: [PATCH] Some fixes, work on Eightpieces draft, add a few capturing variants --- TODO | 5 +- client/src/base_rules.js | 4 +- client/src/components/Settings.vue | 4 +- client/src/translations/en.js | 6 +- client/src/translations/es.js | 6 +- client/src/translations/fr.js | 6 +- client/src/translations/rules/Capture/en.pug | 22 ++ client/src/translations/rules/Capture/es.pug | 23 ++ client/src/translations/rules/Capture/fr.pug | 23 ++ client/src/translations/rules/Losers/en.pug | 29 +-- client/src/translations/rules/Losers/es.pug | 33 +-- client/src/translations/rules/Losers/fr.pug | 31 +-- .../src/translations/rules/Racingkings/en.pug | 30 +++ .../src/translations/rules/Racingkings/es.pug | 31 +++ .../src/translations/rules/Racingkings/fr.pug | 31 +++ .../src/translations/rules/Royalrace/en.pug | 2 +- .../src/translations/rules/Royalrace/es.pug | 2 +- .../src/translations/rules/Royalrace/fr.pug | 2 +- client/src/translations/rules/Suicide/en.pug | 30 +++ client/src/translations/rules/Suicide/es.pug | 33 +++ client/src/translations/rules/Suicide/fr.pug | 33 +++ client/src/utils/gameStorage.js | 14 +- client/src/variants/Alice.js | 3 +- client/src/variants/Capture.js | 48 ++++ client/src/variants/Eightpieces.js | 143 ++++++++++-- client/src/variants/Extinction.js | 6 +- client/src/variants/Losers.js | 208 +++-------------- client/src/variants/Racingkings.js | 63 ++++++ client/src/variants/Royalrace.js | 13 +- client/src/variants/Suicide.js | 210 ++++++++++++++++++ client/src/variants/Threechecks.js | 2 +- client/src/views/Hall.vue | 7 +- client/src/views/MyGames.vue | 17 +- client/src/views/Rules.vue | 3 +- server/db/populate.sql | 7 +- 35 files changed, 825 insertions(+), 305 deletions(-) create mode 100644 client/src/translations/rules/Capture/en.pug create mode 100644 client/src/translations/rules/Capture/es.pug create mode 100644 client/src/translations/rules/Capture/fr.pug create mode 100644 client/src/translations/rules/Racingkings/en.pug create mode 100644 client/src/translations/rules/Racingkings/es.pug create mode 100644 client/src/translations/rules/Racingkings/fr.pug create mode 100644 client/src/translations/rules/Suicide/en.pug create mode 100644 client/src/translations/rules/Suicide/es.pug create mode 100644 client/src/translations/rules/Suicide/fr.pug create mode 100644 client/src/variants/Capture.js create mode 100644 client/src/variants/Racingkings.js create mode 100644 client/src/variants/Suicide.js diff --git a/TODO b/TODO index 51e8e48c..a993b216 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ +Revise variants code by using more aggregate/disaggregate flags functions? + # New variants 8-pieces https://www.youtube.com/watch?v=XZ8K02Da7Ps&list=PLRyjH8DPuzTBiym6lA0r84P8N0HnTtZyN&index=6&t=0s https://www.chessvariants.com/rules/8-piece-chess "Eightpieces" @@ -17,9 +19,6 @@ https://www.chessvariants.com/diffsetup.dir/unachess.html Rugby http://www.echecspourtous.com/?page_id=7945 -"Capture" Chess (idea of opperwezen, maybe not new): captures are forced, -but the goal is still to checkmate (not lose all material). - Cannibal Chess with forced captures. Knightrelay: implement "official" version as Knightrelay v1 diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 1c5eabca..33b0fc90 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -1058,7 +1058,7 @@ export const ChessRules = class ChessRules { play(move) { // DEBUG: // if (!this.states) this.states = []; -// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); +// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen(); // this.states.push(stateFen); if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) @@ -1078,7 +1078,7 @@ export const ChessRules = class ChessRules { this.unupdateVariables(move); // DEBUG: -// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); +// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen(); // if (stateFen != this.states[this.states.length-1]) debugger; // this.states.pop(); } diff --git a/client/src/components/Settings.vue b/client/src/components/Settings.vue index 5b79e06e..d2194dae 100644 --- a/client/src/components/Settings.vue +++ b/client/src/components/Settings.vue @@ -60,7 +60,7 @@ div v-model="st.settings.gotonext" ) fieldset - label(for="setRandomness") {{ st.tr["Randomness against computer"] }} + label(for="setRandomness") {{ st.tr["Randomness"] }} select#setRandomness(v-model="st.settings.randomness") option(value="0") {{ st.tr["Deterministic"] }} option(value="1") {{ st.tr["Symmetric random"] }} @@ -110,7 +110,7 @@ export default { <style lang="sass" scoped> [type="checkbox"].modal+div .card - max-width: 767px + max-width: 520px max-height: 100% #flagContainer display: inline-block diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 2e69f302..3c0228c1 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -103,7 +103,6 @@ export const translations = { Problems: "Problems", "Random?": "Random?", "Randomness": "Randomness", - "Randomness against computer": "Randomness against computer", Refuse: "Refuse", Register: "Register", "Registration complete! Please check your emails now": "Registration complete! Please check your emails now", @@ -166,12 +165,15 @@ export const translations = { "Exotic captures": "Exotic captures", "Explosive captures": "Explosive captures", "In the shadow": "In the shadow", + "Get strong at self-mate": "Get strong at self-mate", "Give three checks": "Give three checks", "Keep antiking in check": "Keep antiking in check", - "Kings cross the board": "Kings cross the board", + "Kings cross the 8x8 board": "Kings cross the 8x8 board", + "Kings cross the 11x11 board": "Kings cross the 11x11 board", "Laws of attraction": "Laws of attraction", "Long jumps over pieces": "Long jumps over pieces", "Lose all pieces": "Lose all pieces", + "Mandatory captures": "Mandatory captures", "Mate any piece (v1)": "Mate any piece (v1)", "Mate any piece (v2)": "Mate any piece (v2)", "Mate the knight": "Mate the knight", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 944cbff9..f31fc202 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -103,7 +103,6 @@ export const translations = { Problems: "Problemas", "Random?": "Aleatorio?", "Randomness": "Grado de azar", - "Randomness against computer": "Grado de azar contra la computadora", Refuse: "Rechazar", Register: "Registrarse", "Registration complete! Please check your emails now": "¡Registro completo! Revise sus correos electrónicos ahora", @@ -166,12 +165,15 @@ export const translations = { "Exotic captures": "Capturas exóticas", "Explosive captures": "Capturas explosivas", "In the shadow": "En la sombra", + "Get strong at self-mate": "Progreso en mates asistidos", "Give three checks": "Dar tres jaques", "Keep antiking in check": "Mantener el antirey en jaque", - "Kings cross the board": "Los reyes cruzan el tablero", + "Kings cross the 8x8 board": "Los reyes cruzan el 8x8 tablero", + "Kings cross the 11x11 board": "Los reyes cruzan el 11x11 tablero", "Laws of attraction": "Las leyes de las atracciones", "Long jumps over pieces": "Saltos largos sobre las piezas", "Lose all pieces": "Perder todas las piezas", + "Mandatory captures": "Capturas obligatorias", "Mate any piece (v1)": "Matar cualquier pieza (v1)", "Mate any piece (v2)": "Matar cualquier pieza (v2)", "Mate the knight": "Matar el caballo", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 5e3108be..0a8d5532 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -103,7 +103,6 @@ export const translations = { Problems: "Problèmes", "Random?": "Aléatoire?", "Randomness": "Degré d'aléa", - "Randomness against computer": "Degré d'aléa contre l'ordinateur", Refuse: "Refuser", Register: "S'enregistrer", "Registration complete! Please check your emails now": "Enregistrement terminé ! Allez voir vos emails maintenant", @@ -166,12 +165,15 @@ export const translations = { "Exotic captures": "Captures exotiques", "Explosive captures": "Captures explosives", "In the shadow": "Dans l'ombre", + "Get strong at self-mate": "Progressez en mats aidés", "Give three checks": "Donnez trois échecs", "Keep antiking in check": "Gardez l'antiroi en échec", - "Kings cross the board": "Les rois traversent l'échiquier", + "Kings cross the 8x8 board": "Les rois traversent l'échiquier 8x8", + "Kings cross the 11x11 board": "Les rois traversent l'échiquier 11x11", "Laws of attraction": "Les lois de l'attraction", "Long jumps over pieces": "Sauts longs par dessus les pièces", "Lose all pieces": "Perdez toutes les pièces", + "Mandatory captures": "Captures obligatoires", "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)", "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)", "Mate the knight": "Matez le cavalier", diff --git a/client/src/translations/rules/Capture/en.pug b/client/src/translations/rules/Capture/en.pug new file mode 100644 index 00000000..5ed55414 --- /dev/null +++ b/client/src/translations/rules/Capture/en.pug @@ -0,0 +1,22 @@ +p.boxed + | Captures are mandatory. Win by checkmating. + +p. + Everything is the same as in orthodox rules, except that when possible, + captures are forced. + The goal is still to checkmate, and stalemate is a draw. + +p. + Some opening traps can be mentioned, like on the following diagram: + Qxd7+ will be forced, losing the queen. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppp1pp1/7p/4P3/8/8/PPP1PPPP/RNBQKBNR: + figcaption After 1.d4?? e5 2.dxe5 h6. + +h3 More information + +p. + This idea was suggested recently (2020) by Vincent Rothuis. + It's simple so probably not new, but I never saw it before. diff --git a/client/src/translations/rules/Capture/es.pug b/client/src/translations/rules/Capture/es.pug new file mode 100644 index 00000000..4994990e --- /dev/null +++ b/client/src/translations/rules/Capture/es.pug @@ -0,0 +1,23 @@ +p.boxed + Las capturas son obligatorias. Gana por jaque mate. + +p. + Todo va como el ajedrez ortodoxo, pero cuando es posible + las capturas son forzadas. + El objetivo aún es de matar, y el empate es tablas. + +p. + Se pueden mencionar algunas trampas de apertura, + como en el siguiente diagrama: Qxd7 será forzado, perdiendo a la dama. + +figure.diagram-container + .diagrama + | fen:rnbqkbnr/pppp1pp1/7p/4P3/8/8/PPP1PPPP/RNBQKBNR: + figcaption Después de 1.d4?? e5 2.dxe5 h6. + +h3 Más información + +p. + Esta idea fue sugerida recientemente (2020) por Vincent Rothuis. + Es simple, asà que probablemente no sea nuevo, pero nunca + previamente encontrado. diff --git a/client/src/translations/rules/Capture/fr.pug b/client/src/translations/rules/Capture/fr.pug new file mode 100644 index 00000000..21a03538 --- /dev/null +++ b/client/src/translations/rules/Capture/fr.pug @@ -0,0 +1,23 @@ +p.boxed + | Les captures sont obligatoires. Gagnez par échec et mat. + +p. + Tout se déroule comme aux échecs orthodoxes, mais quand elles sont possibles + les captures sont forcées. + L'objectif est toujours de mater, et le pat fait nulle. + +p. + Quelques pièges d'ouvertures peuvent être mentionnés, + comme sur le diagramme suivant : Qxd7 sera forcé, perdant la dame. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppp1pp1/7p/4P3/8/8/PPP1PPPP/RNBQKBNR: + figcaption Après 1.d4?? e5 2.dxe5 h6. + +h3 Plus d'information + +p. + Cette idée a été suggérée récemment (2020) par Vincent Rothuis. + Elle est simple donc probablement pas nouvelle, mais je ne l'ai jamais + rencontrée auparavant. diff --git a/client/src/translations/rules/Losers/en.pug b/client/src/translations/rules/Losers/en.pug index ff2b55d3..e258d9e0 100644 --- a/client/src/translations/rules/Losers/en.pug +++ b/client/src/translations/rules/Losers/en.pug @@ -1,28 +1,21 @@ p.boxed - | Win by losing all your pieces. Capture is mandatory. + | Win by losing all your pieces except the king. Capture is mandatory. p. - The goal is to lose all pieces, or get stalemated like on the following diagram. - The king has no royal status: it can be taken as any other piece. - Thus, there is no castle rule, no checks. - -p Captures are mandatory, but when several capturing moves are possible you can choose. + The goal is to lose all pieces except the king, or get stalemated + or checkmated like on the following diagram. + All usual chess rules apply, but captures are mandatory. + When several captures are possible you can choose: from the + diagram position 1.Nf3?? and 1.g4?! allow respectively 1...Qxf3 and 1...Nxg4. figure.diagram-container .diagram - | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8: - figcaption White cannot move: 1-0. - -h3 Special moves - -p. - Castling is not possible, but en-passant captures are allowed. - Pawns may promote into king (so you can potentially have several kings on the board). + | fen:r1b1kb1r/p2ppp1p/2q2np1/8/7P/7R/PP1PPPP1/RNB1KBN1: + figcaption 1.g3 forces 1...Qxc1# checkmate h3 More information p - | This is a popular variant, played in many places on the web. - | A starting point can be the - a(href="https://en.wikipedia.org/wiki/Losing_Chess") Wikipedia page - | . + | This variant can be played + a(href="https://www.freechess.org/Help/HelpFiles/losers.html") on FICS + | . We follow their naming choice. diff --git a/client/src/translations/rules/Losers/es.pug b/client/src/translations/rules/Losers/es.pug index ef0f3399..f4e95220 100644 --- a/client/src/translations/rules/Losers/es.pug +++ b/client/src/translations/rules/Losers/es.pug @@ -1,30 +1,21 @@ p.boxed - | Gana perdiendo todas tus piezas. Capturas obligatorias. + | Gana perdiendo todas tus piezas excepto el rey. Capturas obligatorias. p. - El objetivo es perder todas sus piezas (peones incluidos), o ser empate - como en el siguiente diagrama. - El rey no tiene un estado especial: puede ser capturado. - No hay noción de jaque, ni de enroque. - -p La captura es obligatoria, pero si es posible, puede elegir. + El objetivo es perder todas sus piezas excepto el rey, o ser empate + o jaque mate como en el siguiente diagrama. + Se aplican todas las reglas habituales, pero las capturas son obligatorias. + Cuando son posibles varias capturas, puede elegir: desde + posición del diagrama 1.Nf3?? y 1.g4?! permiten 1...Qxf3 y 1...Nxg4 respectivamente. figure.diagram-container - .diagram - | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8: - figcaption Las blancas no puede moverse: 1-0. - -h3 Movimientos especiales - -p. - El enroque no está permitido, pero las capturas al pasar son posibles. - Un peón puede ser promovido a rey (por lo que potencialmente puede haber - varios reyes en el tablero). + .diagrama + | fen:r1b1kb1r/p2ppp1p/2q2np1/8/7P/7R/PP1PPPP1/RNB1KBN1: + figcaption 1.g3 fuerza 1...Qxc1# jaque mate h3 Más información p - | Esta variante es muy popular y se juega en varios lugares en Internet. - | la - a(href="https://en.wikipedia.org/wiki/Losing_Chess") página Wikipedia - | contiene varias referencias. + | Podemos jugar esta variante + a(href="https://www.freechess.org/Help/HelpFiles/losers.html") en FICS + | . Seguimos su elección de nombre. diff --git a/client/src/translations/rules/Losers/fr.pug b/client/src/translations/rules/Losers/fr.pug index d509f938..507ce030 100644 --- a/client/src/translations/rules/Losers/fr.pug +++ b/client/src/translations/rules/Losers/fr.pug @@ -1,30 +1,21 @@ p.boxed - | Gagnez en perdant toutes vos pièces. Captures obligatoires. + | Gagnez en perdant toutes vos pièces sauf le roi. Captures obligatoires. p. - L'objectif est de perdre toutes ses pièces (pions inclus), ou d'être pat - comme sur le diagramme suivant. - Le roi n'a pas de statut particulier : il peut être capturé. - Il n'y a pas de notion d'échec, et pas de roque. - -p Les captures sont obligatoires, mais si plusieurs sont possibles vous pouvez choisir. + L'objectif est de perdre toutes ses pièces sauf le roi, ou d'être pat + ou mat comme sur le diagramme suivant. + Toutes les règles habituelles s'appliquent, mais les capturees sont obligatoires. + Quand plusieurs captures sont possibles vous pouvez choisir : depuis la + position du diagramme 1.Nf3?? et 1.g4?! permettent respectivement 1...Qxf3 et 1...Nxg4. figure.diagram-container .diagram - | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8: - figcaption Les blancs ne peuvent pas bouger : 1-0. - -h3 Coups spéciaux - -p. - Le roque n'est pas permis, mais les prises en passant sont possibles. - Un pion peut se promouvoir en roi (donc il peut potentiellement y avoir - plusieurs rois sur l'échiquier). + | fen:r1b1kb1r/p2ppp1p/2q2np1/8/7P/7R/PP1PPPP1/RNB1KBN1: + figcaption 1.g3 force 1...Qxc1# échec et mat h3 Plus d'information p - | Cette variante est très populaire, et est jouée en divers endroits sur internet. - | La - a(href="https://en.wikipedia.org/wiki/Losing_Chess") page Wikipedia - | est un point de départ possible. + | On peut jouer à cette variante + a(href="https://www.freechess.org/Help/HelpFiles/losers.html") sur FICS + | . Nous suivons leur choix de nom. diff --git a/client/src/translations/rules/Racingkings/en.pug b/client/src/translations/rules/Racingkings/en.pug new file mode 100644 index 00000000..1eb092e1 --- /dev/null +++ b/client/src/translations/rules/Racingkings/en.pug @@ -0,0 +1,30 @@ +p.boxed + | Bring your king to the other side of the board. + | Giving check is not allowed. + +p. + To win your king must arrive on the 8th rank. + He can never be in check (thus there is no checkmate). + Stalemate would be a draw, but it seldom occur in this variant. + +p. + The basic strategy is to erect barriers on the way of the opponent king, + with rooks and queen for example, while your king advance next to it. + +figure.diagram-container + .diagram + | fen:8/8/8/8/8/8/krbnNBRK/qrbnNBRQ: + figcaption Initial position. + +p. + Note: the initial position is always the same. + See the "Royalrace" variant for a randomized version. + +h3 More information + +p + a(href="https://www.chessvariants.com/diffobjective.dir/racing.html") + | Racing Kings variant + | on chessvariants.com. + +p Inventor: Vernon R. Parton (1961) diff --git a/client/src/translations/rules/Racingkings/es.pug b/client/src/translations/rules/Racingkings/es.pug new file mode 100644 index 00000000..00e18c1f --- /dev/null +++ b/client/src/translations/rules/Racingkings/es.pug @@ -0,0 +1,31 @@ +p.boxed + | Lleva a tu rey al otro lado del tablero. + | Las jaques no están permitidas. + +p. + Para ganar tu rey debes alcanzar en la 8ma fila. + Ãl nunca puede estar en jaque (por lo que no hay jaque mate). + El empate significarÃa tablas, pero rara vez ocurre en esta variante. + +p. + La estrategia básica es erigir barreras en el camino del rey contrario, + con torres y la dama, por ejemplo, mientras tu rey avanza al lado. + +figure.diagram-container + .diagram + | fen:8/8/8/8/8/8/krbnNBRK/qrbnNBRQ: + figcaption Position initiale. + +p. + Nota: la posición inicial es siempre la misma. + Para una versión aleatoria, vea la variante "Royalrace". + +h3 Más información + +p + | La + a(href="https://www.chessvariants.com/diffobjective.dir/racing.html") + | variante Racing Kings + | en chessvariants.com. + +p Inventor: Vernon R. Parton (1961) diff --git a/client/src/translations/rules/Racingkings/fr.pug b/client/src/translations/rules/Racingkings/fr.pug new file mode 100644 index 00000000..41d0091f --- /dev/null +++ b/client/src/translations/rules/Racingkings/fr.pug @@ -0,0 +1,31 @@ +p.boxed + | Amenez votre roi de l'autre côté de l'échiquier. + | Les échecs ne sont pas autorisés. + +p. + Pour gagner votre roi doit parvenir sur la 8eme rangée. + Il ne peut jamais être en échec (il n'y a donc pas de mat). + Le pat signifierait match nul, mais il arrive très rarement dans cette variante. + +p. + La stratégie de base consiste à ériger des barrières sur le chemin du roi adverse, + avec des tours et la dame par exemple, tandis que votre roi avance à côté. + +figure.diagram-container + .diagram + | fen:8/8/8/8/8/8/krbnNBRK/qrbnNBRQ: + figcaption Position initiale. + +p. + Note : la position de départ est toujours la même. + Pour une version aléatoire, voir la variante "Royalrace". + +h3 Plus d'information + +p + | La + a(href="https://www.chessvariants.com/diffobjective.dir/racing.html") + | variante Racing Kings + | sur chessvariants.com. + +p Inventeur : Vernon R. Parton (1961) diff --git a/client/src/translations/rules/Royalrace/en.pug b/client/src/translations/rules/Royalrace/en.pug index 25c95ea0..8198d101 100644 --- a/client/src/translations/rules/Royalrace/en.pug +++ b/client/src/translations/rules/Royalrace/en.pug @@ -32,4 +32,4 @@ p | Strongly inspired by the Racing Kings variant invented by Vernon R. Parton (1961), | playable for example a(href="https://lichess.org/variant/racingKings") on lichess - | . + | , and also here: see "Racingkings" variant :-) diff --git a/client/src/translations/rules/Royalrace/es.pug b/client/src/translations/rules/Royalrace/es.pug index 82b12a81..634f4b7c 100644 --- a/client/src/translations/rules/Royalrace/es.pug +++ b/client/src/translations/rules/Royalrace/es.pug @@ -34,4 +34,4 @@ p | Fuertemente inspirado por la variante Racing Kings inventado por Vernon | R. Parton (1961), jugable entre otros a(href="https://lichess.org/variant/racingKings") en lichess - | . + | , y también aquÃ: ver la variante "Racingkings" :-) diff --git a/client/src/translations/rules/Royalrace/fr.pug b/client/src/translations/rules/Royalrace/fr.pug index 921c030d..02f42f69 100644 --- a/client/src/translations/rules/Royalrace/fr.pug +++ b/client/src/translations/rules/Royalrace/fr.pug @@ -34,4 +34,4 @@ p | Fortement inspiré par la variante Racing Kings inventée par Vernon R. Parton (1961), | jouable entre autres a(href="https://lichess.org/variant/racingKings") sur lichess - | . + | , et également ici : voir la variante "Racingkings" :-) diff --git a/client/src/translations/rules/Suicide/en.pug b/client/src/translations/rules/Suicide/en.pug new file mode 100644 index 00000000..8ece4861 --- /dev/null +++ b/client/src/translations/rules/Suicide/en.pug @@ -0,0 +1,30 @@ +p.boxed + | Win by losing all your pieces. Capture is mandatory. + +p. + The goal is to lose all pieces, or get stalemated like on the following diagram. + The king has no royal status: it can be taken as any other piece. + Thus, there is no castle rule, no checks. + +p Captures are mandatory, but when several capturing moves are possible you can choose. + +figure.diagram-container + .diagram + | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8: + figcaption White cannot move: 1-0. + +h3 Special moves + +p. + Castling is not possible, but en-passant captures are allowed. + Pawns may promote into king (so you can potentially have several kings on the board). + +h3 More information + +p + | This is a popular variant, played in many places on the web. + | A starting point can be the + a(href="https://en.wikipedia.org/wiki/Losing_Chess") Wikipedia page + | . Note: this variant has several names, we choose here the same as + a(href="https://www.freechess.org/Help/HelpFiles/suicide_chess.html") on FICS + | . diff --git a/client/src/translations/rules/Suicide/es.pug b/client/src/translations/rules/Suicide/es.pug new file mode 100644 index 00000000..4122aaf4 --- /dev/null +++ b/client/src/translations/rules/Suicide/es.pug @@ -0,0 +1,33 @@ +p.boxed + | Gana perdiendo todas tus piezas. Capturas obligatorias. + +p. + El objetivo es perder todas sus piezas (peones incluidos), o ser empate + como en el siguiente diagrama. + El rey no tiene un estado especial: puede ser capturado. + No hay noción de jaque, ni de enroque. + +p La captura es obligatoria, pero si es posible, puede elegir. + +figure.diagram-container + .diagram + | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8: + figcaption Las blancas no puede moverse: 1-0. + +h3 Movimientos especiales + +p. + El enroque no está permitido, pero las capturas al pasar son posibles. + Un peón puede ser promovido a rey (por lo que potencialmente puede haber + varios reyes en el tablero). + +h3 Más información + +p + | Esta variante es muy popular y se juega en varios lugares en Internet. + | la + a(href="https://en.wikipedia.org/wiki/Losing_Chess") página Wikipedia + | contiene varias referencias. + | Nota: esta variante tiene varios nombres, elegimos aquà lo mismo que + a(href="https://www.freechess.org/Help/HelpFiles/suicide_chess.html") en FICS + | . diff --git a/client/src/translations/rules/Suicide/fr.pug b/client/src/translations/rules/Suicide/fr.pug new file mode 100644 index 00000000..5a5f735b --- /dev/null +++ b/client/src/translations/rules/Suicide/fr.pug @@ -0,0 +1,33 @@ +p.boxed + | Gagnez en perdant toutes vos pièces. Captures obligatoires. + +p. + L'objectif est de perdre toutes ses pièces (pions inclus), ou d'être pat + comme sur le diagramme suivant. + Le roi n'a pas de statut particulier : il peut être capturé. + Il n'y a pas de notion d'échec, et pas de roque. + +p Les captures sont obligatoires, mais si plusieurs sont possibles vous pouvez choisir. + +figure.diagram-container + .diagram + | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8: + figcaption Les blancs ne peuvent pas bouger : 1-0. + +h3 Coups spéciaux + +p. + Le roque n'est pas permis, mais les prises en passant sont possibles. + Un pion peut se promouvoir en roi (donc il peut potentiellement y avoir + plusieurs rois sur l'échiquier). + +h3 Plus d'information + +p + | Cette variante est très populaire, et est jouée en divers endroits sur internet. + | La + a(href="https://en.wikipedia.org/wiki/Losing_Chess") page Wikipedia + | est un point de départ possible. + | Note : cette variante a plusieurs noms, nous choisisson ici le même que + a(href="https://www.freechess.org/Help/HelpFiles/suicide_chess.html") sur FICS + | . diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js index 59efb43a..ba189491 100644 --- a/client/src/utils/gameStorage.js +++ b/client/src/utils/gameStorage.js @@ -83,8 +83,7 @@ export const GameStorage = { }, // Retrieve all local games (running, completed, imported...) - // light: do not retrieve moves or clocks (TODO: this is the only usage) - getAll: function(light, callback) { + getAll: function(callback) { dbOperation((err,db) => { let objectStore = db.transaction("games").objectStore("games"); let games = []; @@ -93,12 +92,11 @@ export const GameStorage = { // if there is still another cursor to go, keep running this code if (cursor) { let g = cursor.value; - if (light) { - g.movesCount = g.moves.length; - delete g.moves; - delete g.clocks; - delete g.initime; - } + // Do not retrieve moves or clocks (unused in list mode) + g.movesCount = g.moves.length; + delete g.moves; + delete g.clocks; + delete g.initime; games.push(g); cursor.continue(); } else callback(games); diff --git a/client/src/variants/Alice.js b/client/src/variants/Alice.js index d787c42e..4d6b3c37 100644 --- a/client/src/variants/Alice.js +++ b/client/src/variants/Alice.js @@ -107,8 +107,7 @@ export const VariantRules = class AliceRules extends ChessRules { // Finally filter impossible moves const res = moves.filter(m => { if (m.appear.length == 2) { - //castle - // appear[i] must be an empty square on the other board + // Castle: appear[i] must be an empty square on the other board for (let psq of m.appear) { if (this.getSquareOccupation(psq.x, psq.y, 3 - mirrorSide) != V.EMPTY) return false; diff --git a/client/src/variants/Capture.js b/client/src/variants/Capture.js new file mode 100644 index 00000000..a43f1671 --- /dev/null +++ b/client/src/variants/Capture.js @@ -0,0 +1,48 @@ +import { ChessRules } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +export const VariantRules = class LosersRules extends ChessRules { + // Trim all non-capturing moves + static KeepCaptures(moves) { + return moves.filter(m => m.vanish.length == 2); + } + + // Stop at the first capture found (if any) + atLeastOneCapture() { + const color = this.turn; + const oppCol = V.GetOppCol(color); + 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 && + this.getColor(i, j) != oppCol && + this.getPotentialMovesFrom([i, j]).some(m => + // Warning: duscard castle moves + m.vanish.length == 2 && m.appear.length == 1) + ) { + return true; + } + } + } + return false; + } + + getPossibleMovesFrom(sq) { + let moves = this.filterValid(this.getPotentialMovesFrom(sq)); + const captureMoves = V.KeepCaptures(moves); + if (captureMoves.length > 0) return captureMoves; + if (this.atLeastOneCapture()) return []; + return moves; + } + + getAllValidMoves() { + const moves = super.getAllValidMoves(); + if (moves.some(m => m.vanish.length == 2)) return V.KeepCaptures(moves); + return moves; + } + + static get SEARCH_DEPTH() { + return 4; + } +}; diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index 8240dd2a..dfab43d7 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -46,23 +46,23 @@ export const VariantRules = class EightpiecesRules extends ChessRules { static ParseFen(fen) { const fenParts = fen.split(" "); return Object.assign(ChessRules.ParseFen(fen), { - sentrypath: fenParts[5] + sentrypush: fenParts[5] }); } getFen() { - return super.getFen() + " " + this.getSentrypathFen(); + return super.getFen() + " " + this.getSentrypushFen(); } getFenForRepeat() { - return super.getFenForRepeat() + "_" + this.getSentrypathFen(); + return super.getFenForRepeat() + "_" + this.getSentrypushFen(); } - getSentrypathFen() { - const L = this.sentryPath.length; - if (!this.sentryPath[L-1]) return "-"; + getSentrypushFen() { + const L = this.sentryPush.length; + if (!this.sentryPush[L-1]) return "-"; let res = ""; - this.sentryPath[L-1].forEach(coords => + this.sentryPush[L-1].forEach(coords => res += V.CoordsToSquare(coords) + ","); return res.slice(0, -1); } @@ -73,10 +73,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules { this.subTurn = 1; // Stack pieces' forbidden squares after a sentry move at each turn const parsedFen = V.ParseFen(fen); - if (parsedFen.sentrypath == "-") this.sentryPath = [null]; + if (parsedFen.sentrypush == "-") this.sentryPush = [null]; else { - this.sentryPath = [ - parsedFen.sentrypath.split(",").map(sq => { + this.sentryPush = [ + parsedFen.sentrypush.split(",").map(sq => { return V.SquareToCoords(sq); }) ]; @@ -91,7 +91,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules { } static GenRandInitFen(randomness) { - // TODO: special conditions + // TODO: special conditions for 960 return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -"; } @@ -141,18 +141,107 @@ export const VariantRules = class EightpiecesRules extends ChessRules { } } + // Is piece on square (x,y) immobilized? + isImmobilized([x, y]) { + const color = this.getColor(x, y); + const oppCol = V.GetOppCol(color); + for (let step of V.steps[V.ROOK]) { + const [i, j] = [x + step[0], y + step[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol + ) { + const oppPiece = this.getPiece(i, j); + if (oppPiece == V.JAILER) return [i, j]; + } + } + return null; + } + + getPotentialMovesFrom_aux([x, y]) { + switch (this.getPiece(x, y)) { + case V.JAILER: + return this.getPotentialJailerMoves([x, y]); + case V.SENTRY: + return this.getPotentialSentryMoves([x, y]); + case V.LANCER + return this.getPotentialLancerMoves([x, y]); + default: + return super.getPotentialMovesFrom([x, y]); + } + } + getPotentialMovesFrom([x,y]) { - // if subturn == 1, normal situation, allow moves except walking back on sentryPath, - // if last element isn't null in sentryPath array - // if subTurn == 2, allow only the end of the path (occupied by a piece) to move - // - // TODO: special pass move: take jailer with king, only if king immobilized - // Move(appear:[], vanish:[], start == king and end = jailer (for animation)) - // - // TODO: post-processing if sentryPath forbid some moves. - // + add all lancer possible orientations - // (except if just after a push: allow all movements from init square then) - // Test if last sentryPath ends at our position: if yes, OK + if (this.subTurn == 1) { + if (!!this.isImmobilized([x, y])) return []; + return this.getPotentialMovesFrom_aux([x, y]); + } + // subTurn == 2: only the piece pushed by the sentry is allowed to move, + // as if the sentry didn't exist + if (x != this.sentryPos.x && y != this.sentryPos.y) return []; + return this.getPotentialMovesFrom_aux([x,y]); + } + + getAllValidMoves() { + let moves = super.getAllValidMoves().filter(m => + // Remove jailer captures + m.vanish[0].p != V.JAILER || m.vanish.length == 1; + ); + const L = this.sentryPush.length; + if (!!this.sentryPush[L-1] && this.subTurn == 1) { + // Delete moves walking back on sentry push path + moves = moves.filter(m => { + if ( + m.vanish[0].p != V.PAWN && + this.sentryPush[L-1].some(sq => sq.x == m.end.x && sq.y == m.end.y) + ) { + return false; + } + return true; + }); + } + return moves; + } + + filterValid(moves) { + // Disable check tests when subTurn == 2, because the move isn't finished + if (this.subTurn == 2) return moves; + return super.filterValid(moves); + } + + getPotentialLancerMoves([x, y]) { + // TODO: add all lancer possible orientations same as pawn promotions, + // except if just after a push: allow all movements from init square then + return []; + } + + getPotentialSentryMoves([x, y]) { + // The sentry moves a priori like a bishop: + let moves = super.getPotentialBishopMoves([x, y]); + // ...but captures are replaced by special move + // "appear = [], vanish = init square" to let the pushed piece move then. + // TODO + } + + getPotentialJailerMoves([x, y]) { + // Captures are removed afterward: + return super.getPotentialRookMoves([x, y]); + } + + getPotentialKingMoves([x, y]) { + let moves = super.getPotentialKingMoves([x, y]); + // Augment with pass move is the king is immobilized: + const jsq = this.isImmobilized([x, y]); + if (!!jsq) { + moves.push(new Move({ + appear: [], + vanish: [], + start: { x: x, y: y }, + end: { x: jsq[0], y: jsq[1] } + }); + } + return moves; } // Adapted: castle with jailer possible @@ -241,10 +330,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules { // A piece is pushed: // TODO: push array of squares between start and end of move, included // (except if it's a pawn) - this.sentryPath.push([]); //TODO + this.sentryPush.push([]); //TODO this.subTurn = 1; } else { - if (move.appear.length == 0 && move.vanish.length == 0) { + if (move.appear.length == 0 && move.vanish.length == 1) { // Special sentry move: subTurn <- 2, and then move pushed piece this.subTurn = 2; } @@ -280,4 +369,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules { ChessRules.VALUES ); } + + getNotation(move) { + // Special case "king takes jailer" is a pass move + if (move.appear.length == 0 && move.vanish.length == 0) return "pass"; + return super.getNotation(move); + } }; diff --git a/client/src/variants/Extinction.js b/client/src/variants/Extinction.js index 8896292a..45ace336 100644 --- a/client/src/variants/Extinction.js +++ b/client/src/variants/Extinction.js @@ -85,8 +85,8 @@ export const VariantRules = class ExtinctionRules extends ChessRules { return true; //always at least one possible move } - underCheck() { - return false; //there is no check + filterValid(moves) { + return moves; //there is no check } getCheckSquares() { @@ -117,7 +117,7 @@ export const VariantRules = class ExtinctionRules extends ChessRules { getCurrentScore() { if (this.atLeastOneMove()) { - // game not over? + // Game not over? const color = this.turn; if ( Object.keys(this.material[color]).some(p => { diff --git a/client/src/variants/Losers.js b/client/src/variants/Losers.js index caebd297..d752f984 100644 --- a/client/src/variants/Losers.js +++ b/client/src/variants/Losers.js @@ -3,207 +3,73 @@ import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; export const VariantRules = class LosersRules extends ChessRules { - static get HasFlags() { - return false; - } - - getPotentialPawnMoves([x, y]) { - let moves = super.getPotentialPawnMoves([x, y]); - - // Complete with promotion(s) into king, if possible - const color = this.turn; - const shift = color == "w" ? -1 : 1; - const lastRank = color == "w" ? 0 : V.size.x - 1; - if (x + shift == lastRank) { - // Normal move - if (this.board[x + shift][y] == V.EMPTY) - moves.push( - this.getBasicMove([x, y], [x + shift, y], { c: color, p: V.KING }) - ); - // Captures - if ( - y > 0 && - this.canTake([x, y], [x + shift, y - 1]) && - this.board[x + shift][y - 1] != V.EMPTY - ) { - moves.push( - this.getBasicMove([x, y], [x + shift, y - 1], { c: color, p: V.KING }) - ); - } - if ( - y < V.size.y - 1 && - this.canTake([x, y], [x + shift, y + 1]) && - this.board[x + shift][y + 1] != V.EMPTY - ) { - moves.push( - this.getBasicMove([x, y], [x + shift, y + 1], { c: color, p: V.KING }) - ); - } - } - - return moves; - } - - getPotentialKingMoves(sq) { - // No castle: - return this.getSlideNJumpMoves( - sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), - "oneStep" - ); + // Trim all non-capturing moves + static KeepCaptures(moves) { + return moves.filter(m => m.vanish.length == 2); } - // Stop at the first capture found (if any) + // Stop at the first capture found (if any) atLeastOneCapture() { const color = this.turn; const oppCol = V.GetOppCol(color); 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 && this.getColor(i, j) != oppCol) { - const moves = this.getPotentialMovesFrom([i, j]); - if (moves.length > 0) { - for (let k = 0; k < moves.length; k++) { - if ( - moves[k].vanish.length == 2 && - this.filterValid([moves[k]]).length > 0 - ) - return true; - } - } + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) != oppCol && + this.getPotentialMovesFrom([i, j]).some(m => + // Warning: duscard castle moves + m.vanish.length == 2 && m.appear.length == 1) + ) { + return true; } } } return false; } - // Trim all non-capturing moves - static KeepCaptures(moves) { - return moves.filter(m => { - return m.vanish.length == 2; - }); - } - getPossibleMovesFrom(sq) { let moves = this.filterValid(this.getPotentialMovesFrom(sq)); - // This is called from interface: we need to know if a capture is possible - if (this.atLeastOneCapture()) moves = V.KeepCaptures(moves); + const captureMoves = V.KeepCaptures(moves); + if (captureMoves.length > 0) return captureMoves; + if (this.atLeastOneCapture()) return []; return moves; } getAllValidMoves() { - let moves = super.getAllValidMoves(); - if ( - moves.some(m => { - return m.vanish.length == 2; - }) - ) - moves = V.KeepCaptures(moves); + const moves = super.getAllValidMoves(); + if (moves.some(m => m.vanish.length == 2)) return V.KeepCaptures(moves); return moves; } - underCheck() { - return false; //No notion of check - } - - getCheckSquares() { - return []; - } - - // No variables update because no royal king + no castling - updateVariables() {} - unupdateVariables() {} - getCurrentScore() { - if (this.atLeastOneMove()) - // game not over - return "*"; - - // No valid move: the side who cannot move wins + // If only my king remains, I win + const color = this.turn; + let onlyKing = true; + outerLoop: 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 && + this.getColor(i,j) == color && + this.getPiece(i,j) != V.KING + ) { + onlyKing = false; + break outerLoop; + } + } + } + if (onlyKing) return color == "w" ? "1-0" : "0-1"; + if (this.atLeastOneMove()) return "*"; + // No valid move: the side who cannot move (or is checkmated) wins return this.turn == "w" ? "1-0" : "0-1"; } - static get VALUES() { - // Experimental... - return { - p: 1, - r: 7, - n: 3, - b: 3, - q: 5, - k: 5 - }; - } - static get SEARCH_DEPTH() { return 4; } evalPosition() { - return -super.evalPosition(); //better with less material - } - - static GenRandInitFen(randomness) { - if (randomness == 0) - return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 -"; - - let pieces = { w: new Array(8), b: new Array(8) }; - // Shuffle pieces on first and last rank - for (let c of ["w", "b"]) { - if (c == 'b' && randomness == 1) { - pieces['b'] = pieces['w']; - break; - } - - let positions = ArrayFun.range(8); - - // Get random squares for bishops - let randIndex = 2 * randInt(4); - let 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]; - // 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]; - positions.splice(randIndex, 1); - randIndex = randInt(5); - let knight2Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Get random square for queen - randIndex = randInt(4); - let queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Random square for king (no castle) - randIndex = randInt(3); - let kingPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Rooks positions are now fixed - let rook1Pos = positions[0]; - let rook2Pos = positions[1]; - - // 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][kingPos] = "k"; - pieces[c][bishop2Pos] = "b"; - pieces[c][knight2Pos] = "n"; - pieces[c][rook2Pos] = "r"; - } - return ( - pieces["b"].join("") + - "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - // En-passant allowed, but no flags - " w 0 -" - ); + // Less material is better (more subtle in fact but...) + return -super.evalPosition(); } }; diff --git a/client/src/variants/Racingkings.js b/client/src/variants/Racingkings.js new file mode 100644 index 00000000..21d5c85d --- /dev/null +++ b/client/src/variants/Racingkings.js @@ -0,0 +1,63 @@ +import { ChessRules } from "@/base_rules"; + +export const VariantRules = class RacingkingsRules extends ChessRules { + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get CanFlip() { + return false; + } + + static GenRandInitFen() { + return "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w 0"; + } + + filterValid(moves) { + if (moves.length == 0) return []; + const color = this.turn; + const oppCol = V.GetOppCol(color); + return moves.filter(m => { + this.play(m); + // Giving check is forbidden as well: + const res = !this.underCheck(color) && !this.underCheck(oppCol); + this.undo(m); + return res; + }); + } + + getCurrentScore() { + // If both kings arrived on the last rank, it's a draw + if (this.kingPos['w'][0] == 0 && this.kingPos['b'][0] == 0) return "1/2"; + // If after my move the opponent king is on last rank, I lose. + // This is only possible with black. + if (this.turn == 'w' && this.kingPos['w'][0] == 0) return "1-0"; + // Turn has changed: + const color = V.GetOppCol(this.turn); + if (this.kingPos[color][0] == 0) { + // The opposing edge is reached! + // If color is white and the black king can arrive on 8th rank + // at next move, then it should be a draw: + if (color == "w" && this.kingPos['b'][0] == 1) { + // Search for a move + const oppKingMoves = this.getPotentialKingMoves(this.kingPos['b']); + if (oppKingMoves.some(m => m.end.x == 0)) return "*"; + } + return color == "w" ? "1-0" : "0-1"; + } + if (this.atLeastOneMove()) return "*"; + // Stalemate (will probably never happen) + return "1/2"; + } + + evalPosition() { + // Count material: + let evaluation = super.evalPosition(); + // Ponder with king position: + return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0]; + } +}; diff --git a/client/src/variants/Royalrace.js b/client/src/variants/Royalrace.js index 9318c122..f5c7550a 100644 --- a/client/src/variants/Royalrace.js +++ b/client/src/variants/Royalrace.js @@ -21,7 +21,7 @@ export const VariantRules = class RoyalraceRules extends ChessRules { static GenRandInitFen(randomness) { if (randomness == 0) - return "11/11/11/11/11/11/11/11/11/QRBNP1pnbrq/KRBNP1pnbrk w 0"; + return "11/11/11/11/11/11/11/11/11/qrbnp1PNBRQ/krbnp1PNBRK w 0"; let pieces = { w: new Array(10), b: new Array(10) }; // Shuffle pieces on first and second rank @@ -93,13 +93,13 @@ export const VariantRules = class RoyalraceRules extends ChessRules { const blackFen = pieces["b"].join(""); return ( "11/11/11/11/11/11/11/11/11/" + - whiteFen.substr(5).split("").reverse().join("") + + blackFen.substr(5).split("").reverse().join("") + "1" + - blackFen.substr(5).split("").join("") + + whiteFen.substr(5).split("").join("") + "/" + - whiteFen.substr(0,5) + + blackFen.substr(0,5) + "1" + - blackFen.substr(0,5).split("").reverse().join("") + + whiteFen.substr(0,5).split("").reverse().join("") + " w 0" ); } @@ -189,8 +189,7 @@ export const VariantRules = class RoyalraceRules extends ChessRules { if (this.kingPos[color][0] == 0) // The opposing edge is reached! return color == "w" ? "1-0" : "0-1"; - if (this.atLeastOneMove()) - return "*"; + if (this.atLeastOneMove()) return "*"; // Stalemate (will probably never happen) return "1/2"; } diff --git a/client/src/variants/Suicide.js b/client/src/variants/Suicide.js new file mode 100644 index 00000000..f030b080 --- /dev/null +++ b/client/src/variants/Suicide.js @@ -0,0 +1,210 @@ +import { ChessRules } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +export const VariantRules = class SuicideRules extends ChessRules { + static get HasFlags() { + return false; + } + + getPotentialPawnMoves([x, y]) { + let moves = super.getPotentialPawnMoves([x, y]); + + // Complete with promotion(s) into king, if possible + const color = this.turn; + const shift = color == "w" ? -1 : 1; + const lastRank = color == "w" ? 0 : V.size.x - 1; + if (x + shift == lastRank) { + // Normal move + if (this.board[x + shift][y] == V.EMPTY) + moves.push( + this.getBasicMove([x, y], [x + shift, y], { c: color, p: V.KING }) + ); + // Captures + if ( + y > 0 && + this.canTake([x, y], [x + shift, y - 1]) && + this.board[x + shift][y - 1] != V.EMPTY + ) { + moves.push( + this.getBasicMove([x, y], [x + shift, y - 1], { c: color, p: V.KING }) + ); + } + if ( + y < V.size.y - 1 && + this.canTake([x, y], [x + shift, y + 1]) && + this.board[x + shift][y + 1] != V.EMPTY + ) { + moves.push( + this.getBasicMove([x, y], [x + shift, y + 1], { c: color, p: V.KING }) + ); + } + } + + return moves; + } + + getPotentialKingMoves(sq) { + // No castle: + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); + } + + // Trim all non-capturing moves (not the most efficient, but easy) + static KeepCaptures(moves) { + return moves.filter(m => m.vanish.length == 2); + } + + // Stop at the first capture found (if any) + atLeastOneCapture() { + const color = this.turn; + const oppCol = V.GetOppCol(color); + 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 && + this.getColor(i, j) != oppCol && + this.getPotentialMovesFrom([i, j]).some(m => m.vanish.length == 2) + ) { + return true; + } + } + } + return false; + } + + getPossibleMovesFrom(sq) { + let moves = this.getPotentialMovesFrom(sq); + const captureMoves = V.KeepCaptures(moves); + if (captureMoves.length > 0) return captureMoves; + if (this.atLeastOneCapture()) return []; + return moves; + } + + filterValid(moves) { + return moves; + } + + getAllValidMoves() { + const moves = super.getAllValidMoves(); + if (moves.some(m => m.vanish.length == 2)) return V.KeepCaptures(moves); + return moves; + } + + atLeastOneMove() { + const color = this.turn; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if ( + this.getColor(i, j) == color && + this.getPotentialMovesFrom([i, j]).length > 0 + ) { + return true; + } + } + } + return false; + } + + getCheckSquares() { + return []; + } + + // No variables update because no royal king + no castling + updateVariables() {} + unupdateVariables() {} + + getCurrentScore() { + if (this.atLeastOneMove()) return "*"; + // No valid move: the side who cannot move wins + return this.turn == "w" ? "1-0" : "0-1"; + } + + static get VALUES() { + return { + p: 1, + r: 7, + n: 3, + b: 3, + q: 5, + k: 5 + }; + } + + static get SEARCH_DEPTH() { + return 4; + } + + evalPosition() { + // Less material is better: + return -super.evalPosition(); + } + + static GenRandInitFen(randomness) { + if (randomness == 0) + return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 -"; + + let pieces = { w: new Array(8), b: new Array(8) }; + // Shuffle pieces on first and last rank + for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w']; + break; + } + + let positions = ArrayFun.range(8); + + // Get random squares for bishops + let randIndex = 2 * randInt(4); + let 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]; + // 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]; + positions.splice(randIndex, 1); + randIndex = randInt(5); + let knight2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random square for queen + randIndex = randInt(4); + let queenPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Random square for king (no castle) + randIndex = randInt(3); + let kingPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Rooks positions are now fixed + let rook1Pos = positions[0]; + let rook2Pos = positions[1]; + + // 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][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; + } + return ( + pieces["b"].join("") + + "/pppppppp/8/8/8/8/PPPPPPPP/" + + pieces["w"].join("").toUpperCase() + + // En-passant allowed, but no flags + " w 0 -" + ); + } +}; diff --git a/client/src/variants/Threechecks.js b/client/src/variants/Threechecks.js index c1d5c5a9..f3b77254 100644 --- a/client/src/variants/Threechecks.js +++ b/client/src/variants/Threechecks.js @@ -27,7 +27,7 @@ export const VariantRules = class ThreechecksRules extends ChessRules { getPpath(b) { // TODO: !!this.checkFlags condition for printDiagram, but clearly not good. // This is just a temporary fix. - if (b[1] == 'k' && this.checkFlags && this.checkFlags[b[0]] > 0) + if (b[1] == 'k' && !!this.checkFlags && this.checkFlags[b[0]] > 0) return "Threechecks/" + b[0] + 'k_' + this.checkFlags[b[0]]; return b; } diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 32d59639..d59946fb 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -227,7 +227,10 @@ export default { vid: parseInt(localStorage.getItem("vid")) || 0, to: "", //name of challenged player (if any) cadence: localStorage.getItem("cadence") || "", - randomness: parseInt(localStorage.getItem("randomness")) || 2, + randomness: + parseInt(localStorage.getItem("challRandomness")) || + // Default to global randomness if no challenges issued yet: + this.st.settings.randomness, // VariantRules object, stored to not interfere with // diagrams of targetted challenges: V: null, @@ -969,7 +972,7 @@ export default { // Remember cadence + vid for quicker further challenges: localStorage.setItem("cadence", chall.cadence); localStorage.setItem("vid", chall.vid); - localStorage.setItem("randomness", chall.randomness); + localStorage.setItem("challRandomness", chall.randomness); document.getElementById("modalNewgame").checked = false; // Show the challenge if not on current display if ( diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index 5faae8a5..c9c34f34 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -45,7 +45,7 @@ export default { }; }, created: function() { - GameStorage.getAll(true, localGames => { + GameStorage.getAll(localGames => { localGames.forEach(g => g.type = "live"); this.decorate(localGames); this.liveGames = localGames; @@ -117,17 +117,16 @@ export default { // Called at loading to augment games with myColor + myTurn infos decorate: function(games) { games.forEach(g => { - // If game is over, myColor and myTurn are ignored: + g.myColor = + (g.type == "corr" && g.players[0].uid == this.st.user.id) || + (g.type == "live" && g.players[0].sid == this.st.user.sid) + ? 'w' + : 'b'; + // If game is over, myTurn doesn't exist: if (g.score == "*") { - g.myColor = - (g.type == "corr" && g.players[0].uid == this.st.user.id) || - (g.type == "live" && g.players[0].sid == this.st.user.sid) - ? 'w' - : 'b'; const rem = g.movesCount % 2; - if ((rem == 0 && g.myColor == 'w') || (rem == 1 && g.myColor == 'b')) { + if ((rem == 0 && g.myColor == 'w') || (rem == 1 && g.myColor == 'b')) g.myTurn = true; - } } }); }, diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index 7830d26a..844457d6 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -153,7 +153,8 @@ export default { }, gotoAnalyze: function() { this.$router.push( - "/analyse/" + this.gameInfo.vname + "/?fen=" + V.GenRandInitFen(2) + "/analyse/" + this.gameInfo.vname + + "/?fen=" + V.GenRandInitFen(this.st.settings.randomness) ); } } diff --git a/server/db/populate.sql b/server/db/populate.sql index 4060a442..6a233f44 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -11,6 +11,7 @@ insert or ignore into Variants (name,description) values ('Baroque', 'Exotic captures'), ('Benedict', 'Change colors'), ('Berolina', 'Pawns move diagonally'), + ('Capture', 'Mandatory captures'), ('Checkered', 'Shared pieces'), ('Chess960', 'Standard rules'), ('Circular', 'Run forward'), @@ -26,13 +27,15 @@ insert or ignore into Variants (name,description) values ('Hiddenqueen', 'Queen disguised as a pawn'), ('Knightmate', 'Mate the knight'), ('Knightrelay', 'The knight transfers its powers'), - ('Losers', 'Lose all pieces'), + ('Losers', 'Get strong at self-mate'), ('Magnetic', 'Laws of attraction'), ('Marseille', 'Move twice'), + ('Racingkings', 'Kings cross the 8x8 board'), ('Rifle', 'Shoot pieces'), - ('Royalrace', 'Kings cross the board'), + ('Royalrace', 'Kings cross the 11x11 board'), ('Recycle', 'Reuse pieces'), ('Shatranj', 'Ancient rules'), + ('Suicide', 'Lose all pieces'), ('Suction', 'Attract opposite king'), ('Threechecks', 'Give three checks'), ('Upsidedown', 'Board upside down'), -- 2.44.0