From 554e3ad3773a3123701bd894db1df4c1843283b8 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 21 Jun 2022 14:25:59 +0200
Subject: [PATCH] Add Ambiguous. Fix a few issues with FEN generation / options

---
 app.js                        |   5 +-
 pieces/ambiguous_target.svg   | 191 ++++++++++++++++++++++++++++++++++
 server.js                     |  16 ++-
 variants.js                   |   2 +-
 variants/Ambiguous/class.js   | 107 ++++++++++---------
 variants/Ambiguous/rules.html |  11 ++
 variants/Ambiguous/style.css  |  43 ++++++++
 variants/Chakart/class.js     |   6 +-
 variants/Suction/class.js     |   4 +-
 9 files changed, 316 insertions(+), 69 deletions(-)
 create mode 100644 pieces/ambiguous_target.svg
 create mode 100644 variants/Ambiguous/rules.html
 create mode 100644 variants/Ambiguous/style.css

diff --git a/app.js b/app.js
index 7d387c5..4c0b5e4 100644
--- a/app.js
+++ b/app.js
@@ -545,8 +545,9 @@ function initializeGame(obj) {
         break;
       }
     }
-    fillGameInfos(obj, playerColor == "w" ? 1 : 0);
-    if (obj.randvar)
+    const playerIndex = (playerColor == "w" ? 0 : 1);
+    fillGameInfos(obj, 1 - playerIndex);
+    if (obj.players[playerIndex].randvar)
       toggleVisible("gameInfos");
     else
       toggleVisible("boardContainer");
diff --git a/pieces/ambiguous_target.svg b/pieces/ambiguous_target.svg
new file mode 100644
index 0000000..53f0ccc
--- /dev/null
+++ b/pieces/ambiguous_target.svg
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns:ns1="http://sozi.baierouge.fr"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    id="svg12417"
+    sodipodi:docname="target.svg"
+    viewBox="0 0 200.13 200.02"
+    version="1.1"
+    inkscape:version="0.48+devel r10958"
+  >
+  <title
+      id="title825"
+    >Target</title
+  >
+  <sodipodi:namedview
+      id="base"
+      fit-margin-left="10"
+      inkscape:zoom="1"
+      height="0px"
+      borderopacity="1.0"
+      inkscape:current-layer="layer1"
+      inkscape:cx="89.904323"
+      inkscape:cy="99.565238"
+      fit-margin-right="10"
+      inkscape:window-maximized="1"
+      showgrid="false"
+      width="0px"
+      inkscape:guide-bbox="true"
+      showguides="true"
+      bordercolor="#666666"
+      inkscape:window-x="-8"
+      inkscape:window-y="-8"
+      fit-margin-bottom="10"
+      inkscape:window-width="1280"
+      inkscape:pageopacity="0.0"
+      inkscape:pageshadow="2"
+      pagecolor="#ffffff"
+      inkscape:document-units="px"
+      inkscape:window-height="962"
+      fit-margin-top="10"
+  />
+  <g
+      id="layer1"
+      inkscape:label="Layer 1"
+      inkscape:groupmode="layer"
+      transform="translate(-248.44 -442.39)"
+    >
+    <g
+        id="g818"
+        transform="translate(-.96350 -1.0044)"
+      >
+      <path
+          id="path12427-9-3-9"
+          sodipodi:rx="180.62735"
+          sodipodi:ry="180.62735"
+          style="color:#000000;stroke:#000000;stroke-width:24.084;fill:none"
+          sodipodi:type="arc"
+          d="m531.77 557.68c0 99.758-80.87 180.63-180.63 180.63-99.758 0-180.63-80.87-180.63-180.63 0-99.758 80.87-180.63 180.63-180.63 99.758 0 180.63 80.87 180.63 180.63z"
+          transform="matrix(.41522 0 0 .41522 203.67 311.84)"
+          sodipodi:cy="557.68408"
+          sodipodi:cx="351.13956"
+      />
+      <path
+          id="path12427-9-3-9-8-5"
+          sodipodi:rx="180.62735"
+          sodipodi:ry="180.62735"
+          style="color:#000000;stroke:#000000;stroke-width:40.139;fill:none"
+          sodipodi:type="arc"
+          d="m531.77 557.68c0 99.758-80.87 180.63-180.63 180.63-99.758 0-180.63-80.87-180.63-180.63 0-99.758 80.87-180.63 180.63-180.63 99.758 0 180.63 80.87 180.63 180.63z"
+          transform="matrix(.24913 0 0 .24913 261.99 404.46)"
+          sodipodi:cy="557.68408"
+          sodipodi:cx="351.13956"
+      />
+      <path
+          id="path12427-9-3-9-8-2-3"
+          sodipodi:rx="180.62735"
+          sodipodi:ry="180.62735"
+          style="color:#000000;fill:#000000"
+          sodipodi:type="arc"
+          d="m531.77 557.68c0 99.758-80.87 180.63-180.63 180.63-99.758 0-180.63-80.87-180.63-180.63 0-99.758 80.87-180.63 180.63-180.63 99.758 0 180.63 80.87 180.63 180.63z"
+          transform="matrix(.12121 0 0 .12121 306.91 475.8)"
+          sodipodi:cy="557.68408"
+          sodipodi:cx="351.13956"
+      />
+      <path
+          id="path13189-0"
+          d="m349.48 455.93v174.95"
+          style="stroke:#000000;stroke-linecap:round;stroke-width:5;fill:none"
+          inkscape:connector-curvature="0"
+      />
+      <path
+          id="path45"
+          style="stroke:#000000;stroke-linecap:round;stroke-width:5;fill:none"
+          inkscape:connector-curvature="0"
+          d="m436.98 543.41h-174.95"
+      />
+    </g
+    >
+  </g
+  >
+  <metadata
+    >
+    <rdf:RDF
+      >
+      <cc:Work
+        >
+        <dc:format
+          >image/svg+xml</dc:format
+        >
+        <dc:type
+            rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+        />
+        <cc:license
+            rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+        />
+        <dc:publisher
+          >
+          <cc:Agent
+              rdf:about="http://openclipart.org/"
+            >
+            <dc:title
+              >Openclipart</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:publisher
+        >
+        <dc:title
+          >Target</dc:title
+        >
+        <dc:date
+          >2012-02-15T07:37:04</dc:date
+        >
+        <dc:description
+          >Target symbol</dc:description
+        >
+        <dc:source
+          >https://openclipart.org/detail/168253/target-by-fanda@cz</dc:source
+        >
+        <dc:creator
+          >
+          <cc:Agent
+            >
+            <dc:title
+              >Fanda@CZ</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:creator
+        >
+        <dc:subject
+          >
+          <rdf:Bag
+            >
+            <rdf:li
+              >target</rdf:li
+            >
+          </rdf:Bag
+          >
+        </dc:subject
+        >
+      </cc:Work
+      >
+      <cc:License
+          rdf:about="http://creativecommons.org/licenses/publicdomain/"
+        >
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Reproduction"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Distribution"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+        />
+      </cc:License
+      >
+    </rdf:RDF
+    >
+  </metadata
+  >
+</svg
+>
diff --git a/server.js b/server.js
index 7e1a99b..dc8bd97 100644
--- a/server.js
+++ b/server.js
@@ -37,15 +37,12 @@ function initializeGame(vname, players, options) {
 function launchGame(gid) {
   moveHash[gid] = {};
   const gameInfo = Object.assign(
-    {seed: Math.floor(Math.random() * 1984), gid: gid},
+    {seed: Math.floor(Math.random() * 19840), gid: gid},
     games[gid]
   );
   // players array is supposed to be full:
-  for (const p of games[gid].players) {
-    send(p.sid,
-         "gamestart",
-         Object.assign({randvar: p.randvar}, gameInfo));
-  }
+  for (const p of games[gid].players)
+    send(p.sid, "gamestart", gameInfo);
 }
 
 function getRandomVariant() {
@@ -137,11 +134,10 @@ wss.on("connection", (socket, req) => {
             const allrand = games[obj.gid].rematch.every(r => r == 2);
             if (allrand)
               vname = getRandomVariant();
-            games[obj.gid].players.forEach(p =>
-              p.randvar = allrand ? true : false);
+            games[obj.gid].players.forEach(p => p.randvar = allrand);
             const gid = initializeGame(vname,
-                           games[obj.gid].players.reverse(),
-                           games[obj.gid].options);
+                                       games[obj.gid].players.reverse(),
+                                       games[obj.gid].options);
             launchGame(gid);
           }
         }
diff --git a/variants.js b/variants.js
index aafd02a..c9af9e4 100644
--- a/variants.js
+++ b/variants.js
@@ -5,7 +5,7 @@ const variants = [
 //  {name: 'Alice', desc: 'Both sides of the mirror'},
 //  {name: 'Align4', desc: 'Align four pawns'},
 //  {name: 'Allmate', desc: 'Mate any piece'},
-//  {name: 'Ambiguous', desc: "Play opponent's pieces"},
+  {name: 'Ambiguous', desc: "Play opponent's pieces"},
 //  {name: 'Antiking1', desc: 'Keep antiking in check', disp: 'Anti-King'},
 //  {name: 'Antimatter', desc: 'Dangerous collisions'},
 //  {name: 'Apocalypse', desc: 'The end of the world'},
diff --git a/variants/Ambiguous/class.js b/variants/Ambiguous/class.js
index 78d3d63..6a001a6 100644
--- a/variants/Ambiguous/class.js
+++ b/variants/Ambiguous/class.js
@@ -1,10 +1,14 @@
 import ChessRules from "/base_rules.js";
-import { randInt, shuffle } from "@/utils/alea";
-import { ArrayFun } from "@/utils/array";
+import GiveawayRules from "/variants/Giveaway/class.js";
 
 export default class AmbiguousRules extends ChessRules {
 
-  // TODO: options
+  static get Options() {
+    return {
+      select: C.Options.select,
+      styles: ["cylinder"]
+    };
+  }
 
   get hasFlags() {
     return false;
@@ -19,25 +23,29 @@ export default class AmbiguousRules extends ChessRules {
   }
 
   genRandInitFen(seed) {
-    const gr = new GiveawayRules(
-      {mode: "suicide", options: this.options, genFenOnly: true});
+    const options = Object.assign({mode: "suicide"}, this.options);
+    const gr = new GiveawayRules({options: options, genFenOnly: true});
     return gr.genRandInitFen(seed);
   }
 
+  canStepOver(x, y) {
+    return this.board[x][y] == "" || this.getPiece(x, y) == V.GOAL;
+  }
+
   // Subturn 1: play a move for the opponent on the designated square.
   // Subturn 2: play a move for me (which just indicate a square).
   getPotentialMovesFrom([x, y]) {
     const color = this.turn;
-    const oppCol = V.GetOppCol(color);
+    const oppCol = C.GetOppCol(color);
     if (this.subTurn == 2) {
       // Just play a normal move (which in fact only indicate a square)
       let movesHash = {};
       return (
         super.getPotentialMovesFrom([x, y])
         .filter(m => {
-          // Filter promotions: keep only one, since no choice now.
+          // Filter promotions: keep only one, since no choice for now.
           if (m.appear[0].p != m.vanish[0].p) {
-            const hash = V.CoordsToSquare(m.start) + V.CoordsToSquare(m.end);
+            const hash = C.CoordsToSquare(m.start) + C.CoordsToSquare(m.end);
             if (!movesHash[hash]) {
               movesHash[hash] = true;
               return true;
@@ -47,48 +55,37 @@ export default class AmbiguousRules extends ChessRules {
           return true;
         })
         .map(m => {
-          if (m.vanish.length == 1) m.appear[0].p = V.GOAL;
-          else m.appear[0].p = V.TARGET_CODE[m.vanish[1].p];
-          m.appear[0].c = oppCol;
+          if (m.vanish.length == 1) {
+            m.appear[0].c = 'a'; //a-color
+            m.appear[0].p = V.GOAL;
+          }
+          else {
+            m.appear[0].p = V.TARGET_CODE[m.vanish[1].p];
+            m.appear[0].c = oppCol;
+          }
           m.vanish.shift();
           return m;
         })
       );
     }
-    // At subTurn == 1, play a targeted move for opponent
+    // At subTurn == 1, play a targeted move for the opponent.
     // Search for target (we could also have it in a stack...)
-    let target = { x: -1, y: -1 };
-    outerLoop: for (let i = 0; i < V.size.x; i++) {
-      for (let j = 0; j < V.size.y; j++) {
-        if (this.board[i][j] != V.EMPTY) {
-          const piece = this.board[i][j][1];
+    let target = {x: -1, y: -1};
+    outerLoop: for (let i = 0; i < this.size.x; i++) {
+      for (let j = 0; j < this.size.y; j++) {
+        if (this.board[i][j] != "") {
+          const piece = this.getPiece(i, j);
           if (
             piece == V.GOAL ||
             Object.keys(V.TARGET_DECODE).includes(piece)
           ) {
-            target = { x: i, y: j};
+            target = {x: i, y:j};
             break outerLoop;
           }
         }
       }
     }
-    // TODO: could be more efficient than generating all moves.
-    this.turn = oppCol;
-    const emptyTarget = (this.board[target.x][target.y][1] == V.GOAL);
-    if (emptyTarget) this.board[target.x][target.y] = V.EMPTY;
-    let moves = super.getPotentialMovesFrom([x, y]);
-    if (emptyTarget) {
-      this.board[target.x][target.y] = color + V.GOAL;
-      moves.forEach(m => {
-        m.vanish.push({
-          x: target.x,
-          y: target.y,
-          c: color,
-          p: V.GOAL
-        });
-      });
-    }
-    this.turn = color;
+    const moves = super.getPotentialMovesFrom([x, y], oppCol);
     return moves.filter(m => m.end.x == target.x && m.end.y == target.y);
   }
 
@@ -127,8 +124,17 @@ export default class AmbiguousRules extends ChessRules {
     };
   }
 
-  pieces() {
-    // .........
+  pieces(color, x, y) {
+    const targets = {
+      's': {"class": "target-pawn", moves: []},
+      'u': {"class": "target-rook", moves: []},
+      'o': {"class": "target-knight", moves: []},
+      'c': {"class": "target-bishop", moves: []},
+      't': {"class": "target-queen", moves: []},
+      'l': {"class": "target-king", moves: []}
+    };
+    return Object.assign(
+      { 'g': {"class": "target"} }, targets, super.pieces(color, x, y));
   }
 
   atLeastOneMove() {
@@ -140,27 +146,26 @@ export default class AmbiguousRules extends ChessRules {
     return moves;
   }
 
+  isKing(symbol) {
+    return ['k', 'l'].includes(symbol);
+  }
+
   getCurrentScore() {
     // This function is only called at subTurn 1
-    const color = V.GetOppCol(this.turn);
-    if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0");
+    const color = C.GetOppCol(this.turn);
+    const kingPos = this.searchKingPos(color);
+    if (kingPos[0] < 0)
+      return (color == 'w' ? "0-1" : "1-0");
     return "*";
   }
 
-  play(move) {
-    let kingCaptured = false;
-    if (this.subTurn == 1) {
-      this.prePlay(move);
-      this.epSquares.push(this.getEpSquare(move));
-      kingCaptured = this.kingPos[this.turn][0] < 0;
-    }
-    if (kingCaptured) move.kingCaptured = true;
-    V.PlayOnBoard(this.board, move);
-    if (this.subTurn == 2 || kingCaptured) {
-      this.turn = V.GetOppCol(this.turn);
+  postPlay(move) {
+    const color = this.turn;
+    if (this.subTurn == 2 || this.searchKingPos(color)[0] < 0) {
+      this.turn = C.GetOppCol(color);
       this.movesCount++;
     }
-    if (!kingCaptured) this.subTurn = 3 - this.subTurn;
+    this.subTurn = 3 - this.subTurn;
   }
 
 };
diff --git a/variants/Ambiguous/rules.html b/variants/Ambiguous/rules.html
new file mode 100644
index 0000000..b51c747
--- /dev/null
+++ b/variants/Ambiguous/rules.html
@@ -0,0 +1,11 @@
+<p>
+  Every move you play can be changed by your opponent by a move arriving
+  on the same square.
+</p>
+
+<p>
+  Consequently, you play twice on each turn: first to select a move for
+  your opponent, then to choose one for you - which could be altered.
+</p>
+
+<p class="author">Fabrice Liardet (2005).</p>
diff --git a/variants/Ambiguous/style.css b/variants/Ambiguous/style.css
new file mode 100644
index 0000000..e31d810
--- /dev/null
+++ b/variants/Ambiguous/style.css
@@ -0,0 +1,43 @@
+@import url("/base_pieces.css");
+
+piece.target {
+  background-image: url('/pieces/ambiguous_target.svg');
+}
+
+piece.white.target-pawn {
+  background-image: url('/pieces/yellow_pawn.svg');
+}
+piece.white.target-rook {
+  background-image: url('/pieces/yellow_rook.svg');
+}
+piece.white.target-knight {
+  background-image: url('/pieces/yellow_knight.svg');
+}
+piece.white.target-bishop {
+  background-image: url('/pieces/yellow_bishop.svg');
+}
+piece.white.target-queen {
+  background-image: url('/pieces/yellow_queen.svg');
+}
+piece.white.target-king {
+  background-image: url('/pieces/yellow_king.svg');
+}
+
+piece.black.target-pawn {
+  background-image: url('/pieces/red_pawn.svg');
+}
+piece.black.target-rook {
+  background-image: url('/pieces/red_rook.svg');
+}
+piece.black.target-knight {
+  background-image: url('/pieces/red_knight.svg');
+}
+piece.black.target-bishop {
+  background-image: url('/pieces/red_bishop.svg');
+}
+piece.black.target-queen {
+  background-image: url('/pieces/red_queen.svg');
+}
+piece.black.target-king {
+  background-image: url('/pieces/red_king.svg');
+}
diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js
index 29fdee9..37261e4 100644
--- a/variants/Chakart/class.js
+++ b/variants/Chakart/class.js
@@ -131,8 +131,8 @@ export default class ChakartRules extends ChessRules {
   }
 
   genRandInitFen(seed) {
-    const gr = new GiveawayRules(
-      {mode: "suicide", options: this.options, genFenOnly: true});
+    const options = Object.assign({mode: "suicide"}, this.options);
+    const gr = new GiveawayRules({options: options, genFenOnly: true});
     // Add Peach + mario flags
     return gr.genRandInitFen(seed).slice(0, -17) + '{"flags":"1111"}';
   }
@@ -180,7 +180,7 @@ export default class ChakartRules extends ChessRules {
     this.moveStack = [];
     // Change seed (after FEN generation!!)
     // so that further calls differ between players:
-    Random.setSeed(Math.floor(10000 * Math.random()));
+    Random.setSeed(Math.floor(19840 * Math.random()));
   }
 
   // For Toadette bonus
diff --git a/variants/Suction/class.js b/variants/Suction/class.js
index f9cf26d..e2e36c8 100644
--- a/variants/Suction/class.js
+++ b/variants/Suction/class.js
@@ -42,8 +42,8 @@ export default class SuctionRules extends ChessRules {
   }
 
   genRandInitFen(seed) {
-    const gr = new GiveawayRules(
-      {mode: "suicide", options: this.options, genFenOnly: true});
+    const options = Object.assign({mode: "suicide"}, this.options);
+    const gr = new GiveawayRules({options: options, genFenOnly: true});
     // Add empty cmove:
     return (
       gr.genRandInitFen(seed).slice(0, -17) + '{"enpassant":"-","cmove":"-"}');
-- 
2.44.0