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
 
-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.
index 5b335b0..93772b9 100644 (file)
@@ -238,11 +238,21 @@ export const ChessRules = class ChessRules {
   /////////////
   // 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) };
-    // Shuffle pieces on first and last rank
+    // Shuffle pieces on first (and last rank if randomness == 2)
     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
index 2a1a447..4af4e15 100644 (file)
@@ -227,11 +227,13 @@ export default {
         this.lastMove = null;
     },
     analyzePosition: function() {
-      const newUrl =
+      let newUrl =
         "/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);
@@ -337,7 +339,7 @@ export default {
         }
       };
       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;
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["Random?"] }}
     tbody
       tr(
         v-for="c in sortedChallenges"
@@ -15,6 +16,7 @@ div
         td {{ c.vname }}
         td {{ withWho(c) }}
         td {{ c.cadence }}
+        td(:class="getRandomnessClass(c)")
 </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";
+    },
+    getRandomnessClass: function(c) {
+      return {
+        ["random-" + c.randomness]: true
+      };
     }
   }
 };
@@ -63,4 +70,12 @@ tr.fromyou > td
   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>
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.
 
-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
 
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.
 
-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
 
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.
 
-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
 
index e7fcb24..856034c 100644 (file)
@@ -9,6 +9,7 @@ export const translations = {
   "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",
@@ -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",
+  Deterministic: "Deterministic",
   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):",
+  "Random?": "Random?",
+  "Randomness": "Randomness",
   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",
+  "Symmetric random": "Symmetric random",
   "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?",
+  "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",
@@ -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",
+  Deterministic: "Determinista",
   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):",
+  "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",
@@ -112,6 +116,7 @@ export const translations = {
   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",
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?",
+  "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",
@@ -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",
+  Deterministic: "Déterministe",
   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) :",
+  "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",
@@ -112,6 +116,7 @@ export const translations = {
   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",
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) {
+    // 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;
index bf8f1be..5511dd5 100644 (file)
@@ -10,8 +10,8 @@ export const VariantRules = class Allmate1Rules extends ChessRules {
     return [];
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen().replace(/ -$/, "");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
   }
 
   getPotentialMovesFrom([x, y]) {
index 34a0f8b..0e515b2 100644 (file)
@@ -10,8 +10,8 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
     return [];
   }
 
-  static GenRandInitFen() {
-    return ChessRules.GenRandInitFen().replace(/ -$/, "");
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
   }
 
   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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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;
   }
 
-  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) {
index ae4b511..a860fe7 100644 (file)
@@ -545,10 +545,20 @@ export const VariantRules = class BaroqueRules extends ChessRules {
     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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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;
   }
 
-  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) {
index 9ec597b..93df8ef 100644 (file)
@@ -30,10 +30,19 @@ export const VariantRules = class CircularRules extends ChessRules {
     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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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() {
index d3659af..c910dd8 100644 (file)
@@ -316,10 +316,21 @@ export const VariantRules = class GrandRules extends ChessRules {
     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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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/",
index c79267a..5ccc3d7 100644 (file)
@@ -157,6 +157,7 @@ export const VariantRules = class HiddenRules extends ChessRules {
     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
index 4f66894..7048d41 100644 (file)
@@ -175,9 +175,9 @@ export const VariantRules = class HiddenqueenRules extends ChessRules {
     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";
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;
   }
 
-  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]) {
index 81d2730..b4385ba 100644 (file)
@@ -142,10 +142,19 @@ export const VariantRules = class LosersRules extends ChessRules {
     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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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() +
+      // En-passant allowed, but no flags
       " 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() {
index 6f64d24..ff6e1e1 100644 (file)
@@ -19,10 +19,22 @@ export const VariantRules = class RoyalraceRules extends ChessRules {
     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"]) {
+      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);
 
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]) {
index 297eef7..0fc88ce 100644 (file)
@@ -189,9 +189,9 @@ export const VariantRules = class SuctionRules extends ChessRules {
     }
   }
 
-  static GenRandInitFen() {
+  static GenRandInitFen(randomness) {
     // Add empty cmove:
-    return ChessRules.GenRandInitFen() + " -";
+    return ChessRules.GenRandInitFen(randomness) + " -";
   }
 
   getFen() {
index 78aaf1f..c1d5c5a 100644 (file)
@@ -47,10 +47,10 @@ export const VariantRules = class ThreechecksRules extends ChessRules {
     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() {
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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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;
   }
 
-  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"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
       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;
       })) {
-        //largest indices first
+        // Largest indices first
         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, " ");
-      this.initialize();
+      // orientation is optional: taken from FEN if missing
+      const orientation = this.$route.query["side"];
+      this.initialize(orientation);
     }
   },
   methods: {
@@ -58,7 +60,7 @@ export default {
         this.$router.replace(newUrl);
       }, 500);
     },
-    initialize: async function() {
+    initialize: async function(orientation) {
       // 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");
-        else this.loadGame();
+        else this.loadGame(orientation);
       })
       .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();
-      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:
index 61ced0a..469d46b 100644 (file)
@@ -54,6 +54,12 @@ 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(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") || "",
+        randomness: parseInt(localStorage.getItem("randomness")) || 2,
         // 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,
+              randomness: c.randomness,
               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);
+            newChall.randomness = chall.randomness;
             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);
+      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):
@@ -753,6 +764,7 @@ export default {
         // Remember cadence  + vid for quicker further challenges:
         localStorage.setItem("cadence", chall.cadence);
         localStorage.setItem("vid", chall.vid);
+        localStorage.setItem("randomness", chall.randomness);
         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(),
-        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])
index c05c87a..e14e9a0 100644 (file)
@@ -43,6 +43,7 @@ create table Challenges (
   uid integer,
   target integer,
   vid integer,
+  randomness integer,
   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)
+ *   randomness: integer in 0..2
  *   fen: varchar (optional)
- *   cadence: string (3m+2s, 7d+1d ...)
+ *   cadence: string (3m+2s, 7d ...)
  */
 
 const ChallengeModel =
@@ -19,6 +20,7 @@ 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}))
     );
@@ -29,10 +31,11 @@ const ChallengeModel =
     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});
       });
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,
+      randomness: req.body.chall.randomness,
       vid: req.body.chall.vid,
       uid: req.userId,
       to: req.body.chall.to, //string: user name (may be empty)