From 08909cf4fc514e056d04f14086ddccc098f3ec75 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 18 Jan 2021 02:56:44 +0100
Subject: [PATCH] Early draft of Fanorona

---
 client/public/images/pieces/Fanorona/bp.svg   |   9 +
 client/public/images/pieces/Fanorona/wp.svg   |   9 +
 .../images/pieces/{Konane => Yote}/SOURCE     |   0
 client/src/variants/Fanorona.js               | 174 +++++++++++++++++-
 client/src/variants/Yote.js                   |   8 -
 5 files changed, 190 insertions(+), 10 deletions(-)
 create mode 100644 client/public/images/pieces/Fanorona/bp.svg
 create mode 100644 client/public/images/pieces/Fanorona/wp.svg
 rename client/public/images/pieces/{Konane => Yote}/SOURCE (100%)

diff --git a/client/public/images/pieces/Fanorona/bp.svg b/client/public/images/pieces/Fanorona/bp.svg
new file mode 100644
index 00000000..a6ea9214
--- /dev/null
+++ b/client/public/images/pieces/Fanorona/bp.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".3" cy=".3" r=".8">
+<stop offset="0" stop-color="#777"/>
+<stop offset=".3" stop-color="#222"/>
+<stop offset="1" stop-color="#000"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="150" fill="url(#rg)"/>
+</svg>
diff --git a/client/public/images/pieces/Fanorona/wp.svg b/client/public/images/pieces/Fanorona/wp.svg
new file mode 100644
index 00000000..2796fe99
--- /dev/null
+++ b/client/public/images/pieces/Fanorona/wp.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".47" cy=".49" r=".48">
+<stop offset=".7" stop-color="#FFF"/>
+<stop offset=".9" stop-color="#DDD"/>
+<stop offset="1" stop-color="#777"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="150" fill="url(#rg)"/>
+</svg>
diff --git a/client/public/images/pieces/Konane/SOURCE b/client/public/images/pieces/Yote/SOURCE
similarity index 100%
rename from client/public/images/pieces/Konane/SOURCE
rename to client/public/images/pieces/Yote/SOURCE
diff --git a/client/src/variants/Fanorona.js b/client/src/variants/Fanorona.js
index 9f1db3a1..bd92c859 100644
--- a/client/src/variants/Fanorona.js
+++ b/client/src/variants/Fanorona.js
@@ -1,7 +1,177 @@
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
 
 export class FanoronaRules extends ChessRules {
 
-  // TODO
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get Monochrome() {
+    return true;
+  }
+
+  static get Lines() {
+    let lines = [];
+    // Draw all inter-squares lines, shifted:
+    for (let i = 0; i < V.size.x; i++)
+      lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]);
+    for (let j = 0; j < V.size.y; j++)
+      lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]);
+    const columnDiags = [
+      [[0.5, 0.5], [2.5, 2.5]],
+      [[0.5, 2.5], [2.5, 0.5]],
+      [[2.5, 0.5], [4.5, 2.5]],
+      [[4.5, 0.5], [2.5, 2.5]]
+    ];
+    for (let j of [0, 2, 4, 6]) {
+      lines = lines.concat(
+        columnDiags.map(L => [[L[0][0], L[0][1] + j], [L[1][0], L[1][1] + j]])
+      );
+    }
+    return lines;
+  }
+
+  static get Notoodark() {
+    return true;
+  }
+
+  static GenRandInitFen() {
+    return "ppppppppp/ppppppppp/pPpP1pPpP/PPPPPPPPP/PPPPPPPPP w 0";
+  }
+
+  setOtherVariables(fen) {
+    // Local stack of captures during a turn (squares + directions)
+    this.captures = [];
+  }
+
+  static get size() {
+    return { x: 5, y: 9 };
+  }
+
+  static get PIECES() {
+    return [V.PAWN];
+  }
+
+  getPiece() {
+    return V.PAWN;
+  }
+
+  getPpath(b) {
+    return "Fanorona/" + b;
+  }
+
+  //TODO
+  //getPPpath() {}
+
+  getPotentialMovesFrom([x, y]) {
+    // NOTE: (x + y) % 2 == 0 ==> has diagonals
+    // TODO
+    // Même stratégie que Yote, revenir sur ses pas si stop avant de tout capturer
+    // Mais première capture obligatoire (si this.captures.length == 0).
+    // After a capture: allow only capturing.
+    // Warning: case 3 on Wikipedia page, if both percussion & aspiration,
+    // two different moves, cannot take all ==> adjust getPPpath showing arrows.
+    // nice looking arrows, with something representing a capture at its end...
+    return [];
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  //TODO: function aux to detect if continuation captures
+  //(not trivial, but not difficult)
+
+  play(move) {
+    const color = this.turn;
+    move.turn = color; //for undo
+    const captureNotEnding = (
+      move.vanish.length >= 2 &&
+      true //TODO: detect if there are continuation captures
+    );
+    this.captures.push(captureNotEnding); //TODO: something more structured
+                                          //with square + direction of capture
+    if (captureNotEnding) move.notTheEnd = true;
+    else {
+      this.turn = oppCol;
+      this.movesCount++;
+    }
+    this.postPlay(move);
+  }
+
+  undo(move) {
+    V.UndoOnBoard(this.board, move);
+    this.captures.pop();
+    if (move.turn != this.turn) {
+      this.turn = move.turn;
+      this.movesCount--;
+    }
+    this.postUndo(move);
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    // If no stones on board, I lose
+    if (
+      this.board.every(b => {
+        return b.every(cell => {
+          return (cell == "" || cell[0] != color);
+        });
+      })
+    ) {
+      return (color == 'w' ? "0-1" : "1-0");
+    }
+    return "*";
+  }
+
+  getComputerMove() {
+    const moves = super.getAllValidMoves();
+    if (moves.length == 0) return null;
+    const color = this.turn;
+    // Capture available? If yes, play it
+    let captures = moves.filter(m => m.vanish.length >= 2);
+    let mvArray = [];
+    while (captures.length >= 1) {
+      // Then just pick random captures (trying to maximize)
+      let candidates = captures.filter(c => !!c.notTheEnd);
+      let mv = null;
+      if (candidates.length >= 1) mv = candidates[randInt(candidates.length)];
+      else mv = captures[randInt(captures.length)];
+      this.play(mv);
+      captures = (this.turn == color ? super.getAllValidMoves() : []);
+    }
+    if (mvArray.length >= 1) {
+      for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+      return mvArray;
+    }
+    // Just play a random move, which if possible do not let a capture
+    let candidates = [];
+    for (let m of moves) {
+      this.play(m);
+      const moves2 = super.getAllValidMoves();
+      if (moves2.every(m2 => m2.vanish.length <= 1))
+        candidates.push(m);
+      this.undo(m);
+    }
+    if (candidates.length >= 1) return candidates[randInt(candidates.length)];
+    return moves[randInt(moves.length)];
+  }
+
+  getNotation(move) {
+    return (
+      V.CoordsToSquare(move.start) +
+      (move.vanish.length >= 2 ? "x" : "") +
+      V.CoordsToSquare(move.end)
+    );
+  }
 
 };
diff --git a/client/src/variants/Yote.js b/client/src/variants/Yote.js
index 0b967861..7158d4f4 100644
--- a/client/src/variants/Yote.js
+++ b/client/src/variants/Yote.js
@@ -435,14 +435,6 @@ export class YoteRules extends ChessRules {
     return moves[randInt(moves.length)];
   }
 
-  evalPosition() {
-    let evaluation = super.evalPosition();
-    // Add reserves:
-    evaluation += this.reserve["w"][V.PAWN];
-    evaluation -= this.reserve["b"][V.PAWN];
-    return evaluation;
-  }
-
   getNotation(move) {
     if (move.vanish.length == 0)
       // Placement:
-- 
2.44.0