Experimental symmetric randomness + deterministic option
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 4 Mar 2020 15:12:30 +0000 (16:12 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 4 Mar 2020 15:12:30 +0000 (16:12 +0100)
37 files changed:
TODO
client/src/base_rules.js
client/src/components/BaseGame.vue
client/src/components/ChallengeList.vue
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/utils/timeControl.js
client/src/variants/Allmate1.js
client/src/variants/Allmate2.js
client/src/variants/Antiking.js
client/src/variants/Arena.js
client/src/variants/Baroque.js
client/src/variants/Checkered.js
client/src/variants/Circular.js
client/src/variants/Crazyhouse.js
client/src/variants/Grand.js
client/src/variants/Grasshopper.js
client/src/variants/Hidden.js
client/src/variants/Hiddenqueen.js
client/src/variants/Knightmate.js
client/src/variants/Losers.js
client/src/variants/Recycle.js
client/src/variants/Royalrace.js
client/src/variants/Shatranj.js
client/src/variants/Suction.js
client/src/variants/Threechecks.js
client/src/variants/Upsidedown.js
client/src/variants/Wildebeest.js
client/src/views/Analyse.vue
client/src/views/Hall.vue
server/db/create.sql
server/models/Challenge.js
server/routes/challenges.js

diff --git a/TODO b/TODO
index ab06e20..7e9c483 100644 (file)
--- a/TODO
+++ b/TODO
@@ -7,14 +7,6 @@ From MyGames page: send "mconnect" to all online players (me included: potential
   When quit, send mdisconnect (relayed by server if no other MyGames tab).
 And remove current "notify through newmove" on server in sockets.js
 
   When quit, send mdisconnect (relayed by server if no other MyGames tab).
 And remove current "notify through newmove" on server in sockets.js
 
-Analyse mode when launched from a position: should keep orientation
---> $route query param, "side=w or b"
-
-Subcursor (intra move) for multi-move variants for better navigation (in BaseGame)
-
-Allow symmetric mode in all variants (vertical symmetry for racing kings)
-flag in challenge, "Symmetric: true / false", option of GenRandInitFen()
-
 # Misc:
 Saw once a "double challenge" bug, one anonymous and a second one logged
 Both were asked a challenge probably, and both challenges added as different ones.
 # Misc:
 Saw once a "double challenge" bug, one anonymous and a second one logged
 Both were asked a challenge probably, and both challenges added as different ones.
index 5b335b0..93772b9 100644 (file)
@@ -238,11 +238,21 @@ export const ChessRules = class ChessRules {
   /////////////
   // FEN UTILS
 
   /////////////
   // FEN UTILS
 
-  // Setup the initial random (assymetric) position
-  static GenRandInitFen() {
+  // Setup the initial random (asymmetric) position
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0)
+      // Deterministic:
+      return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 1111 -";
+
     let pieces = { w: new Array(8), b: new Array(8) };
     let pieces = { w: new Array(8), b: new Array(8) };
-    // Shuffle pieces on first and last rank
+    // Shuffle pieces on first (and last rank if randomness == 2)
     for (let c of ["w", "b"]) {
     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 positions = ArrayFun.range(8);
 
       // Get random squares for bishops
index 2a1a447..4af4e15 100644 (file)
@@ -227,11 +227,13 @@ export default {
         this.lastMove = null;
     },
     analyzePosition: function() {
         this.lastMove = null;
     },
     analyzePosition: function() {
-      const newUrl =
+      let newUrl =
         "/analyse/" +
         this.game.vname +
         "/?fen=" +
         this.vr.getFen().replace(/ /g, "_");
         "/analyse/" +
         this.game.vname +
         "/?fen=" +
         this.vr.getFen().replace(/ /g, "_");
+      if (this.game.mycolor)
+        newUrl += "&side=" + this.game.mycolor;
       // Open in same tab in live games (against cheating)
       if (this.game.type == "live") this.$router.push(newUrl);
       else window.open("#" + newUrl);
       // Open in same tab in live games (against cheating)
       if (this.game.type == "live") this.$router.push(newUrl);
       else window.open("#" + newUrl);
@@ -337,7 +339,7 @@ export default {
         }
       };
       const playMove = () => {
         }
       };
       const playMove = () => {
-        const animate = V.ShowMoves == "all" && received;
+        const animate = V.ShowMoves == "all" && (received || navigate);
         if (!Array.isArray(move)) move = [move];
         let moveIdx = 0;
         let self = this;
         if (!Array.isArray(move)) move = [move];
         let moveIdx = 0;
         let self = this;
index a350c70..7572ede 100644 (file)
@@ -6,6 +6,7 @@ div
         th {{ st.tr["Variant"] }}
         th {{ st.tr["With"] }}
         th {{ st.tr["Cadence"] }}
         th {{ st.tr["Variant"] }}
         th {{ st.tr["With"] }}
         th {{ st.tr["Cadence"] }}
+        th {{ st.tr["Random?"] }}
     tbody
       tr(
         v-for="c in sortedChallenges"
     tbody
       tr(
         v-for="c in sortedChallenges"
@@ -15,6 +16,7 @@ div
         td {{ c.vname }}
         td {{ withWho(c) }}
         td {{ c.cadence }}
         td {{ c.vname }}
         td {{ withWho(c) }}
         td {{ c.cadence }}
+        td(:class="getRandomnessClass(c)")
 </template>
 
 <script>
 </template>
 
 <script>
@@ -52,6 +54,11 @@ export default {
       if (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id)
         return c.to || this.st.tr["Any player"];
       return c.from.name || "@nonymous";
       if (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id)
         return c.to || this.st.tr["Any player"];
       return c.from.name || "@nonymous";
+    },
+    getRandomnessClass: function(c) {
+      return {
+        ["random-" + c.randomness]: true
+      };
     }
   }
 };
     }
   }
 };
@@ -63,4 +70,12 @@ tr.fromyou > td
   font-style: italic
 tr.toyou > td
   background-color: #fcd785
   font-style: italic
 tr.toyou > td
   background-color: #fcd785
+
+tr > td:last-child
+  &.random-0
+    background-color: #FF5733
+  &.random-1
+    background-color: #2B63B4
+  &.random-2
+    background-color: #33B42B
 </style>
 </style>
index a47463c..3968dac 100644 (file)
@@ -9,12 +9,14 @@ p.
   A few years later I had a prototype to play in live, then other variants
   were added and the website was made more attractive.
 
   A few years later I had a prototype to play in live, then other variants
   were added and the website was made more attractive.
 
-h3 Philosophy
+h3 Notes
 
 
-p Have fun and challenge your mind :-)
-ul
-  li ELO rating is purposely absent from this website.
-  li Games start with a random assymetric position.
+p ELO rating is purposely absent from this website.
+
+p.
+  Games start by default with a random asymmetric position.
+  Random symmetric or even deterministic positions are available too,
+  if you prefer more fairness (but less fun? :-P ).
 
 h3 Contribute
 
 
 h3 Contribute
 
index 4566df8..f240ef8 100644 (file)
@@ -9,12 +9,14 @@ p.
   para jugar en vivo, y luego otras variantes se agregaron
   y el sitio se hizo más atractivo.
 
   para jugar en vivo, y luego otras variantes se agregaron
   y el sitio se hizo más atractivo.
 
-h3 Filosofía
+h3 Notas
 
 
-p Diviértete y desafía tu mente:-)
-ul
-  li El ranking ELO está ausente a propósito de este sitio.
-  li Las partidas comienzan con una posición aleatoria no simétrica.
+p El ranking ELO está ausente a propósito de este sitio.
+
+p.
+  Las partidas comienzan por defecto con una posición aleatoria no simétrica.
+  Es posible una posición aleatoria simétrica o incluso determinista,
+  si prefieres más justicia (¿pero más aburrimiento? :-P).
 
 h3 Contribuir
 
 
 h3 Contribuir
 
index 2860445..b994c79 100644 (file)
@@ -9,12 +9,14 @@ p.
   tard je disposais d'un prototype pour jouer en direct, puis d'autres
   variantes se sont ajoutées et le site a Ã©té rendu plus attractif.
 
   tard je disposais d'un prototype pour jouer en direct, puis d'autres
   variantes se sont ajoutées et le site a Ã©té rendu plus attractif.
 
-h3 Philosophie
+h3 Notes
 
 
-p Exercez vos neurones en vous amusant :-)
-ul
-  li Le classement ELO est Ã  dessein absent de ce site.
-  li Les parties démarrent avec une position aléatoire non symétrique.
+p Le classement ELO est Ã  dessein absent de ce site.
+
+p.
+  Les parties démarrent par défaut avec une position aléatoire non symétrique.
+  Une position aléatoire symétrique ou même déterministe est possible,
+  si vous préférez plus de justice (mais plus d'ennui ? :-P ).
 
 h3 Contribuer
 
 
 h3 Contribuer
 
index e7fcb24..856034c 100644 (file)
@@ -9,6 +9,7 @@ export const translations = {
   "Any player": "Any player",
   Apply: "Apply",
   "Are you sure?": "Are you sure?",
   "Any player": "Any player",
   Apply: "Apply",
   "Are you sure?": "Are you sure?",
+  "Asymmetric random": "Asymmetric random",
   "Authentication successful!": "Authentication successful!",
   "Back to Hall in 3 seconds...": "Back to Hall in 3 seconds...",
   "Back to list": "Back to list",
   "Authentication successful!": "Authentication successful!",
   "Back to Hall in 3 seconds...": "Back to Hall in 3 seconds...",
   "Back to list": "Back to list",
@@ -30,6 +31,7 @@ export const translations = {
   "Correspondance games": "Correspondance games",
   "Database error: stop private browsing, or update your browser": "Database error: stop private browsing, or update your browser",
   Delete: "Delete",
   "Correspondance games": "Correspondance games",
   "Database error: stop private browsing, or update your browser": "Database error: stop private browsing, or update your browser",
   Delete: "Delete",
+  Deterministic: "Deterministic",
   Download: "Download",
   Draw: "Draw",
   "Draw offer only in your turn": "Draw offer only in your turn",
   Download: "Download",
   Draw: "Draw",
   "Draw offer only in your turn": "Draw offer only in your turn",
@@ -92,6 +94,8 @@ export const translations = {
   "Processing... Please wait": "Processing... Please wait",
   Problems: "Problems",
   "participant(s):": "participant(s):",
   "Processing... Please wait": "Processing... Please wait",
   Problems: "Problems",
   "participant(s):": "participant(s):",
+  "Random?": "Random?",
+  "Randomness": "Randomness",
   Refuse: "Refuse",
   Register: "Register",
   "Registration complete! Please check your emails now": "Registration complete! Please check your emails now",
   Refuse: "Refuse",
   Register: "Register",
   "Registration complete! Please check your emails now": "Registration complete! Please check your emails now",
@@ -112,6 +116,7 @@ export const translations = {
   Stop: "Stop",
   "Stop game": "Stop game",
   Subject: "Subject",
   Stop: "Stop",
   "Stop game": "Stop game",
   Subject: "Subject",
+  "Symmetric random": "Symmetric random",
   "Terminate game?": "Terminate game?",
   "Three repetitions": "Three repetitions",
   Time: "Time",
   "Terminate game?": "Terminate game?",
   "Three repetitions": "Three repetitions",
   Time: "Time",
index 70e00e7..1592618 100644 (file)
@@ -9,6 +9,7 @@ export const translations = {
   "Any player": "Cualquier jugador",
   Apply: "Aplicar",
   "Are you sure?": "¿Está usted seguro?",
   "Any player": "Cualquier jugador",
   Apply: "Aplicar",
   "Are you sure?": "¿Está usted seguro?",
+  "Asymmetric random": "Aleatorio asimétrico",
   "Authentication successful!": "¡Autenticación exitosa!",
   "Back to Hall in 3 seconds...": "Regreso al salón en 3 segundos...",
   "Back to list": "Volver a la lista",
   "Authentication successful!": "¡Autenticación exitosa!",
   "Back to Hall in 3 seconds...": "Regreso al salón en 3 segundos...",
   "Back to list": "Volver a la lista",
@@ -30,6 +31,7 @@ export const translations = {
   "Correspondance games": "Partidas por correspondencia",
   "Database error: stop private browsing, or update your browser": "Error de la base de datos: detener la navegación privada, o actualizar su navegador",
   Delete: "Borrar",
   "Correspondance games": "Partidas por correspondencia",
   "Database error: stop private browsing, or update your browser": "Error de la base de datos: detener la navegación privada, o actualizar su navegador",
   Delete: "Borrar",
+  Deterministic: "Determinista",
   Download: "Descargar",
   Draw: "Tablas",
   "Draw offer only in your turn": "Oferta de tablas solo en tu turno",
   Download: "Descargar",
   Draw: "Tablas",
   "Draw offer only in your turn": "Oferta de tablas solo en tu turno",
@@ -92,6 +94,8 @@ export const translations = {
   "Processing... Please wait": "Procesando... por favor espere",
   Problems: "Problemas",
   "participant(s):": "participante(s):",
   "Processing... Please wait": "Procesando... por favor espere",
   Problems: "Problemas",
   "participant(s):": "participante(s):",
+  "Random?": "Aleatorio?",
+  "Randomness": "Grado de azar",
   Refuse: "Rechazar",
   Register: "Registrarse",
   "Registration complete! Please check your emails now": "¡Registro completo! Revise sus correos electrónicos ahora",
   Refuse: "Rechazar",
   Register: "Registrarse",
   "Registration complete! Please check your emails now": "¡Registro completo! Revise sus correos electrónicos ahora",
@@ -112,6 +116,7 @@ export const translations = {
   Stop: "Interrupción",
   "Stop game": "Terminar la partida",
   Subject: "Asunto",
   Stop: "Interrupción",
   "Stop game": "Terminar la partida",
   Subject: "Asunto",
+  "Symmetric random": "Aleatorio simétrico",
   "Terminate game?": "¿Terminar la partida?",
   "Three repetitions": "Tres repeticiones",
   Time: "Tiempo",
   "Terminate game?": "¿Terminar la partida?",
   "Three repetitions": "Tres repeticiones",
   Time: "Tiempo",
index 3fa73d0..848241a 100644 (file)
@@ -10,6 +10,7 @@ export const translations = {
   Apply: "Appliquer",
   "Authentication successful!": "Authentification réussie !",
   "Are you sure?": "Étes vous sûr?",
   Apply: "Appliquer",
   "Authentication successful!": "Authentification réussie !",
   "Are you sure?": "Étes vous sûr?",
+  "Asymmetric random": "Aléatoire asymétrique",
   "Back to Hall in 3 seconds...": "Retour au Hall dans 3 secondes...",
   "Back to list": "Retour Ã  la liste",
   "Black to move": "Trait aux noirs",
   "Back to Hall in 3 seconds...": "Retour au Hall dans 3 secondes...",
   "Back to list": "Retour Ã  la liste",
   "Black to move": "Trait aux noirs",
@@ -30,6 +31,7 @@ export const translations = {
   "Correspondance games": "Parties par correspondance",
   "Database error: stop private browsing, or update your browser": "Erreur de base de données : arrêtez la navigation privée, ou mettez Ã  jour votre navigateur",
   Delete: "Supprimer",
   "Correspondance games": "Parties par correspondance",
   "Database error: stop private browsing, or update your browser": "Erreur de base de données : arrêtez la navigation privée, ou mettez Ã  jour votre navigateur",
   Delete: "Supprimer",
+  Deterministic: "Déterministe",
   Download: "Télécharger",
   Draw: "Nulle",
   "Draw offer only in your turn": "Proposition de nulle seulement sur votre temps",
   Download: "Télécharger",
   Draw: "Nulle",
   "Draw offer only in your turn": "Proposition de nulle seulement sur votre temps",
@@ -92,6 +94,8 @@ export const translations = {
   "Processing... Please wait": "Traitement en cours... Attendez SVP",
   Problems: "Problèmes",
   "participant(s):": "participant(s) :",
   "Processing... Please wait": "Traitement en cours... Attendez SVP",
   Problems: "Problèmes",
   "participant(s):": "participant(s) :",
+  "Random?": "Aléatoire?",
+  "Randomness": "Degré d'aléa",
   Refuse: "Refuser",
   Register: "S'enregistrer",
   "Registration complete! Please check your emails now": "Enregistrement terminé ! Allez voir vos emails maintenant",
   Refuse: "Refuser",
   Register: "S'enregistrer",
   "Registration complete! Please check your emails now": "Enregistrement terminé ! Allez voir vos emails maintenant",
@@ -112,6 +116,7 @@ export const translations = {
   Stop: "Arrêt",
   "Stop game": "Arrêter la partie",
   Subject: "Sujet",
   Stop: "Arrêt",
   "Stop game": "Arrêter la partie",
   Subject: "Sujet",
+  "Symmetric random": "Aléatoire symétrique",
   "Terminate game?": "Stopper la partie ?",
   "Three repetitions": "Triple répétition",
   Time: "Temps",
   "Terminate game?": "Stopper la partie ?",
   "Three repetitions": "Triple répétition",
   Time: "Temps",
index b4bac05..c38d08b 100644 (file)
@@ -33,6 +33,8 @@ export function extractTime(cadence) {
   const mainTime = timeUnitToSeconds(mainTimeValue, mainTimeUnit);
   let increment = 0;
   if (tcParts.length >= 2) {
   const mainTime = timeUnitToSeconds(mainTimeValue, mainTimeUnit);
   let increment = 0;
   if (tcParts.length >= 2) {
+    // Correspondance games don't use an increment:
+    if (mainTimeUnit == 'd') return null;
     tcParts[1] += "s";
     const incrementArray = tcParts[1].match(/^([0-9]+)([smhd]+)$/);
     if (!incrementArray) return null;
     tcParts[1] += "s";
     const incrementArray = tcParts[1].match(/^([0-9]+)([smhd]+)$/);
     if (!incrementArray) return null;
index bf8f1be..5511dd5 100644 (file)
@@ -10,8 +10,8 @@ export const VariantRules = class Allmate1Rules extends ChessRules {
     return [];
   }
 
     return [];
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen().replace(/ -$/, "");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
   }
 
   getPotentialMovesFrom([x, y]) {
   }
 
   getPotentialMovesFrom([x, y]) {
index 34a0f8b..0e515b2 100644 (file)
@@ -10,8 +10,8 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
     return [];
   }
 
     return [];
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen().replace(/ -$/, "");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
   }
 
   getPotentialMovesFrom([x, y]) {
   }
 
   getPotentialMovesFrom([x, y]) {
index 21e37f4..1c062bb 100644 (file)
@@ -151,10 +151,19 @@ export const VariantRules = class AntikingRules extends ChessRules {
     );
   }
 
     );
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0)
+      return "rnbqkbnr/pppppppp/3A4/8/8/3a4/PPPPPPPP/RNBQKBNR w 0 1111 -";
+
     let pieces = { w: new Array(8), b: new Array(8) };
     let antikingPos = { w: -1, b: -1 };
     for (let c of ["w", "b"]) {
     let pieces = { w: new Array(8), b: new Array(8) };
     let antikingPos = { w: -1, b: -1 };
     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, but avoid corners; because,
       let positions = ArrayFun.range(8);
 
       // Get random squares for bishops, but avoid corners; because,
index 42b1f4a..f3713e2 100644 (file)
@@ -5,8 +5,10 @@ export const VariantRules = class ArenaRules extends ChessRules {
     return false;
   }
 
     return false;
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen().replace("w 1111 -", "w -");
+  static GenRandInitFen(randomness) {
+    return ChessRules
+      .GenRandInitFen(randomness)
+      .replace("w 0 1111 -", "w 0 -");
   }
 
   static InArena(x) {
   }
 
   static InArena(x) {
index ae4b511..a860fe7 100644 (file)
@@ -545,10 +545,20 @@ export const VariantRules = class BaroqueRules extends ChessRules {
     return 2;
   }
 
     return 2;
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0)
+      // Deterministic:
+      return "rnbqkbnrm/pppppppp/8/8/8/8/PPPPPPPP/MNBKQBNR 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"]) {
     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 every piece, totally freely
 
       let positions = ArrayFun.range(8);
       // Get random squares for every piece, totally freely
 
index ac99602..5f9bf8f 100644 (file)
@@ -295,10 +295,10 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     return evaluation;
   }
 
     return evaluation;
   }
 
-  static GenRandInitFen() {
-    const randFen = ChessRules.GenRandInitFen();
-    // Add 16 pawns flags + empty cmove:
-    return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness)
+      // Add 16 pawns flags + empty cmove:
+      .replace(" w 0 1111", " w 0 11111111111111111111 -");
   }
 
   static ParseFen(fen) {
   }
 
   static ParseFen(fen) {
index 9ec597b..93df8ef 100644 (file)
@@ -30,10 +30,19 @@ export const VariantRules = class CircularRules extends ChessRules {
     this.pawnFlags = flags;
   }
 
     this.pawnFlags = flags;
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0)
+      return "8/8/pppppppp/rnbqkbnr/8/8/PPPPPPPP/RNBQKBNR w 0 1111111111111111";
+
     let pieces = { w: new Array(8), b: new Array(8) };
     // Shuffle pieces on first and fifth rank
     for (let c of ["w", "b"]) {
     let pieces = { w: new Array(8), b: new Array(8) };
     // Shuffle pieces on first and fifth 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 positions = ArrayFun.range(8);
 
       // Get random squares for bishops
index 24018d1..7dc5346 100644 (file)
@@ -28,8 +28,8 @@ export const VariantRules = class CrazyhouseRules extends ChessRules {
     });
   }
 
     });
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen() + " 0000000000 -";
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness) + " 0000000000 -";
   }
 
   getFen() {
   }
 
   getFen() {
index d3659af..c910dd8 100644 (file)
@@ -316,10 +316,21 @@ export const VariantRules = class GrandRules extends ChessRules {
     return 2;
   }
 
     return 2;
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0) {
+      return "rnbqkmcbnr/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/RNBQKMCBNR " +
+        "w 0 1111 - 00000000000000";
+    }
+
     let pieces = { w: new Array(10), b: new Array(10) };
     // Shuffle pieces on first and last rank
     for (let c of ["w", "b"]) {
     let pieces = { w: new Array(10), b: new Array(10) };
     // 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(10);
 
       // Get random squares for bishops
       let positions = ArrayFun.range(10);
 
       // Get random squares for bishops
index 8f24340..a991035 100644 (file)
@@ -133,8 +133,8 @@ export const VariantRules = class GrasshopperRules extends ChessRules {
     );
   }
 
     );
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen()
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness)
       .replace("w 0 1111 -", "w 0 1111")
       .replace(
         "/pppppppp/8/8/8/8/PPPPPPPP/",
       .replace("w 0 1111 -", "w 0 1111")
       .replace(
         "/pppppppp/8/8/8/8/PPPPPPPP/",
index c79267a..5ccc3d7 100644 (file)
@@ -157,6 +157,7 @@ export const VariantRules = class HiddenRules extends ChessRules {
     return moves;
   }
 
     return moves;
   }
 
+  // Ignore randomness here: placement is always random asymmetric
   static GenRandInitFen() {
     let pieces = { w: new Array(8), b: new Array(8) };
     // Shuffle pieces + pawns on two first ranks
   static GenRandInitFen() {
     let pieces = { w: new Array(8), b: new Array(8) };
     // Shuffle pieces + pawns on two first ranks
index 4f66894..7048d41 100644 (file)
@@ -175,9 +175,9 @@ export const VariantRules = class HiddenqueenRules extends ChessRules {
     return this.filterValid(this.getPotentialMovesFrom(sq));
   }
 
     return this.filterValid(this.getPotentialMovesFrom(sq));
   }
 
-  static GenRandInitFen() {
-    let fen = ChessRules.GenRandInitFen();
-    // Place hidden queens at random:
+  static GenRandInitFen(randomness) {
+    let fen = ChessRules.GenRandInitFen(randomness);
+    // Place hidden queens at random (always):
     let hiddenQueenPos = randInt(8);
     let pawnRank = "PPPPPPPP".split("");
     pawnRank[hiddenQueenPos] = "T";
     let hiddenQueenPos = randInt(8);
     let pawnRank = "PPPPPPPP".split("");
     pawnRank[hiddenQueenPos] = "T";
index c0e84c6..0ac7ebe 100644 (file)
@@ -15,56 +15,9 @@ export const VariantRules = class KnightmateRules extends ChessRules {
     return ([V.KING, V.COMMONER].includes(b[1]) ? "Knightmate/" : "") + b;
   }
 
     return ([V.KING, V.COMMONER].includes(b[1]) ? "Knightmate/" : "") + b;
   }
 
-  static GenRandInitFen() {
-    let pieces = { w: new Array(8), b: new Array(8) };
-    // Shuffle pieces on first and last rank
-    for (let c of ["w", "b"]) {
-      let positions = ArrayFun.range(8);
-
-      // Get random squares for bishops
-      let randIndex = 2 * randInt(4);
-      const bishop1Pos = positions[randIndex];
-      let randIndex_tmp = 2 * randInt(4) + 1;
-      const bishop2Pos = positions[randIndex_tmp];
-      positions.splice(Math.max(randIndex, randIndex_tmp), 1);
-      positions.splice(Math.min(randIndex, randIndex_tmp), 1);
-
-      // Get random squares for commoners
-      randIndex = randInt(6);
-      const commoner1Pos = positions[randIndex];
-      positions.splice(randIndex, 1);
-      randIndex = randInt(5);
-      const commoner2Pos = positions[randIndex];
-      positions.splice(randIndex, 1);
-
-      // Get random square for queen
-      randIndex = randInt(4);
-      const queenPos = positions[randIndex];
-      positions.splice(randIndex, 1);
-
-      // Rooks and king positions are now fixed,
-      // because of the ordering rook-king-rook
-      const rook1Pos = positions[0];
-      const kingPos = positions[1];
-      const rook2Pos = positions[2];
-
-      // Finally put the shuffled pieces in the board array
-      pieces[c][rook1Pos] = "r";
-      pieces[c][commoner1Pos] = "c";
-      pieces[c][bishop1Pos] = "b";
-      pieces[c][queenPos] = "q";
-      pieces[c][kingPos] = "k";
-      pieces[c][bishop2Pos] = "b";
-      pieces[c][commoner2Pos] = "c";
-      pieces[c][rook2Pos] = "r";
-    }
-    // Add turn + flags + enpassant
-    return (
-      pieces["b"].join("") +
-      "/pppppppp/8/8/8/8/PPPPPPPP/" +
-      pieces["w"].join("").toUpperCase() +
-      " w 0 1111 -"
-    );
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness)
+      .replace(/n/g, 'c').replace(/N/g, 'C');
   }
 
   getPotentialMovesFrom([x, y]) {
   }
 
   getPotentialMovesFrom([x, y]) {
index 81d2730..b4385ba 100644 (file)
@@ -142,10 +142,19 @@ export const VariantRules = class LosersRules extends ChessRules {
     return -super.evalPosition(); //better with less material
   }
 
     return -super.evalPosition(); //better with less material
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    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"]) {
     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 positions = ArrayFun.range(8);
 
       // Get random squares for bishops
@@ -194,7 +203,8 @@ export const VariantRules = class LosersRules extends ChessRules {
       pieces["b"].join("") +
       "/pppppppp/8/8/8/8/PPPPPPPP/" +
       pieces["w"].join("").toUpperCase() +
       pieces["b"].join("") +
       "/pppppppp/8/8/8/8/PPPPPPPP/" +
       pieces["w"].join("").toUpperCase() +
+      // En-passant allowed, but no flags
       " w 0 -"
       " w 0 -"
-    ); //en-passant allowed, but no flags
+    );
   }
 };
   }
 };
index 50ccd74..a234350 100644 (file)
@@ -18,8 +18,8 @@ export const VariantRules = class RecycleRules extends ChessRules {
     });
   }
 
     });
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen() + " 0000000000";
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness) + " 0000000000";
   }
 
   getFen() {
   }
 
   getFen() {
index 6f64d24..ff6e1e1 100644 (file)
@@ -19,10 +19,22 @@ export const VariantRules = class RoyalraceRules extends ChessRules {
     return { x: 11, y: 11 };
   }
 
     return { x: 11, y: 11 };
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 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
     for (let c of ["w", "b"]) {
     let pieces = { w: new Array(10), b: new Array(10) };
     // Shuffle pieces on first and second rank
     for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = JSON.parse(JSON.stringify(pieces['w'])).reverse();
+        pieces['b'] =
+          pieces['b'].splice(5,10).reverse().concat(
+          pieces['b'].splice(0,5).reverse());
+        break;
+      }
+
       // Reserve 4 and 5 which are pawns positions
       let positions = ArrayFun.range(10).filter(i => i != 4 && i != 5);
 
       // Reserve 4 and 5 which are pawns positions
       let positions = ArrayFun.range(10).filter(i => i != 4 && i != 5);
 
index bb00101..ef6f65b 100644 (file)
@@ -21,8 +21,8 @@ export const VariantRules = class ShatranjRules extends ChessRules {
     ];
   }
 
     ];
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen().replace("w 1111 -", "w");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).replace("w 1111 -", "w");
   }
 
   getPotentialPawnMoves([x, y]) {
   }
 
   getPotentialPawnMoves([x, y]) {
index 297eef7..0fc88ce 100644 (file)
@@ -189,9 +189,9 @@ export const VariantRules = class SuctionRules extends ChessRules {
     }
   }
 
     }
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
     // Add empty cmove:
     // Add empty cmove:
-    return ChessRules.GenRandInitFen() + " -";
+    return ChessRules.GenRandInitFen(randomness) + " -";
   }
 
   getFen() {
   }
 
   getFen() {
index 78aaf1f..c1d5c5a 100644 (file)
@@ -47,10 +47,10 @@ export const VariantRules = class ThreechecksRules extends ChessRules {
     return super.getCurrentScore();
   }
 
     return super.getCurrentScore();
   }
 
-  static GenRandInitFen() {
-    const randFen = ChessRules.GenRandInitFen();
-    // Add check flags (at 0)
-    return randFen.replace(" w 0 1111", " w 0 111100");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness)
+      // Add check flags (at 0)
+      .replace(" w 0 1111", " w 0 111100");
   }
 
   getFlagsFen() {
   }
 
   getFlagsFen() {
index 538fb4a..44dab7f 100644 (file)
@@ -20,9 +20,18 @@ export const VariantRules = class UpsidedownRules extends ChessRules {
     );
   }
 
     );
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0)
+      return "RNBQKBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbqkbnr w 0";
+
     let pieces = { w: new Array(8), b: new Array(8) };
     for (let c of ["w", "b"]) {
     let pieces = { w: new Array(8), b: new Array(8) };
     for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       let positions = ArrayFun.range(8);
 
       let randIndex = randInt(8);
       let positions = ArrayFun.range(8);
 
       let randIndex = randInt(8);
index fa89cb2..565f627 100644 (file)
@@ -244,9 +244,18 @@ export const VariantRules = class WildebeestRules extends ChessRules {
     return 2;
   }
 
     return 2;
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
+    if (!randomness) randomness = 2;
+    if (randomness == 0)
+      return "rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w 0 1111 -";
+
     let pieces = { w: new Array(10), b: new Array(10) };
     for (let c of ["w", "b"]) {
     let pieces = { w: new Array(10), b: new Array(10) };
     for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       let positions = ArrayFun.range(11);
 
       // Get random squares for bishops + camels (different colors)
       let positions = ArrayFun.range(11);
 
       // Get random squares for bishops + camels (different colors)
@@ -264,7 +273,7 @@ export const VariantRules = class WildebeestRules extends ChessRules {
       for (let idx of randIndexes.concat(randIndexes_tmp).sort((a, b) => {
         return b - a;
       })) {
       for (let idx of randIndexes.concat(randIndexes_tmp).sort((a, b) => {
         return b - a;
       })) {
-        //largest indices first
+        // Largest indices first
         positions.splice(idx, 1);
       }
 
         positions.splice(idx, 1);
       }
 
index 0a66ffc..baf1036 100644 (file)
@@ -45,7 +45,9 @@ export default {
     if (!routeFen) this.alertAndQuit("Missing FEN");
     else {
       this.gameRef.fen = routeFen.replace(/_/g, " ");
     if (!routeFen) this.alertAndQuit("Missing FEN");
     else {
       this.gameRef.fen = routeFen.replace(/_/g, " ");
-      this.initialize();
+      // orientation is optional: taken from FEN if missing
+      const orientation = this.$route.query["side"];
+      this.initialize(orientation);
     }
   },
   methods: {
     }
   },
   methods: {
@@ -58,7 +60,7 @@ export default {
         this.$router.replace(newUrl);
       }, 500);
     },
         this.$router.replace(newUrl);
       }, 500);
     },
-    initialize: async function() {
+    initialize: async function(orientation) {
       // Obtain VariantRules object
       await import("@/variants/" + this.gameRef.vname + ".js")
       .then((vModule) => {
       // Obtain VariantRules object
       await import("@/variants/" + this.gameRef.vname + ".js")
       .then((vModule) => {
@@ -66,17 +68,17 @@ export default {
         if (!V.CanAnalyze)
           // Late check, in case the user tried to enter URL by hand
           this.alertAndQuit("Analysis disabled for this variant");
         if (!V.CanAnalyze)
           // Late check, in case the user tried to enter URL by hand
           this.alertAndQuit("Analysis disabled for this variant");
-        else this.loadGame();
+        else this.loadGame(orientation);
       })
       .catch((err) => { this.alertAndQuit("Mispelled variant name", true); });
     },
       })
       .catch((err) => { this.alertAndQuit("Mispelled variant name", true); });
     },
-    loadGame: function() {
+    loadGame: function(orientation) {
       // NOTE: no need to set score (~unused)
       this.game.vname = this.gameRef.vname;
       this.game.fen = this.gameRef.fen;
       this.curFen = this.game.fen;
       this.adjustFenSize();
       // NOTE: no need to set score (~unused)
       this.game.vname = this.gameRef.vname;
       this.game.fen = this.gameRef.fen;
       this.curFen = this.game.fen;
       this.adjustFenSize();
-      this.game.mycolor = V.ParseFen(this.gameRef.fen).turn;
+      this.game.mycolor = orientation || V.ParseFen(this.gameRef.fen).turn;
       this.$set(this.game, "fenStart", this.gameRef.fen);
     },
     // Triggered by "fenchange" emitted in BaseGame:
       this.$set(this.game, "fenStart", this.gameRef.fen);
     },
     // Triggered by "fenchange" emitted in BaseGame:
index 61ced0a..469d46b 100644 (file)
@@ -54,6 +54,12 @@ main
             v-model="newchallenge.cadence"
             placeholder="5+0, 1h+30s, 5d ..."
           )
             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(v-if="st.user.id > 0")
           label(for="selectPlayers") {{ st.tr["Play with?"] }}
           input#selectPlayers(
         fieldset(v-if="st.user.id > 0")
           label(for="selectPlayers") {{ st.tr["Play with?"] }}
           input#selectPlayers(
@@ -178,6 +184,7 @@ export default {
         vid: parseInt(localStorage.getItem("vid")) || 0,
         to: "", //name of challenged player (if any)
         cadence: localStorage.getItem("cadence") || "",
         vid: parseInt(localStorage.getItem("vid")) || 0,
         to: "", //name of challenged player (if any)
         cadence: localStorage.getItem("cadence") || "",
+        randomness: parseInt(localStorage.getItem("randomness")) || 2,
         // VariantRules object, stored to not interfere with
         // diagrams of targetted challenges:
         V: null,
         // VariantRules object, stored to not interfere with
         // diagrams of targetted challenges:
         V: null,
@@ -541,6 +548,7 @@ export default {
               id: c.id,
               from: this.st.user.sid,
               to: c.to,
               id: c.id,
               from: this.st.user.sid,
               to: c.to,
+              randomness: c.randomness,
               fen: c.fen,
               vid: c.vid,
               cadence: c.cadence,
               fen: c.fen,
               vid: c.vid,
               cadence: c.cadence,
@@ -561,6 +569,7 @@ export default {
           ) {
             let newChall = Object.assign({}, chall);
             newChall.type = this.classifyObject(chall);
           ) {
             let newChall = Object.assign({}, chall);
             newChall.type = this.classifyObject(chall);
+            newChall.randomness = chall.randomness;
             newChall.added = Date.now();
             let fromValues = Object.assign({}, this.people[chall.from]);
             delete fromValues["pages"]; //irrelevant in this context
             newChall.added = Date.now();
             let fromValues = Object.assign({}, this.people[chall.from]);
             delete fromValues["pages"]; //irrelevant in this context
@@ -719,6 +728,8 @@ export default {
       }
       // NOTE: "from" information is not required here
       let chall = Object.assign({}, this.newchallenge);
       }
       // NOTE: "from" information is not required here
       let chall = Object.assign({}, this.newchallenge);
+      delete chall["V"];
+      delete chall["diag"];
       const finishAddChallenge = cid => {
         chall.id = cid || "c" + getRandString();
         // Remove old challenge if any (only one at a time of a given type):
       const finishAddChallenge = cid => {
         chall.id = cid || "c" + getRandString();
         // Remove old challenge if any (only one at a time of a given type):
@@ -753,6 +764,7 @@ export default {
         // Remember cadence  + vid for quicker further challenges:
         localStorage.setItem("cadence", chall.cadence);
         localStorage.setItem("vid", chall.vid);
         // Remember cadence  + vid for quicker further challenges:
         localStorage.setItem("cadence", chall.cadence);
         localStorage.setItem("vid", chall.vid);
+        localStorage.setItem("randomness", chall.randomness);
         document.getElementById("modalNewgame").checked = false;
         // Show the challenge if not on current display
         if (
         document.getElementById("modalNewgame").checked = false;
         // Show the challenge if not on current display
         if (
@@ -841,7 +853,7 @@ export default {
       // These game informations will be shared
       let gameInfo = {
         id: getRandString(),
       // These game informations will be shared
       let gameInfo = {
         id: getRandString(),
-        fen: c.fen || V.GenRandInitFen(),
+        fen: c.fen || V.GenRandInitFen(c.randomness),
         // White player index 0, black player index 1:
         players: c.mycolor
           ? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
         // White player index 0, black player index 1:
         players: c.mycolor
           ? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
index c05c87a..e14e9a0 100644 (file)
@@ -43,6 +43,7 @@ create table Challenges (
   uid integer,
   target integer,
   vid integer,
   uid integer,
   target integer,
   vid integer,
+  randomness integer,
   fen varchar,
   cadence varchar,
   foreign key (uid) references Users(id),
   fen varchar,
   cadence varchar,
   foreign key (uid) references Users(id),
index b7c20b8..243da70 100644 (file)
@@ -8,8 +8,9 @@ const UserModel = require("./User");
  *   uid: user id (int)
  *   target: recipient id (optional)
  *   vid: variant id (int)
  *   uid: user id (int)
  *   target: recipient id (optional)
  *   vid: variant id (int)
+ *   randomness: integer in 0..2
  *   fen: varchar (optional)
  *   fen: varchar (optional)
- *   cadence: string (3m+2s, 7d+1d ...)
+ *   cadence: string (3m+2s, 7d ...)
  */
 
 const ChallengeModel =
  */
 
 const ChallengeModel =
@@ -19,6 +20,7 @@ const ChallengeModel =
     return (
       c.vid.toString().match(/^[0-9]+$/) &&
       c.cadence.match(/^[0-9dhms +]+$/) &&
     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}))
     );
       c.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
       (!c.to || UserModel.checkNameEmail({name: c.to}))
     );
@@ -29,10 +31,11 @@ const ChallengeModel =
     db.serialize(function() {
       const query =
         "INSERT INTO Challenges " +
     db.serialize(function() {
       const query =
         "INSERT INTO Challenges " +
-        "(added, uid, " + (!!c.to ? "target, " : "") + "vid, fen, cadence) " +
-          "VALUES " +
-        "(" + Date.now() + "," + c.uid + "," + (!!c.to ? c.to + "," : "") +
-          c.vid + ",'" + c.fen + "','" + c.cadence + "')";
+          "(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) {
         cb(err, {cid: this.lastID});
       });
       db.run(query, function(err) {
         cb(err, {cid: this.lastID});
       });
index efc6970..aee6fd2 100644 (file)
@@ -11,6 +11,7 @@ router.post("/challenges", access.logged, access.ajax, (req,res) => {
     {
       fen: req.body.chall.fen,
       cadence: req.body.chall.cadence,
     {
       fen: req.body.chall.fen,
       cadence: req.body.chall.cadence,
+      randomness: req.body.chall.randomness,
       vid: req.body.chall.vid,
       uid: req.userId,
       to: req.body.chall.to, //string: user name (may be empty)
       vid: req.body.chall.vid,
       uid: req.userId,
       to: req.body.chall.to, //string: user name (may be empty)