Experimental change: options replacing randomness (more general)
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 8 Apr 2021 19:41:58 +0000 (21:41 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 8 Apr 2021 19:41:58 +0000 (21:41 +0200)
47 files changed:
TODO
client/public/images/pieces/Fugue/wl.svg
client/src/App.vue
client/src/base_rules.js
client/src/components/ChallengeList.vue
client/src/components/ComputerGame.vue
client/src/components/GameList.vue
client/src/components/Settings.vue
client/src/main.js
client/src/store.js
client/src/translations/about/en.pug
client/src/translations/about/es.pug
client/src/translations/about/fr.pug
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Allmate/en.pug [moved from client/src/translations/rules/Allmate1/en.pug with 95% similarity]
client/src/translations/rules/Allmate/es.pug [moved from client/src/translations/rules/Allmate1/es.pug with 99% similarity]
client/src/translations/rules/Allmate/fr.pug [moved from client/src/translations/rules/Allmate1/fr.pug with 96% similarity]
client/src/translations/rules/Allmate2/en.pug [deleted file]
client/src/translations/rules/Allmate2/es.pug [deleted file]
client/src/translations/rules/Allmate2/fr.pug [deleted file]
client/src/translations/rules/Checkered/en.pug [moved from client/src/translations/rules/Checkered1/en.pug with 97% similarity]
client/src/translations/rules/Checkered/es.pug [moved from client/src/translations/rules/Checkered1/es.pug with 97% similarity]
client/src/translations/rules/Checkered/fr.pug [moved from client/src/translations/rules/Checkered1/fr.pug with 97% similarity]
client/src/translations/rules/Checkered2/en.pug [deleted file]
client/src/translations/rules/Checkered2/es.pug [deleted file]
client/src/translations/rules/Checkered2/fr.pug [deleted file]
client/src/translations/rules/Chess960/en.pug [moved from client/src/translations/rules/Chess/en.pug with 99% similarity]
client/src/translations/rules/Chess960/es.pug [moved from client/src/translations/rules/Chess/es.pug with 99% similarity]
client/src/translations/rules/Chess960/fr.pug [moved from client/src/translations/rules/Chess/fr.pug with 99% similarity]
client/src/variants/Allmate.js [moved from client/src/variants/Allmate1.js with 99% similarity]
client/src/variants/Allmate2.js [deleted file]
client/src/variants/Checkered.js [moved from client/src/variants/Checkered1.js with 95% similarity]
client/src/variants/Checkered2.js [deleted file]
client/src/variants/Chess.js [deleted symlink]
client/src/variants/Chess960.js [new file with mode: 0644]
client/src/views/Analyse.vue
client/src/views/Game.vue
client/src/views/Hall.vue
client/src/views/Rules.vue
client/src/views/Variants.vue
server/db/create.sql
server/db/populate.sql
server/models/Challenge.js
server/models/Game.js
server/routes/challenges.js

diff --git a/TODO b/TODO
index d3b6227..924b6d0 100644 (file)
--- a/TODO
+++ b/TODO
@@ -20,4 +20,15 @@ CWDA : need game options (also useful at least for Monster)
 PizzaKings https://www.chessvariants.com/unequal.dir/pizza-kings.html
 https://en.m.wikipedia.org/wiki/Chess_with_different_armies#Pizza_Kings%5B11%5D_(John_Lawson)
 
+Coin Chess
+https://msoworld.com/product/chess-variants/
+Background: Unknown inventor
+Rules:
+Normal rules apply except for the introduction of a coin (or counter)
+Black starts by placing the coin on any unoccupied square. Play then continues with players alternating turns as in normal chess.
+On their turn, a player makes any legal move but may not move onto the square where the coin has been placed. The player may move over (but not onto), the coin square.
+The player ends their turn by leaving the coin where it stands or moving the coin to a different unoccupied square.
+The coin can never be placed on an occupied square, and therefore cannot be used to protect a piece from capture
+A player wins by checkmating the opponent. Note that the coin can be used to remove escape squares from the king.
+
 https://www.chessvariants.com/other.dir/nemoroth.html :-)
index d73d3bd..cc2b974 100644 (file)
@@ -13,7 +13,7 @@
    version="1.1"
    id="svg12"
    sodipodi:docname="wl.svg"
-   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)">
+   inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
   <metadata
      id="metadata18">
     <rdf:RDF>
      guidetolerance="10"
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
-     inkscape:window-width="1920"
+     inkscape:window-width="960"
      inkscape:window-height="1060"
      id="namedview14"
      showgrid="false"
      inkscape:zoom="1.7246094"
      inkscape:cx="256"
-     inkscape:cy="255.42016"
+     inkscape:cy="254.84032"
      inkscape:window-x="0"
      inkscape:window-y="20"
      inkscape:window-maximized="0"
-     inkscape:current-layer="svg12" />
+     inkscape:current-layer="svg12"
+     inkscape:document-rotation="0" />
   <g
      id="010---Frog"
      transform="matrix(0.8,0,0,0.8,6.0000003,6)">
      d="m 337.81831,445.85023 c -10.4388,-2.80716 -18.34008,-11.95076 -19.75585,-22.86208 -0.3099,-2.38844 -0.57152,-7.14629 -0.58136,-10.573 l -0.0179,-6.23038 2.94262,-3.62692 c 3.89557,-4.80148 8.76783,-12.99552 11.31926,-19.03639 3.94316,-9.33604 6.61242,-22.77207 6.61242,-33.28449 0,-3.51978 0.11123,-3.78972 3.5586,-8.63619 5.97066,-8.39384 12.04985,-15.26538 22.90012,-25.88492 18.58016,-18.18504 33.22365,-27.66968 49.66778,-32.16997 2.66295,-0.72877 3.59096,-1.30322 4.49161,-2.78035 2.51574,-4.12599 0.70172,-9.36723 -3.75061,-10.83664 -4.93204,-1.62772 -21.05317,4.81069 -34.44901,13.75814 -9.69646,6.47653 -16.23253,12.10004 -29.43403,25.32449 l -12.81849,12.84076 -0.37277,-7.36534 c -0.20502,-4.05093 -0.51019,-7.72345 -0.67815,-8.16114 -0.67475,-1.75837 15.29792,-17.9046 26.1136,-26.39736 18.48353,-14.51375 38.21698,-23.34861 52.15124,-23.34861 6.54713,0 10.13163,1.11515 13.08827,4.07179 2.89575,2.89576 4.15016,6.83571 4.13303,12.98141 -0.0522,18.7298 -14.70341,45.11564 -38.04471,68.51594 -9.49806,9.52206 -16.97344,15.60664 -28.544,23.23334 -8.35929,5.51 -10.87871,7.93729 -12.7369,12.2711 -4.26923,9.95703 0.0894,22.23736 9.5458,26.89528 l 2.72108,1.34031 37.97962,0.28993 c 36.08008,0.27541 38.06085,0.34497 39.60381,1.39075 3.07868,2.08662 4.02373,5.39369 2.42488,8.48553 -1.82084,3.52113 -1.18172,3.46007 -36.22011,3.46007 -35.03251,0 -35.03094,-1.5e-4 -36.79159,3.40457 -2.21954,4.29214 -0.22935,9.26049 4.20261,10.49143 1.27878,0.35517 10.71904,0.59487 23.5043,0.5968 l 21.36715,0.003 1.93729,1.93728 c 2.46615,2.46616 2.95099,4.36523 1.75687,6.88163 -1.96428,4.13942 0.28881,3.94194 -44.48849,3.89944 -33.22344,-0.0315 -40.75306,-0.18432 -43.33798,-0.87945 z"
      id="path26"
      transform="scale(0.1171875)" />
+  <path
+     style="fill:#ffffff;stroke:#000000;stroke-width:0.579841"
+     d="m 194.60655,118.19666 c -2.41356,-1.22724 -5.07782,-4.80585 -5.93454,-7.97119 -1.03527,-3.82506 -0.3344,-9.24725 1.59667,-12.35241 2.34703,-3.774029 4.34459,-5.098424 7.68987,-5.098424 3.50397,0 6.34607,2.039401 8.26069,5.927609 1.92065,3.900445 1.91919,10.324345 -0.003,14.237645 -2.51645,5.12252 -7.46596,7.36366 -11.60946,5.25677 z"
+     id="path16"
+     transform="scale(0.1171875)" />
+  <path
+     style="fill:#ffffff;stroke:#000000;stroke-width:0.579841"
+     d="m 310.57484,118.19666 c -5.83545,-2.96721 -8.15717,-12.49955 -4.75579,-19.525881 1.86272,-3.84785 4.71914,-5.896143 8.2224,-5.896143 3.37415,0 5.34355,1.325238 7.77055,5.228919 1.58018,2.541625 1.73953,3.253555 1.73953,7.772045 0,4.38385 -0.18949,5.29646 -1.5968,7.69048 -2.88247,4.90343 -7.3707,6.76917 -11.37989,4.73058 z"
+     id="path18"
+     transform="scale(0.1171875)" />
 </svg>
index 67dc225..b17133d 100644 (file)
           a.menuitem(href="https://github.com/yagu0/vchess")
             span {{ st.tr["Code"] }}
             img(src="/images/icons/github.svg")
-          //a.menuitem(href="https://www.facebook.com/Variants-Chess-Club-112565840437886")
-            img(src="/images/icons/facebook.svg")
-          //a.menuitem(href="https://twitter.com/VchessC")
-            img(src="/images/icons/twitter.svg")
 </template>
 
 <script>
index f7a2fee..43f9f26 100644 (file)
@@ -34,6 +34,30 @@ export const ChessRules = class ChessRules {
   //////////////
   // MISC UTILS
 
+  static get Options() {
+    return {
+      select: [
+        {
+          label: "Randomness",
+          variable: "randomness",
+          defaut: 2,
+          options: [
+            { label: "Deterministic", value: 0 },
+            { label: "Symmetric random", value: 1 },
+            { label: "Asymmetric random", value: 2 }
+          ]
+        }
+      ],
+      check: []
+    };
+  }
+
+  static AbbreviateOptions(opts) {
+    return "";
+    // Randomness is a special option: (TODO?)
+    //return "R" + opts.randomness;
+  }
+
   // Some variants don't have flags:
   static get HasFlags() {
     return true;
@@ -343,8 +367,8 @@ export const ChessRules = class ChessRules {
   // FEN UTILS
 
   // Setup the initial random (asymmetric) position
-  static GenRandInitFen(randomness) {
-    if (randomness == 0)
+  static GenRandInitFen(options) {
+    if (!options.randomness || options.randomness == 0)
       // Deterministic:
       return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -";
 
@@ -352,7 +376,7 @@ export const ChessRules = class ChessRules {
     let flags = "";
     // Shuffle pieces on first (and last rank if randomness == 2)
     for (let c of ["w", "b"]) {
-      if (c == 'b' && randomness == 1) {
+      if (c == 'b' && options.randomness == 1) {
         pieces['b'] = pieces['w'];
         flags += flags;
         break;
index 1107b58..a354203 100644 (file)
@@ -6,7 +6,7 @@ div
         th {{ st.tr["Variant"] }}
         th {{ st.tr["With"] }}
         th {{ st.tr["Cadence"] }}
-        th {{ st.tr["Random?"] }}
+        th {{ st.tr["Options"] }}
     tbody
       tr(
         v-for="c in sortedChallenges"
@@ -16,7 +16,7 @@ div
         td {{ c.vname }}
         td {{ withWho(c) }}
         td {{ c.cadence }}
-        td(:class="getRandomnessClass(c)")
+        td(:class="getRandomnessClass(c)") {{ c.options.abridged }}
   p(v-else)
     | {{ st.tr["No challenges found :( Click on 'New game'!"] }}
 </template>
@@ -63,8 +63,9 @@ export default {
       return c.from.name || "@nonymous";
     },
     getRandomnessClass: function(c) {
+      if (!c.options.randomness) return {};
       return {
-        ["random-" + c.randomness]: true
+        ["random-" + c.options.randomness]: true
       };
     }
   }
@@ -84,9 +85,9 @@ tr.toyou > td
 
 tr > td:last-child
   &.random-0
-    background-color: #FF5733
+    background-color: #FEAF9E
   &.random-1
-    background-color: #2B63B4
+    background-color: #9EB2FE
   &.random-2
-    background-color: #33B42B
+    background-color: #A5FE9E
 </style>
index 9168922..b212d0f 100644 (file)
@@ -63,12 +63,12 @@ export default {
     };
   },
   methods: {
-    launchGame: function(game) {
+    launchGame: function(game, options) {
       this.compWorker.postMessage(["scripts", this.gameInfo.vname]);
       if (!game) {
         game = {
           vname: this.gameInfo.vname,
-          fenStart: V.GenRandInitFen(this.st.settings.randomness),
+          fenStart: V.GenRandInitFen(options),
           moves: []
         };
         game.fen = game.fenStart;
index d81a1de..ed6b99f 100644 (file)
@@ -13,7 +13,7 @@ div
         @click="$emit('show-game',g)"
         :class="{'my-turn': !!g.myTurn}"
       )
-        td {{ g.vname }}
+        td {{ g.vname + '-' + g.options.abridged }}
         td {{ player_s(g) }}
         td(v-if="showCadence") {{ g.cadence }}
         td(
index 415e9d4..514e704 100644 (file)
@@ -60,12 +60,6 @@ div
             type="checkbox"
             v-model="st.settings.gotonext"
           )
-        fieldset
-          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"] }}
-            option(value="2") {{ st.tr["Asymmetric random"] }}
 </template>
 
 <script>
@@ -100,7 +94,7 @@ export default {
       const propName = event.target.id
         .substr(3)
         .replace(/^\w/, c => c.toLowerCase());
-      const value = ["bcolor","randomness"].includes(propName)
+      const value = propName == "bcolor"
         ? event.target.value
         : event.target.checked;
       store.updateSetting(propName, value);
index 3577b15..b0f9ddc 100644 (file)
@@ -22,8 +22,8 @@ new Vue({
         modalBoxes.forEach(m => {
           if (
             m.checked &&
-            !["modalAccept", "modalConfirm", "modalChat", "modalPeople"]
-              .includes(m.id)
+            !["Accept", "Confirm", "Chat", "People"]
+              .includes(m.id.substr(5)) //modalThing --> Thing
           ) {
             m.checked = false;
           }
index 45535f8..13c600b 100644 (file)
@@ -90,11 +90,7 @@ export const store = {
       hints: getItemDefaultTrue("hints"),
       highlight: getItemDefaultTrue("highlight"),
       gotonext: getItemDefaultTrue("gotonext"),
-      randomness: parseInt(localStorage.getItem("randomness"), 10)
     };
-    if (isNaN(this.state.settings.randomness))
-      // Default: random asymmetric
-      this.state.settings.randomness = 2;
     const supportedLangs = ["en", "es", "fr"];
     const navLanguage = navigator.language.substr(0, 2);
     this.state.lang =
index 4979575..82fabd0 100644 (file)
@@ -50,8 +50,10 @@ h3 Related links
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
   a(href="http://www.iggamecenter.com/") iggamecenter.com
+  a(href="https://boardspace.net/english/index.shtml") boardspace.net
   a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
   a(href="https://schemingmind.com/") schemingmind.com
+  a(href="https://boardgamearena.com/") boardgamearena.com
   a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
   a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net
   a(href="https://brainking.com/") brainking.com
index 4b08b48..95f69c0 100644 (file)
@@ -49,8 +49,10 @@ h3 Enlaces relacionados
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
   a(href="http://www.iggamecenter.com/") iggamecenter.com
+  a(href="https://boardspace.net/english/index.shtml") boardspace.net
   a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
   a(href="https://schemingmind.com/") schemingmind.com
+  a(href="https://boardgamearena.com/") boardgamearena.com
   a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
   a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net
   a(href="https://brainking.com/") brainking.com
index cf58ca7..eb47ccd 100644 (file)
@@ -50,8 +50,10 @@ h3 Liens connexes
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
   a(href="http://www.iggamecenter.com/") iggamecenter.com
+  a(href="https://boardspace.net/english/index.shtml") boardspace.net
   a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
   a(href="https://schemingmind.com/") schemingmind.com
+  a(href="https://boardgamearena.com/") boardgamearena.com
   a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
   a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net
   a(href="https://brainking.com/") brainking.com
index b8c1cd9..133a0de 100644 (file)
@@ -90,7 +90,7 @@ export const translations = {
   "No more problems": "No more problems",
   "No subject. Send anyway?": "No subject. Send anyway?",
   "Notifications by email": "Notifications by email",
-  Number: "Number",
+  "Number": "Number",
   Observe: "Observe",
   "Offer draw?": "Offer draw?",
   "Opponent action": "Opponent action",
@@ -108,8 +108,6 @@ export const translations = {
   Previous_p: "Previous",
   "Processing... Please wait": "Processing... Please wait",
   Problems: "Problems",
-  "Random?": "Random?",
-  "Randomness": "Randomness",
   Refuse: "Refuse",
   Register: "Register",
   "Registration complete! Please check your emails now": "Registration complete! Please check your emails now",
@@ -260,8 +258,7 @@ export const translations = {
   "Lose all pieces": "Lose all pieces",
   "Malagasy Draughts": "Malagasy Draughts",
   "Mandatory captures": "Mandatory captures",
-  "Mate any piece (v1)": "Mate any piece (v1)",
-  "Mate any piece (v2)": "Mate any piece (v2)",
+  "Mate any piece": "Mate any piece",
   "Mate the knight (v1)": "Mate the knight (v1)",
   "Mate the knight (v2)": "Mate the knight (v2)",
   "Meet the Mammoth": "Meet the Mammoth",
@@ -313,8 +310,7 @@ export const translations = {
   "Run forward": "Run forward",
   "Score a goal": "Score a goal",
   "Seirawan-Harper Chess": "Seirawan-Harper Chess",
-  "Shared pieces (v1)": "Shared pieces (v1)",
-  "Shared pieces (v2)": "Shared pieces (v2)",
+  "Shared pieces": "Shared pieces",
   "Shogi 5 x 5": "Shogi 5 x 5",
   "Shoot pieces": "Shoot pieces",
   "Spartan versus Persians": "Spartan versus Persians",
@@ -343,7 +339,7 @@ export const translations = {
   // Variants by categories:
   "What is a chess variant?": "What is a chess variant?",
   "Why play chess variants?": "Why play chess variants?",
-  "chess_v": ": to play under standard rules, with a random (or not) symmetric (or not) initial position.",
+  "chess960_v": ": to play under standard rules, with a random symmetric (or not) initial position.",
   "vt0": "Simplified games to learn chess",
   "vg0": "Variants with very few different pieces, and a simplified goal.",
   "vt1": "Forced captures",
@@ -408,4 +404,9 @@ export const translations = {
   "vg30": "Pieces can temporarily borrow powers from others.",
   "vt31": "Miscelleanous",
   "vg31": "These variants are not classified yet, generally because they are the only one of their kind on this website.",
+
+  // Variants' options
+  "Options": "Options",
+  "Randomness": "Randomness",
+  "With switch": "With switch",
 };
index 55dd304..d3be2c1 100644 (file)
@@ -90,7 +90,7 @@ export const translations = {
   "No more problems": "No mas problemas",
   "No subject. Send anyway?": "Sin asunto. ¿Enviar sin embargo?",
   "Notifications by email": "Notificaciones por email",
-  Number: "Número",
+  "Number": "Número",
   "Offer draw?": "¿Ofrecer tablas?",
   Observe: "Observar",
   "Opponent action": "Acción del adversario",
@@ -108,8 +108,6 @@ export const translations = {
   Previous_n: "Anterior",
   "Processing... Please wait": "Procesando... por favor espere",
   Problems: "Problemas",
-  "Random?": "Aleatorio?",
-  "Randomness": "Grado de azar",
   Refuse: "Rechazar",
   Register: "Inscribirse",
   "Registration complete! Please check your emails now": "¡Registro completo! Revise sus correos electrónicos ahora",
@@ -260,8 +258,7 @@ export const translations = {
   "Lose all pieces": "Perder todas las piezas",
   "Malagasy Draughts": "Damas malgaches",
   "Mandatory captures": "Capturas obligatorias",
-  "Mate any piece (v1)": "Matar cualquier pieza (v1)",
-  "Mate any piece (v2)": "Matar cualquier pieza (v2)",
+  "Mate any piece": "Matar cualquier pieza",
   "Mate the knight (v1)": "Matar el caballo (v1)",
   "Mate the knight (v2)": "Matar el caballo (v2)",
   "Meet the Mammoth": "Conoce al Mamut",
@@ -313,8 +310,7 @@ export const translations = {
   "Run forward": "Correr hacia adelante",
   "Score a goal": "Marcar una meta",
   "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper",
-  "Shared pieces (v1)": "Piezas compartidas (v1)",
-  "Shared pieces (v2)": "Piezas compartidas (v2)",
+  "Shared pieces": "Piezas compartidas",
   "Shogi 5 x 5": "Shogi 5 x 5",
   "Shoot pieces": "Tirar de las piezas",
   "Spartan versus Persians": "Espartanos contra Persas",
@@ -343,7 +339,7 @@ export const translations = {
   // Variants by categories:
   "What is a chess variant?": "¿Qué es una variante?",
   "Why play chess variants?": "¿Por qué jugar las variantes?",
-  "chess_v": ": para jugar con reglas estándar, desde una posición inicial aleatorio (o no) simétrico (o no).",
+  "chess960_v": ": para jugar con reglas estándar, desde una posición inicial aleatorio simétrico (o no).",
   "vt0": "Juegos simplificados para aprender ajedrez",
   "vg0": "Variantes con muy pocas piezas diferentes y un propósito simplificado.",
   "vt1": "Capturas obligatorias",
@@ -408,4 +404,9 @@ export const translations = {
   "vg30": "Las piezas pueden temporalmente tomar prestados poderes de otras",
   "vt31": "Varios",
   "vg31": "Estas variantes aún no están clasificadas, en general porque son el único representante de su tipo en este sitio.",
+
+  // Variants' options
+  "Options": "Optiones",
+  "Randomness": "Grado de azar",
+  "With switch": "Con switch",
 };
index 7f5f8fd..3cef19e 100644 (file)
@@ -90,7 +90,7 @@ export const translations = {
   "No more problems": "Plus de problèmes",
   "No subject. Send anyway?": "Pas de sujet. Envoyer quand-même ??",
   "Notifications by email": "Notifications par email",
-  Number: "Numéro",
+  "Number": "Numéro",
   "Offer draw?": "Proposer nulle ?",
   Observe: "Observer",
   "Opponent action": "Action de l'adversaire",
@@ -108,8 +108,6 @@ export const translations = {
   Previous_n: "Précédente",
   "Processing... Please wait": "Traitement en cours... Attendez SVP",
   Problems: "Problèmes",
-  "Random?": "Aléatoire?",
-  "Randomness": "Degré d'aléa",
   Refuse: "Refuser",
   Register: "S'inscrire",
   "Registration complete! Please check your emails now": "Enregistrement terminé ! Allez voir vos emails maintenant",
@@ -260,8 +258,7 @@ export const translations = {
   "Lose all pieces": "Perdez toutes les pièces",
   "Malagasy Draughts": "Dames malgaches",
   "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 any piece": "Matez n'importe quelle pièce",
   "Mate the knight (v1)": "Matez le cavalier (v1)",
   "Mate the knight (v2)": "Matez le cavalier (v2)",
   "Meet the Mammoth": "Rencontrez le Mammouth",
@@ -313,8 +310,7 @@ export const translations = {
   "Run forward": "Courir vers l'avant",
   "Score a goal": "Marquer un but",
   "Seirawan-Harper Chess": "Échecs Seirawan-Harper",
-  "Shared pieces (v1)": "Pièces partagées (v1)",
-  "Shared pieces (v2)": "Pièces partagées (v2)",
+  "Shared pieces": "Pièces partagées",
   "Shogi 5 x 5": "Shogi 5 x 5",
   "Shoot pieces": "Tirez sur les pièces",
   "Spartan versus Persians": "Spartiates contre Perses",
@@ -343,7 +339,7 @@ export const translations = {
   // Variants by categories:
   "What is a chess variant?": "Qu'est-ce qu'une variante ?",
   "Why play chess variants?": "Pourquoi jouer aux variantes ?",
-  "chess_v": " : pour jouer avec les règles standard, depuis une position initiale aléatoire (ou non) symétrique (ou non).",
+  "chess960_v": " : pour jouer avec les règles standard, depuis une position initiale aléatoire symétrique (ou non).",
   "vt0": "Jeux simplifiés pour apprendre les échecs",
   "vg0": "Variantes avec très peu de pièces différentes, et un but simplifié.",
   "vt1": "Captures obligatoires",
@@ -408,4 +404,9 @@ export const translations = {
   "vg30": "Les pièces peuvent temporairement emprunter des pouvoir aux autres",
   "vt31": "Divers",
   "vg31": "Ces variantes ne sont pas encore classées, en général car elles sont l'unique représentant de leur type sur ce site.",
+
+  // Variants' options
+  "Options": "Options",
+  "Randomness": "Degré d'aléa",
+  "With switch": "Avec switch",
 };
similarity index 95%
rename from client/src/translations/rules/Allmate1/en.pug
rename to client/src/translations/rules/Allmate/en.pug
index fabc8af..adf8d0d 100644 (file)
@@ -36,7 +36,7 @@ figure.diagram-container
 p.
   Note about moves notation: since captures do not occur on final squares,
   an "X" mark is appended to the move to indicate a capture.
-  To keep notation short the potential list of captured squares is not written.
+  To keep notation short the list of captured squares is not written.
 
 h3 "Suicide" note
 
similarity index 99%
rename from client/src/translations/rules/Allmate1/es.pug
rename to client/src/translations/rules/Allmate/es.pug
index d6ba11b..4365f4f 100644 (file)
@@ -37,7 +37,7 @@ figure.diagram-container
 p.
   Nota sobre la notación de las jugadas: ya que las capturas no se realizan
   en la casilla de llegada, se agrega una marca "X" al movimiento para indicar
-  la captura. Para evitar las notaciones demasiado largas, la lista potencial
+  la captura. Para evitar las notaciones demasiado largas, la lista
   de casillas de captura no está escrita.
 
 h3 Nota "suicidio"
similarity index 96%
rename from client/src/translations/rules/Allmate1/fr.pug
rename to client/src/translations/rules/Allmate/fr.pug
index d396233..8b6619f 100644 (file)
@@ -38,7 +38,7 @@ figure.diagram-container
 p.
   Note sur la notation des coups: puisque les captures ne s'effectuent pas
   sur la case d'arrivée, une marque "X" est ajoutée au coup pour indiquer la
-  capture. Afin d'éviter les notations à rallonge la liste potentielle des
+  capture. Afin d'éviter les notations à rallonge la liste des
   cases de capture n'est pas écrite.
 
 h3 Note "suicide"
diff --git a/client/src/translations/rules/Allmate2/en.pug b/client/src/translations/rules/Allmate2/en.pug
deleted file mode 100644 (file)
index 70fdafe..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-p.boxed
-  | An attacked piece is taken if the capture cannot
-  | be prevented with non-capturing moves.
-
-p.
-  This is the Allmate1 variant, with a weaker mating condition:
-  capturing moves to escape from checkmate are not considered.
-  (Mate-)Capturing is thus easier: on the next diagram, 1.Qe6 captures
-  the d7, e7 and f7 pawns.
-
-figure.diagram-container
-  .diagram.diag12
-    | fen:4k3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3:
-  .diagram.diag22
-    | fen:4k3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3:
-  figcaption Before and after 1.Qe6X
-
-p Since captures are much easier, they don't cascade as in Allmate1.
-
-h3 Source
-
-p See Allmate1 variant.
diff --git a/client/src/translations/rules/Allmate2/es.pug b/client/src/translations/rules/Allmate2/es.pug
deleted file mode 100644 (file)
index df2defd..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-p.boxed
-  | Se captura una pieza atacada si no se puede evitar
-  | la toma por movimientos sin captura.
-
-p.
-  Esta es la variante Allmate1, con una condición de mate más
-  débil: no se consideran las capturas para escapar de un jaque mate.
-  Por lo tanto, las (mate-)capturas son más fáciles: en el siguiente diagrama,
-  1.Qe6 toma los peones d7, e7 y f7.
-
-figure.diagram-container
-  .diagram.diag12
-    | fen:4K3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3:
-  .diagram.diag22
-    | fen:4K3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3:
-  figcaption Antes y después 1.Qe6X
-
-p.
-  Como las capturas son mucho más fáciles, no tienen lugar
-  en cascada como en Allmate1.
-
-h3 Fuente
-
-p Ver la variante Allmate1.
diff --git a/client/src/translations/rules/Allmate2/fr.pug b/client/src/translations/rules/Allmate2/fr.pug
deleted file mode 100644 (file)
index 1ea1a05..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-p.boxed
-  | Une pièce attaquée est capturée si la prise ne peut être
-  | empêchée par des coups non capturants.
-
-p.
-  C'est la variante Allmate1, avec une condition de mat plus
-  faible : les coups capturants pour échapper à un mat ne sont pas considérés.
-  Les (mat-)captures sont donc plus faciles : sur le diagramme suivant,
-  1.Qe6 prend les pions d7, e7 et f7.
-
-figure.diagram-container
-  .diagram.diag12
-    | fen:4k3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3:
-  .diagram.diag22
-    | fen:4k3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3:
-  figcaption Avant et après 1.Qe6X
-
-p.
-  Puisque les captures sont beaucoup plus faciles, elles ne s'effectuent pas
-  en cascade comme dans Allmate1.
-
-h3 Source
-
-p Voir la variante Allmate1.
@@ -77,6 +77,9 @@ p.
 
 h2.stageDelimiter Stage 2
 
+p.italic.
+  This stage can be disabled by unselecting "With switch" at game creation.
+
 p.
   During the game one of the two players can decide to take control of the
   checkered pieces.
@@ -75,6 +75,10 @@ p.
 
 h2.stageDelimiter Fase 2
 
+p.italic.
+  Si anula la selección de "Con switch" al crear la partida, entonces
+  el juego permanecerá en la fase 1.
+
 p.
   Durante el juego, uno de los dos jugadores puede decidir tomar
   las piezas a cuadros. Estos luego se vuelven autónomos
@@ -79,6 +79,10 @@ p.
 
 h2.stageDelimiter Phase 2
 
+p.italic.
+  Si vous désélectionnez "Avec switch" lors de la création de la partie,
+  alors le jeu restera en phase 1.
+
 p.
   Au cours de la partie l'un des deux joueurs peut décider de prendre le
   contrôle des pièces échiquetées. Celles-ci deviennent alors autonomes
diff --git a/client/src/translations/rules/Checkered2/en.pug b/client/src/translations/rules/Checkered2/en.pug
deleted file mode 100644 (file)
index 0d0af19..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-p.boxed
-  | The capture of an enemy piece produces a new "checkered" piece belonging
-  | to both players.
-
-p
-  | This is the 
-  a(href="/#/variants/Checkered1") Checkered1 variant
-  | , without the stage 2.
-  | Probably more drawish, but also quite different.
-  | I think both are interesting.
diff --git a/client/src/translations/rules/Checkered2/es.pug b/client/src/translations/rules/Checkered2/es.pug
deleted file mode 100644 (file)
index a38eaf2..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-p.boxed
-  | La captura de una pieza enemiga da lugar al nacimiento de una pieza
-  | "a cuadros", que pertenece a ambos campos.
-
-p
-  | Esta la 
-  a(href="/#/variants/Checkered1") variante Checkered1
-  | , sin fase 2.
-  | Probablemente más canceladoras, pero también bastante diferente.
-  | Creo que ambos tienen interés.
diff --git a/client/src/translations/rules/Checkered2/fr.pug b/client/src/translations/rules/Checkered2/fr.pug
deleted file mode 100644 (file)
index 4b0b92a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-p.boxed
-  | La capture d'une pièce ennemie donne lieu à la naissance d'une pièce
-  | "échiquetée", qui appartient aux deux camps.
-
-p
-  | C'est la 
-  a(href="/#/variants/Checkered1") variante Checkered1
-  | , sans la phase 2.
-  | Sans doute plus annulant, mais aussi assez différente.
-  | Je pense que les deux ont un intérêt.
similarity index 99%
rename from client/src/translations/rules/Chess/en.pug
rename to client/src/translations/rules/Chess960/en.pug
index e7645ef..f978917 100644 (file)
@@ -1,5 +1,5 @@
 p.boxed
-  | Orthodox rules (with potentially shuffled starting position).
+  | Orthodox rules with shuffled starting position.
 
 p.
   Chess is played between two players, one moving the white pieces and the
similarity index 99%
rename from client/src/translations/rules/Chess/es.pug
rename to client/src/translations/rules/Chess960/es.pug
index b60b7b3..205285b 100644 (file)
@@ -1,5 +1,5 @@
 p.boxed
-  | Juego ortodoxo (con una posición inicial potencialmente aleatoria).
+  | Juego ortodoxo con una posición inicial aleatoria.
 
 p.
   El ajedrez es un juego entre dos jugadores, uno que mueve las piezas blancas
similarity index 99%
rename from client/src/translations/rules/Chess/fr.pug
rename to client/src/translations/rules/Chess960/fr.pug
index 9a54a91..fdbd2cf 100644 (file)
@@ -1,5 +1,5 @@
 p.boxed
-  | Jeu orthodoxe (avec une position de départ potentiellement aléatoire).
+  | Jeu orthodoxe avec une position de départ aléatoire.
 
 p.
   Les échecs sont un jeu entre deux joueurs, l'un déplaçant les pièces blanches
similarity index 99%
rename from client/src/variants/Allmate1.js
rename to client/src/variants/Allmate.js
index 88599e4..86409c2 100644 (file)
@@ -1,6 +1,6 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
-export class Allmate1Rules extends ChessRules {
+export class AllmateRules extends ChessRules {
 
   static get HasEnpassant() {
     return false;
diff --git a/client/src/variants/Allmate2.js b/client/src/variants/Allmate2.js
deleted file mode 100644 (file)
index f4abbe3..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-import { ChessRules, PiPo, Move } from "@/base_rules";
-
-export class Allmate2Rules extends ChessRules {
-
-  static get HasEnpassant() {
-    return false;
-  }
-
-  getCheckSquares() {
-    // No notion of check
-    return [];
-  }
-
-  static GenRandInitFen(randomness) {
-    return ChessRules.GenRandInitFen(randomness).slice(0, -2);
-  }
-
-  getPotentialMovesFrom([x, y]) {
-    let moves = super.getPotentialMovesFrom([x, y]);
-    // Remove standard captures (without removing castling):
-    moves = moves.filter(m => {
-      return m.vanish.length == 1 || m.appear.length == 2;
-    });
-
-    // Augment moves with "mate-captures":
-    // TODO: this is coded in a highly inefficient way...
-    const color = this.turn;
-    const oppCol = V.GetOppCol(this.turn);
-    moves.forEach(m => {
-      this.play(m);
-
-      // 1) What is attacked?
-      let attacked = {};
-      for (let i=0; i<V.size.x; i++) {
-        for (let j=0; j<V.size.y; j++) {
-          if (this.getColor(i,j) == oppCol && this.isAttacked([i,j], color))
-            attacked[i+"_"+j] = [i,j];
-        }
-      }
-
-      // 2) Among attacked pieces, which cannot escape capture?
-      // --> without (normal-)capturing: difference with Allmate1 variant
-      // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
-      outerLoop: for (let i=0; i<V.size.x; i++) {
-        for (let j=0; j<V.size.y; j++) {
-          if (this.getColor(i,j) == oppCol) {
-            let oppMoves = [];
-            switch (this.getPiece(i, j)) {
-              case V.PAWN:
-                oppMoves = this.getPotentialPawnMoves([i, j]);
-                break;
-              case V.ROOK:
-                oppMoves = this.getPotentialRookMoves([i, j]);
-                break;
-              case V.KNIGHT:
-                oppMoves = this.getPotentialKnightMoves([i, j]);
-                break;
-              case V.BISHOP:
-                oppMoves = this.getPotentialBishopMoves([i, j]);
-                break;
-              case V.QUEEN:
-                oppMoves = this.getPotentialQueenMoves([i, j]);
-                break;
-              case V.KING:
-                // Do not allow castling to escape from check
-                oppMoves = super.getSlideNJumpMoves(
-                  [i, j],
-                  V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
-                  "oneStep"
-                );
-                break;
-            }
-            for (let om of oppMoves) {
-              if (om.vanish.length == 2)
-                // Skip captures: forbidden in this mode
-                continue;
-              V.PlayOnBoard(this.board, om);
-              Object.values(attacked).forEach(sq => {
-                const origSq = [sq[0], sq[1]];
-                if (om.start.x == sq[0] && om.start.y == sq[1])
-                  // Piece moved:
-                  sq = [om.appear[0].x, om.appear[0].y];
-                if (!this.isAttacked(sq, color))
-                  delete attacked[origSq[0]+"_"+origSq[1]];
-              });
-              V.UndoOnBoard(this.board, om);
-              if (Object.keys(attacked).length == 0)
-                // No need to explore more moves
-                break outerLoop;
-            }
-          }
-        }
-      }
-      this.undo(m);
-
-      // 3) Add mate-captures:
-      Object.values(attacked).forEach(sq => {
-        m.vanish.push(new PiPo({
-          x: sq[0],
-          y: sq[1],
-          c: oppCol,
-          p: this.getPiece(sq[0], sq[1])
-        }));
-      });
-    });
-
-    return moves;
-  }
-
-  // No "under check" conditions in castling
-  getCastleMoves(sq) {
-    return super.getCastleMoves(sq, null, "castleInCheck");
-  }
-
-  // TODO: allow pieces to "commit suicide"? (Currently yes except king)
-  filterValid(moves) {
-    // Remove moves which let the king mate-captured:
-    if (moves.length == 0) return [];
-    const color = this.turn;
-    const oppCol = V.GetOppCol(color);
-    return moves.filter(m => {
-      let res = true;
-      this.play(m);
-      if (this.underCheck(color)) {
-        res = false;
-        const attacked = this.kingPos[color];
-        // Try to find a move to escape check
-        // TODO: very inefficient method.
-        outerLoop: for (let i=0; i<V.size.x; i++) {
-          for (let j=0; j<V.size.y; j++) {
-            if (this.getColor(i,j) == color) {
-              let emoves = [];
-              // Artficial turn change to "play twice":
-              this.turn = color;
-              switch (this.getPiece(i, j)) {
-                case V.PAWN:
-                  emoves = this.getPotentialPawnMoves([i, j]);
-                  break;
-                case V.ROOK:
-                  emoves = this.getPotentialRookMoves([i, j]);
-                  break;
-                case V.KNIGHT:
-                  emoves = this.getPotentialKnightMoves([i, j]);
-                  break;
-                case V.BISHOP:
-                  emoves = this.getPotentialBishopMoves([i, j]);
-                  break;
-                case V.QUEEN:
-                  emoves = this.getPotentialQueenMoves([i, j]);
-                  break;
-                case V.KING:
-                  emoves = this.getPotentialKingMoves([i, j]);
-                  break;
-              }
-              this.turn = oppCol;
-              for (let em of emoves) {
-                V.PlayOnBoard(this.board, em);
-                let sq = attacked;
-                if (em.start.x == attacked[0] && em.start.y == attacked[1])
-                  // King moved:
-                  sq = [em.appear[0].x, em.appear[0].y];
-                if (!this.isAttacked(sq, oppCol))
-                  res = true;
-                V.UndoOnBoard(this.board, em);
-                if (res)
-                  // No need to explore more moves
-                  break outerLoop;
-              }
-            }
-          }
-        }
-      }
-      this.undo(m);
-      return res;
-    });
-  }
-
-  postPlay(move) {
-    super.postPlay(move);
-    if (move.vanish.length >= 2 && move.appear.length == 1) {
-      for (let i = 1; i<move.vanish.length; i++) {
-        const v = move.vanish[i];
-        // Did opponent king disappeared?
-        if (v.p == V.KING)
-          this.kingPos[this.turn] = [-1, -1];
-        // Or maybe a rook?
-        else if (v.p == V.ROOK) {
-          if (v.y < this.kingPos[v.c][1])
-            this.castleFlags[v.c][0] = 8;
-          else
-            // v.y > this.kingPos[v.c][1]
-            this.castleFlags[v.c][1] = 8;
-        }
-      }
-    }
-  }
-
-  preUndo(move) {
-    super.preUndo(move);
-    const oppCol = this.turn;
-    if (move.vanish.length >= 2 && move.appear.length == 1) {
-      // Did opponent king disappeared?
-      const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
-      if (psq)
-        this.kingPos[psq.c] = [psq.x, psq.y];
-    }
-  }
-
-  getCurrentScore() {
-    const color = this.turn;
-    const kp = this.kingPos[color];
-    if (kp[0] < 0)
-      // King disappeared
-      return color == "w" ? "0-1" : "1-0";
-    if (this.atLeastOneMove()) return "*";
-    // Kings still there, no moves:
-    return "1/2";
-  }
-
-  static get SEARCH_DEPTH() {
-    return 1;
-  }
-
-  getNotation(move) {
-    let notation = super.getNotation(move);
-    // Add a capture mark (not describing what is captured...):
-    if (move.vanish.length > 1 && move.appear.length == 1) {
-      if (!!(notation.match(/^[a-h]x/)))
-        // Pawn capture: remove initial "b" in bxc4 for example
-        notation = notation.substr(1);
-      notation = notation.replace("x","") + "X";
-    }
-    return notation;
-  }
-
-};
similarity index 95%
rename from client/src/variants/Checkered1.js
rename to client/src/variants/Checkered.js
index 2451f9b..5036a82 100644 (file)
@@ -1,6 +1,6 @@
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
-export class Checkered1Rules extends ChessRules {
+export class CheckeredRules extends ChessRules {
 
   static board2fen(b) {
     const checkered_codes = {
@@ -41,6 +41,26 @@ export class Checkered1Rules extends ChessRules {
     return (b[0] == "c" ? "Checkered/" : "") + b;
   }
 
+  static get Options() {
+    return Object.assign(
+      {},
+      ChessRules.Options,
+      {
+        check: [
+          {
+            label: "With switch",
+            defaut: true,
+            variable: "switch"
+          }
+        ]
+      }
+    );
+  }
+
+  static AbbreviateOptions(opts) {
+    return (!opts["switch"] ? "NS" : "");
+  }
+
   setOtherVariables(fen) {
     super.setOtherVariables(fen);
     // Local stack of non-capturing checkered moves:
@@ -56,6 +76,7 @@ export class Checkered1Rules extends ChessRules {
     // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
     const stageInfo = V.ParseFen(fen).stage;
     this.stage = parseInt(stageInfo[0], 10);
+    this.canSwitch = (this.stage == 1 && stageInfo[1] != '-');
     this.sideCheckered = (this.stage == 2 ? stageInfo[1] : undefined);
   }
 
@@ -65,7 +86,7 @@ export class Checkered1Rules extends ChessRules {
     if (fenParts.length != 7) return false;
     if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/))
       return false;
-    if (!fenParts[6].match(/^[12][wb]?$/)) return false;
+    if (!fenParts[6].match(/^[12][wb-]?$/)) return false;
     return true;
   }
 
@@ -143,7 +164,7 @@ export class Checkered1Rules extends ChessRules {
       if (this.getPiece(x, y) == V.KING) {
         // If at least one checkered piece, allow switching:
         if (
-          !noswitch &&
+          this.canSwitch && !noswitch &&
           this.board.some(b => b.some(cell => cell[0] == 'c'))
         ) {
           const oppCol = V.GetOppCol(color);
@@ -592,10 +613,13 @@ export class Checkered1Rules extends ChessRules {
     return evaluation;
   }
 
-  static GenRandInitFen(randomness) {
-    // Add 16 pawns flags + empty cmove + stage == 1:
-    return ChessRules.GenRandInitFen(randomness)
-      .slice(0, -2) + "1111111111111111 - - 1";
+  static GenRandInitFen(options) {
+    const baseFen = ChessRules.GenRandInitFen(options.randomness);
+    return (
+      // Add 16 pawns flags + empty cmove + stage == 1:
+      baseFen.slice(0, -2) + "1111111111111111 - - 1" +
+      (!options["switch"] ? '-' : "")
+    );
   }
 
   static ParseFen(fen) {
@@ -620,7 +644,9 @@ export class Checkered1Rules extends ChessRules {
   }
 
   getStageFen() {
-    return (this.stage == 1 ? "1" : "2" + this.sideCheckered);
+    if (this.stage == 1) return "1" + (!this.canSwitch ? '-' : "");
+    // Stage == 2:
+    return "2" + this.sideCheckered;
   }
 
   getFen() {
diff --git a/client/src/variants/Checkered2.js b/client/src/variants/Checkered2.js
deleted file mode 100644 (file)
index 997c1c7..0000000
+++ /dev/null
@@ -1,472 +0,0 @@
-import { ChessRules, Move, PiPo } from "@/base_rules";
-
-export class Checkered2Rules extends ChessRules {
-
-  static board2fen(b) {
-    const checkered_codes = {
-      p: "s",
-      q: "t",
-      r: "u",
-      b: "c",
-      n: "o"
-    };
-    if (b[0] == "c") return checkered_codes[b[1]];
-    return ChessRules.board2fen(b);
-  }
-
-  static fen2board(f) {
-    // Tolerate upper-case versions of checkered pieces (why not?)
-    const checkered_pieces = {
-      s: "p",
-      S: "p",
-      t: "q",
-      T: "q",
-      u: "r",
-      U: "r",
-      c: "b",
-      C: "b",
-      o: "n",
-      O: "n"
-    };
-    if (Object.keys(checkered_pieces).includes(f))
-      return "c" + checkered_pieces[f];
-    return ChessRules.fen2board(f);
-  }
-
-  static get PIECES() {
-    return ChessRules.PIECES.concat(["s", "t", "u", "c", "o"]);
-  }
-
-  getPpath(b) {
-    return (b[0] == "c" ? "Checkered/" : "") + b;
-  }
-
-  setOtherVariables(fen) {
-    super.setOtherVariables(fen);
-    // Local stack of non-capturing checkered moves:
-    this.cmoves = [];
-    const cmove = V.ParseFen(fen).cmove;
-    if (cmove == "-") this.cmoves.push(null);
-    else {
-      this.cmoves.push({
-        start: ChessRules.SquareToCoords(cmove.substr(0, 2)),
-        end: ChessRules.SquareToCoords(cmove.substr(2))
-      });
-    }
-  }
-
-  static IsGoodFen(fen) {
-    if (!ChessRules.IsGoodFen(fen)) return false;
-    const fenParts = fen.split(" ");
-    if (fenParts.length != 6) return false;
-    if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/))
-      return false;
-    return true;
-  }
-
-  static IsGoodFlags(flags) {
-    // 4 for castle + 16 for pawns
-    return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/);
-  }
-
-  setFlags(fenflags) {
-    super.setFlags(fenflags); //castleFlags
-    this.pawnFlags = {
-      w: [...Array(8)], //pawns can move 2 squares?
-      b: [...Array(8)]
-    };
-    const flags = fenflags.substr(4); //skip first 4 letters, for castle
-    for (let c of ["w", "b"]) {
-      for (let i = 0; i < 8; i++)
-        this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1";
-    }
-  }
-
-  aggregateFlags() {
-    return [this.castleFlags, this.pawnFlags];
-  }
-
-  disaggregateFlags(flags) {
-    this.castleFlags = flags[0];
-    this.pawnFlags = flags[1];
-  }
-
-  getEpSquare(moveOrSquare) {
-    if (typeof moveOrSquare !== "object" || moveOrSquare.appear[0].c != 'c')
-      return super.getEpSquare(moveOrSquare);
-    // Checkered move: no en-passant
-    return undefined;
-  }
-
-  getCmove(move) {
-    if (move.appear[0].c == "c" && move.vanish.length == 1)
-      return { start: move.start, end: move.end };
-    return null;
-  }
-
-  canTake([x1, y1], [x2, y2]) {
-    const color1 = this.getColor(x1, y1);
-    const color2 = this.getColor(x2, y2);
-    // Checkered aren't captured
-    return (
-      color1 != color2 &&
-      color2 != "c" &&
-      (color1 != "c" || color2 != this.turn)
-    );
-  }
-
-  // Post-processing: apply "checkerization" of standard moves
-  getPotentialMovesFrom([x, y]) {
-    let standardMoves = super.getPotentialMovesFrom([x, y]);
-    const lastRank = this.turn == "w" ? 0 : 7;
-    // King is treated differently: it never turn checkered
-    if (this.getPiece(x, y) == V.KING) return standardMoves;
-    let moves = [];
-    standardMoves.forEach(m => {
-      if (m.vanish[0].p == V.PAWN) {
-        if (
-          Math.abs(m.end.x - m.start.x) == 2 &&
-          !this.pawnFlags[this.turn][m.start.y]
-        ) {
-          return; //skip forbidden 2-squares jumps
-        }
-        if (
-          this.board[m.end.x][m.end.y] == V.EMPTY &&
-          m.vanish.length == 2 &&
-          this.getColor(m.start.x, m.start.y) == "c"
-        ) {
-          return; //checkered pawns cannot take en-passant
-        }
-      }
-      if (m.vanish.length == 1)
-        // No capture
-        moves.push(m);
-      else {
-        // A capture occured (m.vanish.length == 2)
-        m.appear[0].c = "c";
-        moves.push(m);
-        if (
-          // Avoid promotions (already treated):
-          m.appear[0].p != m.vanish[1].p &&
-          (m.vanish[0].p != V.PAWN || m.end.x != lastRank)
-        ) {
-          // Add transformation into captured piece
-          let m2 = JSON.parse(JSON.stringify(m));
-          m2.appear[0].p = m.vanish[1].p;
-          moves.push(m2);
-        }
-      }
-    });
-    return moves;
-  }
-
-  getPotentialPawnMoves([x, y]) {
-    let moves = super.getPotentialPawnMoves([x, y]);
-    // Post-process: set right color for checkered moves
-    if (this.getColor(x, y) == 'c') {
-      moves.forEach(m => {
-        m.appear[0].c = 'c'; //may be done twice if capture
-        m.vanish[0].c = 'c';
-      });
-    }
-    return moves;
-  }
-
-  canIplay(side, [x, y]) {
-    return side == this.turn && [side, "c"].includes(this.getColor(x, y));
-  }
-
-  // Does m2 un-do m1 ? (to disallow undoing checkered moves)
-  oppositeMoves(m1, m2) {
-    return (
-      !!m1 &&
-      m2.appear[0].c == "c" &&
-      m2.appear.length == 1 &&
-      m2.vanish.length == 1 &&
-      m1.start.x == m2.end.x &&
-      m1.end.x == m2.start.x &&
-      m1.start.y == m2.end.y &&
-      m1.end.y == m2.start.y
-    );
-  }
-
-  filterValid(moves) {
-    if (moves.length == 0) return [];
-    const color = this.turn;
-    const L = this.cmoves.length; //at least 1: init from FEN
-    return moves.filter(m => {
-      if (this.oppositeMoves(this.cmoves[L - 1], m)) return false;
-      this.play(m);
-      const res = !this.underCheck(color);
-      this.undo(m);
-      return res;
-    });
-  }
-
-  getAllValidMoves() {
-    const oppCol = V.GetOppCol(this.turn);
-    let potentialMoves = [];
-    for (let i = 0; i < V.size.x; i++) {
-      for (let j = 0; j < V.size.y; j++) {
-        // NOTE: just testing == color isn't enough because of checkered pieces
-        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
-          Array.prototype.push.apply(
-            potentialMoves,
-            this.getPotentialMovesFrom([i, j])
-          );
-        }
-      }
-    }
-    return this.filterValid(potentialMoves);
-  }
-
-  atLeastOneMove() {
-    const oppCol = V.GetOppCol(this.turn);
-    for (let i = 0; i < V.size.x; i++) {
-      for (let j = 0; j < V.size.y; j++) {
-        // NOTE: just testing == color isn't enough because of checkered pieces
-        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 (this.filterValid([moves[k]]).length > 0) return true;
-            }
-          }
-        }
-      }
-    }
-    return false;
-  }
-
-  // colors: array, generally 'w' and 'c' or 'b' and 'c'
-  isAttacked(sq, colors) {
-    if (!Array.isArray(colors)) colors = [colors];
-    return (
-      this.isAttackedByPawn(sq, colors) ||
-      this.isAttackedByRook(sq, colors) ||
-      this.isAttackedByKnight(sq, colors) ||
-      this.isAttackedByBishop(sq, colors) ||
-      this.isAttackedByQueen(sq, colors) ||
-      this.isAttackedByKing(sq, colors)
-    );
-  }
-
-  isAttackedByPawn([x, y], colors) {
-    for (let c of colors) {
-      const color = (c == "c" ? this.turn : c);
-      let pawnShift = color == "w" ? 1 : -1;
-      if (x + pawnShift >= 0 && x + pawnShift < 8) {
-        for (let i of [-1, 1]) {
-          if (
-            y + i >= 0 &&
-            y + i < 8 &&
-            this.getPiece(x + pawnShift, y + i) == V.PAWN &&
-            this.getColor(x + pawnShift, y + i) == c
-          ) {
-            return true;
-          }
-        }
-      }
-    }
-    return false;
-  }
-
-  isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
-    for (let step of steps) {
-      let rx = x + step[0],
-          ry = y + step[1];
-      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
-        rx += step[0];
-        ry += step[1];
-      }
-      if (
-        V.OnBoard(rx, ry) &&
-        this.getPiece(rx, ry) === piece &&
-        colors.includes(this.getColor(rx, ry))
-      ) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  isAttackedByRook(sq, colors) {
-    return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]);
-  }
-
-  isAttackedByKnight(sq, colors) {
-    return this.isAttackedBySlideNJump(
-      sq,
-      colors,
-      V.KNIGHT,
-      V.steps[V.KNIGHT],
-      "oneStep"
-    );
-  }
-
-  isAttackedByBishop(sq, colors) {
-    return this.isAttackedBySlideNJump(
-      sq, colors, V.BISHOP, V.steps[V.BISHOP]);
-  }
-
-  isAttackedByQueen(sq, colors) {
-    return this.isAttackedBySlideNJump(
-      sq,
-      colors,
-      V.QUEEN,
-      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
-    );
-  }
-
-  isAttackedByKing(sq, colors) {
-    return this.isAttackedBySlideNJump(
-      sq,
-      colors,
-      V.KING,
-      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
-      "oneStep"
-    );
-  }
-
-  underCheck(color) {
-    return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]);
-  }
-
-  getCheckSquares() {
-    const color = this.turn;
-    // Artifically change turn, for checkered pawns
-    this.turn = V.GetOppCol(color);
-    const kingAttacked =
-      this.isAttacked(
-        this.kingPos[color],
-        [this.turn, 'c']
-      );
-    let res = kingAttacked
-      ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
-      : [];
-    this.turn = color;
-    return res;
-  }
-
-  postPlay(move) {
-    super.postPlay(move);
-    // Does this move turn off a 2-squares pawn flag?
-    if (
-      [1, 6].includes(move.start.x) &&
-      move.vanish[0].p == V.PAWN &&
-      Math.abs(move.end.x - move.start.x) == 2
-    ) {
-      this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
-    }
-    this.cmoves.push(this.getCmove(move));
-  }
-
-  postUndo(move) {
-    super.postUndo(move);
-    this.cmoves.pop();
-  }
-
-  getCurrentScore() {
-    if (this.atLeastOneMove()) return "*";
-    const color = this.turn;
-    // Artifically change turn, for checkered pawns
-    this.turn = V.GetOppCol(this.turn);
-    const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"])
-      ? color == "w"
-        ? "0-1"
-        : "1-0"
-      : "1/2";
-    this.turn = V.GetOppCol(this.turn);
-    return res;
-  }
-
-  evalPosition() {
-    let evaluation = 0;
-    // Just count material for now, considering checkered neutral (...)
-    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 sqColor = this.getColor(i, j);
-          if (["w","b"].includes(sqColor)) {
-            const sign = sqColor == "w" ? 1 : -1;
-            evaluation += sign * V.VALUES[this.getPiece(i, j)];
-          }
-        }
-      }
-    }
-    return evaluation;
-  }
-
-  static GenRandInitFen(randomness) {
-    // Add 16 pawns flags + empty cmove:
-    return ChessRules.GenRandInitFen(randomness)
-      .slice(0, -2) + "1111111111111111 - -";
-  }
-
-  static ParseFen(fen) {
-    return Object.assign(
-      ChessRules.ParseFen(fen),
-      { cmove: fen.split(" ")[5] }
-    );
-  }
-
-  getCmoveFen() {
-    const L = this.cmoves.length;
-    return (
-      !this.cmoves[L - 1]
-        ? "-"
-        : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) +
-          ChessRules.CoordsToSquare(this.cmoves[L - 1].end)
-    );
-  }
-
-  getFen() {
-    return super.getFen() + " " + this.getCmoveFen();
-  }
-
-  getFenForRepeat() {
-    return super.getFenForRepeat() + "_" + this.getCmoveFen();
-  }
-
-  getFlagsFen() {
-    let fen = super.getFlagsFen();
-    // Add pawns flags
-    for (let c of ["w", "b"])
-      for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0");
-    return fen;
-  }
-
-  static get SEARCH_DEPTH() {
-    return 2;
-  }
-
-  getNotation(move) {
-    if (move.appear.length == 2) {
-      // Castle
-      if (move.end.y < move.start.y) return "0-0-0";
-      return "0-0";
-    }
-
-    const finalSquare = V.CoordsToSquare(move.end);
-    const piece = this.getPiece(move.start.x, move.start.y);
-    let notation = "";
-    if (piece == V.PAWN) {
-      // Pawn move
-      if (move.vanish.length > 1) {
-        // Capture
-        const startColumn = V.CoordToColumn(move.start.y);
-        notation = startColumn + "x" + finalSquare;
-      } else notation = finalSquare;
-    } else {
-      // Piece movement
-      notation =
-        piece.toUpperCase() +
-        (move.vanish.length > 1 ? "x" : "") +
-        finalSquare;
-    }
-    if (move.appear[0].p != move.vanish[0].p)
-      notation += "=" + move.appear[0].p.toUpperCase();
-    return notation;
-  }
-
-};
diff --git a/client/src/variants/Chess.js b/client/src/variants/Chess.js
deleted file mode 120000 (symlink)
index 8e6b27c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../base_rules.js
\ No newline at end of file
diff --git a/client/src/variants/Chess960.js b/client/src/variants/Chess960.js
new file mode 100644 (file)
index 0000000..968ec1c
--- /dev/null
@@ -0,0 +1,23 @@
+import { ChessRules } from "@/base_rules";
+
+export class Chess960Rules extends ChessRules {
+
+  // Do not allow standard chess:
+  static get Options() {
+    return {
+      select: [
+        {
+          label: "Randomness",
+          variable: "randomness",
+          defaut: 2,
+          options: [
+            { label: "Symmetric random", value: 1 },
+            { label: "Asymmetric random", value: 2 }
+          ]
+        }
+      ],
+      check: []
+    };
+  }
+
+};
index 06ef790..5c525a3 100644 (file)
@@ -44,7 +44,8 @@ export default {
       rulesContent: "",
       gameRef: {
         vname: "",
-        fen: ""
+        fen: "",
+        options: {}
       },
       game: {
         players: [{ name: "Analyse" }, { name: "Analyse" }],
index 3d73893..b13e383 100644 (file)
@@ -78,7 +78,10 @@ main
   .row
     #aboveBoard.col-sm-12
       span.variant-cadence(v-if="game.type!='import'") {{ game.cadence }}
-      span.variant-name {{ game.vname }}
+      span.variant-name
+        | {{ game.vname }}
+        | -
+        | {{ vr.constructor.AbbreviateOptions(game.options) }}
       span#nextGame(
         v-if="nextIds.length > 0"
         @click="showNextGame()"
@@ -408,8 +411,15 @@ export default {
           this.conn.onopen = () => callback();
       };
       this.fetchGame((game) => {
-        if (!!game)
+        if (!!game) {
+          if (!game.options) {
+            // Patch for retro-compatibility (TODO: remove it)
+            game.options = { randomness: game.randomness };
+            delete game["randomness"];
+          }
+          else game.options = JSON.parse(game.options);
           this.loadVariantThenGame(game, () => socketInit(this.roomInit));
+        }
         else
           // Live game stored remotely: need socket to retrieve it
           // NOTE: the callback "roomInit" will be lost, so it's not provided.
@@ -707,7 +717,7 @@ export default {
           const gameToSend = Object.keys(this.game)
             .filter(k =>
               [
-                "id","fen","players","vid","cadence","fenStart",
+                "id","fen","players","vid","cadence","fenStart","options",
                 "moves","clocks","score","drawOffer","rematchOffer"
               ].includes(k))
             .reduce(
@@ -1048,8 +1058,8 @@ export default {
         // Start a new game!
         let gameInfo = {
           id: getRandString(), //ignored if corr
-          fen: V.GenRandInitFen(this.game.randomness),
-          randomness: this.game.randomness,
+          fen: V.GenRandInitFen(this.game.options),
+          options: this.game.options,
           players: [this.game.players[1], this.game.players[0]],
           vid: this.game.vid,
           cadence: this.game.cadence
@@ -1082,7 +1092,11 @@ export default {
             "/games",
             "POST",
             {
-              data: { gameInfo: gameInfo },
+              data: Object.assign(
+                {},
+                gameInfo,
+                { options: JSON.stringify(this.game.options) }
+              ),
               success: (response) => {
                 gameInfo.id = response.id;
                 notifyNewGame();
index 7749dfa..6d675a1 100644 (file)
@@ -12,7 +12,9 @@ main
   div#acceptDiv(role="dialog")
     .card
       p.text-center
-        span.variantName {{ curChallToAccept.vname }} 
+        span.variantName
+          | {{ curChallToAccept.vname }}
+          | {{ curChallToAccept.options.abridged }} 
         span {{ curChallToAccept.cadence }} 
         span {{ st.tr["with"] + " " + curChallToAccept.from.name }}
       p.text-center(v-if="!!curChallToAccept.color")
@@ -49,6 +51,23 @@ main
               :selected="newchallenge.vid==v.id"
             )
               | {{ v.display }}
+        // Variant-specific options (often at least randomness)
+        fieldset(v-if="!!newchallenge.V")
+          div(v-for="select of newchallenge.V.Options.select")
+            label(:for="select.variable + '_opt'") {{ st.tr[select.label] }} *
+            select(:id="select.variable + '_opt'")
+              option(
+                v-for="o of select.options"
+                :value="o.value"
+                :selected="o.value == select.defaut"
+              )
+                | {{ st.tr[o.label] }}
+          div(v-for="check of newchallenge.V.Options.check")
+            label(:for="check.variable + '_opt'") {{ st.tr[check.label] }} *
+            input(
+              :id="check.variable + '_opt'"
+              type="checkbox"
+              :checked="check.defaut")
         fieldset
           label(for="cadence") {{ st.tr["Cadence"] }} *
           div#predefinedCadences
@@ -61,12 +80,6 @@ main
             v-model="newchallenge.cadence"
             placeholder="5+0, 1h+30s, 5d ..."
           )
-        fieldset
-          label(for="selectRandomLevel") {{ st.tr["Randomness"] }} *
-          select#selectRandomLevel(v-model="newchallenge.randomness")
-            option(value="0") {{ st.tr["Deterministic"] }}
-            option(value="1") {{ st.tr["Symmetric random"] }}
-            option(value="2") {{ st.tr["Asymmetric random"] }}
         fieldset
           label(for="memorizeChall") {{ st.tr["Memorize"] }}
           input#memorizeChall(
@@ -156,7 +169,7 @@ main
           tr
             th {{ st.tr["Variant"] }}
             th {{ st.tr["Cadence"] }}
-            th {{ st.tr["Random?"] }}
+            th {{ st.tr["Options"] }}
             th
         tbody
           tr(
@@ -165,7 +178,7 @@ main
           )
             td {{ pc.vname }}
             td {{ pc.cadence }}
-            td(:class="getRandomnessClass(pc)")
+            td(:class="getRandomnessClass(pc)") {{ pc.options.abridged }}
             td.remove-preset(@click="removePresetChall($event, pc)")
               img(src="/images/icons/delete.svg")
   .row
@@ -253,11 +266,9 @@ export default {
         to: "", //name of challenged player (if any)
         color: '',
         cadence: localStorage.getItem("cadence") || "",
-        randomness:
-          // Warning: randomness can be 0, then !!randomness is false
-          (parseInt(localStorage.getItem("challRandomness"),10)+1 || 3) - 1,
+        options: {},
         // VariantRules object, stored to not interfere with
-        // diagrams of targetted challenges:
+        // diagrams of targeted challenges:
         V: null,
         vname: "",
         diag: "", //visualizing FEN
@@ -265,7 +276,7 @@ export default {
       },
       focus: true,
       tchallDiag: "",
-      curChallToAccept: { from: {} },
+      curChallToAccept: { from: {}, options: {} },
       presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"),
       conn: null,
       connexionString: "",
@@ -326,13 +337,11 @@ export default {
                 to: this.$route.query["challenge"],
                 color: this.$route.query["color"] || '',
                 cadence: this.$route.query["cadence"],
-                // Tournament: no randomness (TODO: for now at least)
-                randomness: 0,
+                options: {},
                 memorize: false
               }
             );
             window.doClick("modalNewgame");
-            //this.issueNewChallenge(); //NOTE: doesn't work yet.
           },
           this.$route.query["variant"]
         );
@@ -417,15 +426,16 @@ export default {
               response.challenges.map(c => {
                 const from = { name: names[c.uid], id: c.uid }; //or just name
                 const type = this.classifyObject(c);
-                const vname = this.getVname(c.vid);
+                this.setVname(c);
                 return Object.assign(
+                  {},
+                  c,
                   {
                     type: type,
-                    vname: vname,
                     from: from,
-                    to: c.target ? names[c.target] : ""
-                  },
-                  c
+                    to: c.target ? names[c.target] : "",
+                    options: JSON.parse(c.options)
+                  }
                 );
               })
             );
@@ -465,8 +475,9 @@ export default {
       this.conn = null;
     },
     getRandomnessClass: function(pc) {
+      if (!pc.options.randomness) return {};
       return {
-        ["random-" + pc.randomness]: true
+        ["random-" + pc.options.randomness]: true
       };
     },
     anonymousCount: function() {
@@ -506,12 +517,22 @@ export default {
       this.partialResetNewchallenge();
       window.doClick("modalNewgame");
     },
+    sameOptions: function(opt1, opt2) {
+      const keys1 = Object.keys(opt1),
+            keys2 = Object.keys(opt2);
+      if (keys1.length != keys2.length) return false;
+      for (const key1 of keys1) {
+        if (!keys2.includes(key1)) return false;
+        if (opt1[key1] != opt2[key1]) return false;
+      }
+      return true;
+    },
     addPresetChall: function(chall) {
       // Add only if not already existing:
       if (this.presetChalls.some(c =>
         c.vid == chall.vid &&
         c.cadence == chall.cadence &&
-        c.randomness == chall.randomness
+        this.sameOptions(c.options, chall.options)
       )) {
         return;
       }
@@ -521,7 +542,7 @@ export default {
         vid: chall.vid,
         vname: chall.vname, //redundant, but easier
         cadence: chall.cadence,
-        randomness: chall.randomness
+        options: chall.options
       });
       localStorage.setItem("presetChalls", JSON.stringify(this.presetChalls));
     },
@@ -825,7 +846,7 @@ export default {
                 id: c.id,
                 from: this.st.user.sid,
                 to: c.to,
-                randomness: c.randomness,
+                options: c.options,
                 fen: c.fen,
                 vid: c.vid,
                 cadence: c.cadence,
@@ -969,7 +990,7 @@ export default {
       ) {
         let newChall = Object.assign({}, chall);
         newChall.type = this.classifyObject(chall);
-        newChall.randomness = chall.randomness;
+        newChall.options = chall.options;
         newChall.added = Date.now();
         let fromValues = Object.assign({}, this.people[chall.from]);
         delete fromValues["pages"]; //irrelevant in this context
@@ -1028,7 +1049,7 @@ export default {
       this.partialResetNewchallenge();
       this.newchallenge.vid = pchall.vid;
       this.newchallenge.cadence = pchall.cadence;
-      this.newchallenge.randomness = pchall.randomness;
+      this.newchallenge.options = pchall.options;
       this.loadNewchallVariant(this.issueNewChallenge);
     },
     issueNewChallenge: async function() {
@@ -1071,6 +1092,17 @@ export default {
       }
       // NOTE: "from" information is not required here
       let chall = Object.assign({}, this.newchallenge);
+      chall.options = {};
+      // Get/set options variables (if any) / TODO: v-model?!
+      for (const check of this.newchallenge.V.Options.check) {
+        const elt = document.getElementById(check.variable + "_opt");
+        if (elt.checked) chall.options[check.variable] = true;
+      }
+      for (const select of this.newchallenge.V.Options.select) {
+        const elt = document.getElementById(select.variable + "_opt");
+        chall.options[select.variable] = elt.value;
+      }
+      chall.options.abridged = V.AbbreviateOptions(chall.options);
       // Add only if not already issued (not counting FEN):
       if (this.challenges.some(c =>
         (
@@ -1085,7 +1117,7 @@ export default {
         &&
         c.vid == chall.vid &&
         c.cadence == chall.cadence &&
-        c.randomness == chall.randomness
+        this.sameOptions(c.options, chall.options)
       )) {
         alert(this.st.tr["Challenge already exists"]);
         return;
@@ -1146,10 +1178,9 @@ export default {
         chall.type = ctype;
         chall.vname = this.newchallenge.vname;
         this.challenges.push(chall);
-        // Remember cadence  + vid for quicker further challenges:
+        // Remember cadence + vid for quicker further challenges:
         localStorage.setItem("cadence", chall.cadence);
         localStorage.setItem("vid", chall.vid);
-        localStorage.setItem("challRandomness", chall.randomness);
         document.getElementById("modalNewgame").checked = false;
         // Show the challenge if not on current display
         if (
@@ -1169,7 +1200,13 @@ export default {
           "/challenges",
           "POST",
           {
-            data: { chall: chall },
+            data: {
+              chall: Object.assign(
+                {},
+                chall,
+                { options: JSON.stringify(chall.options) }
+              )
+            },
             success: (response) => {
               finishAddChallenge(response.id);
             }
@@ -1275,8 +1312,8 @@ export default {
       // These game informations will be shared
       let gameInfo = {
         id: getRandString(),
-        fen: c.fen || V.GenRandInitFen(c.randomness),
-        randomness: c.randomness, //for rematch
+        fen: c.fen || V.GenRandInitFen(c.options),
+        options: c.options, //for rematch
         players: players,
         vid: c.vid,
         cadence: c.cadence
@@ -1326,7 +1363,11 @@ export default {
           {
             // cid is useful to delete the challenge:
             data: {
-              gameInfo: gameInfo,
+              gameInfo: Object.assign(
+                {},
+                gameInfo,
+                { options: JSON.stringify(gameInfo.options) }
+              ),
               cid: c.id
             },
             success: (response) => {
@@ -1478,11 +1519,11 @@ button.refuseBtn
 
 tr > td
   &.random-0
-    background-color: #FF5733
+    background-color: #FEAF9E
   &.random-1
-    background-color: #2B63B4
+    background-color: #9EB2FE
   &.random-2
-    background-color: #33B42B
+    background-color: #A5FE9E
 
 @media screen and (max-width: 767px)
   h4
index 09d5e06..fc3a0cd 100644 (file)
@@ -1,5 +1,30 @@
 <template lang="pug">
 main
+  input#modalOptions.modal(type="checkbox")
+  div#optionsDiv(
+    role="dialog"
+    data-checkbox="modalOptions"
+  )
+    .card
+      label.modal-close(for="modalOptions")
+      h3 {{ st.tr["Options"] }}
+      fieldset(v-if="!!V")
+        div(v-for="select of V.Options.select")
+          label(:for="select.variable + '_opt'") {{ st.tr[select.label] }} *
+          select(:id="select.variable + '_opt'")
+            option(
+              v-for="o of select.options"
+              :value="o.value"
+              :selected="o.value == select.defaut"
+            )
+              | {{ st.tr[o.label] }}
+        div(v-for="check of V.Options.check")
+          label(:for="check.variable + '_opt'") {{ st.tr[check.label] }} *
+          input(
+            :id="check.variable + '_opt'"
+            type="checkbox"
+            :checked="check.defaut")
+      button(@click="setOptions()") {{ st.tr["Validate"] }}
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       .button-group
@@ -44,6 +69,7 @@ import ComputerGame from "@/components/ComputerGame.vue";
 import { store } from "@/store";
 import { replaceByDiag } from "@/utils/printDiagram";
 import { CompgameStorage } from "@/utils/compgameStorage";
+import { processModalClick } from "@/utils/modalClick";
 import afterRawLoad from "@/utils/afterRawLoad";
 export default {
   name: "my-rules",
@@ -58,9 +84,9 @@ export default {
       // variables passed to ComputerGame:
       gameInfo: {
         vname: "",
-        mode: "versus",
+        mode: "versus"
       },
-      V: null,
+      V: null
     };
   },
   watch: {
@@ -72,6 +98,10 @@ export default {
     // NOTE: variant cannot be set before store is initialized
     this.re_setVariant(this.$route.params["vname"]);
   },
+  mounted: function() {
+    document.getElementById("optionsDiv")
+      .addEventListener("click", processModalClick);
+  },
   computed: {
     showAnalyzeBtn: function() {
       return !!this.V && this.V.CanAnalyze;
@@ -117,12 +147,38 @@ export default {
         }, 500);
       });
     },
-    startGame: function(mode) {
+    setOptions: function() {
+      let options = {};
+      // Get/set options variables / TODO: v-model?!
+      for (const check of this.V.Options.check) {
+        const elt = document.getElementById(check.variable + "_opt");
+        if (elt.checked) options[check.variable] = true;
+      }
+      for (const select of this.V.Options.select) {
+        const elt = document.getElementById(select.variable + "_opt");
+        options[select.variable] = elt.value;
+      }
+      document.getElementById("modalOptions").checked = false;
+      if (this.whatNext == "analyze") this.gotoAnalyze(options);
+      else this.startGame(this.whatNext, options);
+    },
+    startGame: function(mode, options) {
       if (this.gameInProgress) return;
-      this.gameInProgress = true;
-      this.display = "computer";
-      this.gameInfo.mode = mode;
-      if (this.gameInfo.mode == "versus") {
+      const next = (game, options) => {
+        this.gameInProgress = true;
+        this.display = "computer";
+        this.gameInfo.mode = mode;
+        this.$refs["compgame"].launchGame(game, options);
+      };
+      if (!!options) {
+        next(null, options);
+        return;
+      }
+      const askOptions = () => {
+        this.whatNext = mode;
+        doClick("modalOptions");
+      };
+      if (mode == "versus") {
         CompgameStorage.get(this.gameInfo.vname, (game) => {
           // NOTE: game might be null (if none stored yet)
           if (!!game && !V.IsGoodFen(game.fen)) {
@@ -130,10 +186,14 @@ export default {
             CompgameStorage.remove(game.vname);
             game = null;
           }
-          this.$refs["compgame"].launchGame(game);
+          if (!!game || Object.keys(V.Options).length == 0) next(game);
+          else askOptions();
         });
       }
-      else this.$refs["compgame"].launchGame();
+      else {
+        if (Object.keys(V.Options).length == 0) next();
+        else askOptions();
+      }
     },
     // The user wants to stop the game:
     stopGame: function() {
@@ -145,11 +205,17 @@ export default {
       if (this.gameInfo.mode == "versus")
         CompgameStorage.remove(this.gameInfo.vname);
     },
-    gotoAnalyze: function() {
-      this.$router.push(
-        "/analyse/" + this.gameInfo.vname +
-        "/?fen=" + V.GenRandInitFen(this.st.settings.randomness)
-      );
+    gotoAnalyze: function(options) {
+      if (!options && Object.keys(V.Options).length > 0) {
+        this.whatNext = "analyze";
+        doClick("modalOptions");
+      }
+      else {
+        this.$router.push(
+          "/analyse/" + this.gameInfo.vname +
+          "/?fen=" + V.GenRandInitFen(options)
+        );
+      }
     }
   }
 };
index 8e28e50..f60e5f1 100644 (file)
@@ -10,8 +10,8 @@ main
         a(href="https://www.chessvariants.com/why.html")
           | {{ st.tr["Why play chess variants?"] }}
       p
-        a(href="/#/variants/Chess") Chess
-        | {{ st.tr["chess_v"] }}
+        a(href="/#/variants/Chess960") Chess960
+        | {{ st.tr["chess960_v"] }}
       div(v-for="g of sortedGroups")
         h3 {{ st.tr["vt" + g] }}
         p {{ st.tr["vg" + g] }}
index e26b03b..248e597 100644 (file)
@@ -39,7 +39,7 @@ create table Challenges (
   uid integer,
   target integer,
   vid integer,
-  randomness integer,
+  options varchar,
   fen varchar,
   cadence varchar,
   foreign key (uid) references Users(id),
@@ -62,7 +62,7 @@ create table Games (
   score varchar default '*',
   scoreMsg varchar,
   cadence varchar,
-  randomness integer, --for rematch
+  options varchar, --for rematch
   created datetime,
   drawOffer character default '',
   rematchOffer character default '',
index 6f67cc1..1cb54e6 100644 (file)
@@ -17,8 +17,7 @@ insert or ignore into Variants (name, description, groupe, display) values
   ('Alapo', 'Geometric Chess', 27, 'Alapo'),
   ('Alice', 'Both sides of the mirror', 31, 'Alice Chess'),
   ('Align4', 'Align four pawns', 31, 'Align4'),
-  ('Allmate1', 'Mate any piece (v1)', 11, 'Allmate1'),
-  ('Allmate2', 'Mate any piece (v2)', 11, 'Allmate2'),
+  ('Allmate', 'Mate any piece', 11, 'Allmate'),
   ('Ambiguous', 'Play opponent''s pieces', 29, 'Ambiguous'),
   ('Antiking1', 'Keep antiking in check (v1)', 9, 'Anti-King 1'),
   ('Antiking2', 'Keep antiking in check (v2)', 9, 'Anti-King 2'),
@@ -44,10 +43,9 @@ insert or ignore into Variants (name, description, groupe, display) values
   ('Capablanca', 'Capablanca Chess', 7, 'Capablanca Chess'),
   ('Capture', 'Mandatory captures', 1, 'Capture'),
   ('Castle', 'Win by castling long', 27, 'Castle'),
-  ('Checkered1', 'Shared pieces (v1)', 12, 'Checkered 1'),
-  ('Checkered2', 'Shared pieces (v2)', 12, 'Checkered 2'),
+  ('Checkered', 'Shared pieces', 12, 'Checkered'),
   ('Checkless', 'No-check mode', 18, 'Checkless'),
-  ('Chess', 'Standard rules', -1, 'Chess'),
+  ('Chess960', 'Standard rules', -1, 'Chess960'),
   ('Circular', 'Run forward', 3, 'Circular Chess'),
   ('Clorange', 'A Clockwork Orange', 20, 'Clockwork Orange'),
   ('Colorbound', 'The colorbound clobberers', 5, 'Colorbound Clobberers'),
index a528ca6..e32cd68 100644 (file)
@@ -8,9 +8,10 @@ const UserModel = require("./User");
  *   uid: user id (int)
  *   target: recipient id (optional)
  *   vid: variant id (int)
- *   randomness: integer in 0..2
+ *   options: varchar
  *   fen: varchar (optional)
  *   cadence: string (3m+2s, 7d ...)
+ *   options: string (js object)
  */
 
 const ChallengeModel = {
@@ -19,7 +20,6 @@ const ChallengeModel = {
     return (
       c.vid.toString().match(/^[0-9]+$/) &&
       c.cadence.match(/^[0-9dhms +]+$/) &&
-      c.randomness.toString().match(/^[0-2]$/) &&
       c.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
       (!c.to || UserModel.checkNameEmail({ name: c.to }))
     );
@@ -30,11 +30,10 @@ const ChallengeModel = {
       const query =
         "INSERT INTO Challenges " +
           "(added, uid, " + (c.to ? "target, " : "") +
-          "vid, randomness, fen, cadence) " +
-        "VALUES " +
-          "(" + Date.now() + "," + c.uid + "," + (c.to ? c.to + "," : "") +
-          c.vid + "," + c.randomness + ",'" + c.fen + "','" + c.cadence + "')";
-      db.run(query, function(err) {
+          "vid, options, fen, cadence) " +
+        "VALUES (" + Date.now() + "," + c.uid + "," + (c.to ? c.to + "," : "")
+          + c.vid + ",?,'" + c.fen + "','" + c.cadence + "')";
+      db.run(query, c.options, function(err) {
         cb(err, { id: this.lastID });
       });
     });
index 5304693..8cc526f 100644 (file)
@@ -15,7 +15,7 @@ const UserModel = require("./User");
  *   created: datetime
  *   drawOffer: char ('w','b' or '' for none)
  *   rematchOffer: char (similar to drawOffer)
- *   randomness: integer
+ *   options: varchar
  *   deletedByWhite: boolean
  *   deletedByBlack: boolean
  *   chatReadWhite: datetime
@@ -40,7 +40,6 @@ const GameModel = {
     return (
       g.vid.toString().match(/^[0-9]+$/) &&
       g.cadence.match(/^[0-9dhms +]+$/) &&
-      g.randomness.toString().match(/^[0-2]$/) &&
       g.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
       g.players.length == 2 &&
       g.players.every(p => p.id.toString().match(/^[0-9]+$/))
@@ -57,22 +56,22 @@ const GameModel = {
     });
   },
 
-  create: function(vid, fen, randomness, cadence, players, cb) {
+  create: function(vid, fen, options, cadence, players, cb) {
     db.serialize(function() {
       let query =
         "INSERT INTO Games " +
         "(" +
-          "vid, fenStart, fen, randomness, " +
+          "vid, fenStart, fen, options, " +
           "white, black, " +
           "cadence, created" +
         ") " +
         "VALUES " +
         "(" +
-          vid + ",'" + fen + "','" + fen + "'," + randomness + "," +
+          vid + ",'" + fen + "','" + fen + "',?," +
           players[0].id + "," + players[1].id + "," +
           "'" + cadence + "'," + Date.now() +
         ")";
-      db.run(query, function(err) {
+      db.run(query, options, function(err) {
         cb(err, { id: this.lastID });
       });
     });
@@ -85,7 +84,7 @@ const GameModel = {
       let query =
         "SELECT " +
           "id, vid, fen, fenStart, cadence, created, " +
-          "white, black, randomness, score, scoreMsg, " +
+          "white, black, options, score, scoreMsg, " +
           "chatReadWhite, chatReadBlack, drawOffer, rematchOffer " +
         "FROM Games " +
         "WHERE id = " + id;
index fe10ee3..1f626f2 100644 (file)
@@ -9,6 +9,7 @@ router.post("/challenges", access.logged, access.ajax, (req,res) => {
     let challenge = {
       fen: req.body.chall.fen,
       cadence: req.body.chall.cadence,
+      options: req.body.chall.options,
       randomness: req.body.chall.randomness,
       vid: req.body.chall.vid,
       uid: req.userId,