From 83cecc0fec143a6eac6f6632048d99398bd4c0da Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 28 Dec 2020 20:57:08 +0100
Subject: [PATCH] Implement Wildebeest castling rule (well... almost correct)

---
 TODO                                          |  3 -
 .../images/pieces/Wildebeest/castle.svg       |  1 +
 client/src/components/Board.vue               |  6 +-
 .../src/translations/rules/Wildebeest/en.pug  | 14 ++--
 .../src/translations/rules/Wildebeest/es.pug  | 11 +--
 .../src/translations/rules/Wildebeest/fr.pug  | 11 +--
 client/src/variants/Wildebeest.js             | 71 ++++++++++++++++++-
 7 files changed, 86 insertions(+), 31 deletions(-)
 create mode 120000 client/public/images/pieces/Wildebeest/castle.svg

diff --git a/TODO b/TODO
index bb5989e8..227683d7 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,3 @@
-Implement Wildebeest castle rules
-=> (1, 2, 3 or 4 squares slide; randomized: may be impossible >1, but possible >4...)
-
 Embedded rules language not updated when language is set (in Analyse, Game and Problems)
 If new live game starts in background, "new game" notify OK but not first move (not too serious however)
 On smartphone for Teleport, Chakart, Weiqi and some others: option "confirm moves on touch screen"
diff --git a/client/public/images/pieces/Wildebeest/castle.svg b/client/public/images/pieces/Wildebeest/castle.svg
new file mode 120000
index 00000000..97a07ec4
--- /dev/null
+++ b/client/public/images/pieces/Wildebeest/castle.svg
@@ -0,0 +1 @@
+../Coregal/castle.svg
\ No newline at end of file
diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue
index 1d650ac0..d45f33b8 100644
--- a/client/src/components/Board.vue
+++ b/client/src/components/Board.vue
@@ -402,10 +402,12 @@ export default {
               this.choices = [];
               this.play(m);
             };
+            const stopPropagation = (e) => { e.stopPropagation(); }
             const onClick =
               this.mobileBrowser
-                ? { touchend: applyMove }
-                : { mouseup: applyMove };
+                // Must cancel mousedown logic:
+                ? { touchstart: stopPropagation, touchend: applyMove }
+                : { mousedown: stopPropagation, mouseup: applyMove };
             return h(
               "div",
               {
diff --git a/client/src/translations/rules/Wildebeest/en.pug b/client/src/translations/rules/Wildebeest/en.pug
index 7d9dd396..aee7cbf0 100644
--- a/client/src/translations/rules/Wildebeest/en.pug
+++ b/client/src/translations/rules/Wildebeest/en.pug
@@ -19,17 +19,13 @@ figure.diagram-container
 h3 Special moves
 
 p.
-  Castling is possible as in orthodox 8x8 game. The white king move to c1 or j1
-  (one square to the left of bottom-right corner) for large (resp. small)
-  castle. Same for black on the other side.
+  Castling is possible, and more flexible than in orthodox game:
+  the king can slide any number of (free) squares toward the rook,
+  which will end next to him on the other side.
 
 p.
-  Promotion occurs when pawns reach last rank.
-  They can only transform into a queen or a wildebeest.
-
-p.
-  Note: the castling rule is more restrictive than described in the original
-  rules. The game seems OK like that, but this may change in the future.
+  When a pawn reaches last rank, it can promote
+  into a queen or a wildebeest (only).
 
 h3 Source
 
diff --git a/client/src/translations/rules/Wildebeest/es.pug b/client/src/translations/rules/Wildebeest/es.pug
index 76af898a..9b4580dd 100644
--- a/client/src/translations/rules/Wildebeest/es.pug
+++ b/client/src/translations/rules/Wildebeest/es.pug
@@ -20,17 +20,12 @@ figure.diagram-container
 h3 Movimientos especiales
 
 p.
-  El enroque es posible como en un juego de ajedrez ortodoxo 8x8.
-  El rey blanco va a c1 o j1 (un cuadro a la izquierda de la esquina inferior
-  derecha) para un enroque grande (resp. pequeño). Lo mismo para las negras
-  del otro lado.
+  El enroque es posible y más flexible que en el juego ortodoxo:
+  el rey puede saltar cualquier número de casillas (libres) a la torre,
+  que terminará junto a él en el otro lado.
 
 p Un peón en la última fila es promovido a dama o ñu solamente.
 
-p.
-  Nota: la regla de enroque es más restrictiva que la descrita por las reglas
-  originales. El juego se ve bien así, pero puede cambiar algún día.
-
 h3 Fuente
 
 p
diff --git a/client/src/translations/rules/Wildebeest/fr.pug b/client/src/translations/rules/Wildebeest/fr.pug
index 3736086c..63ccdabd 100644
--- a/client/src/translations/rules/Wildebeest/fr.pug
+++ b/client/src/translations/rules/Wildebeest/fr.pug
@@ -20,19 +20,14 @@ figure.diagram-container
 h3 Coups spéciaux
 
 p.
-  Le roque est possible comme dans une partie d'échecs orthodoxe 8x8.
-  Le roi blanc va en c1 ou j1 (une case à gauche du coin inférieur droit).
-  pour un grand (resp. petit) roque. Pareil pour les noirs de l'autre côté.
+  Le roque est possible, et plus souple que dans le jeu orthodoxe :
+  le roi peut sauter d'un nombre quelconque de cases (libres) vers la tour,
+  qui se retrouvera à côté de lui de l'autre côté.
 
 p.
   Un pion arrivé sur la dernière rangée se promeut en une dame ou un gnou
   seulement.
 
-p.
-  Note : la règle du roque est plus restrictive que celle décrite par les
-  règles originelles. Le jeu semble OK comme ça, mais cela pourrait changer un
-  jour.
-
 h3 Source
 
 p
diff --git a/client/src/variants/Wildebeest.js b/client/src/variants/Wildebeest.js
index 7a9e35c4..a2fc6148 100644
--- a/client/src/variants/Wildebeest.js
+++ b/client/src/variants/Wildebeest.js
@@ -1,4 +1,4 @@
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
 import { ArrayFun } from "@/utils/array";
 import { sample, randInt } from "@/utils/alea";
 
@@ -190,6 +190,75 @@ export class WildebeestRules extends ChessRules {
     );
   }
 
+  getPPpath(m) {
+    if (
+      m.appear.length == 2 && m.vanish.length == 2 &&
+      Math.abs(m.end.y - m.start.y) == 1 &&
+      this.board[m.end.x][m.end.y] == V.EMPTY
+    ) {
+      // Castle, king moved by one square only, not directly onto rook
+      return "Wildebeest/castle";
+    }
+    return super.getPPpath(m);
+  }
+
+  // Special Wildebeest castling rules:
+  getCastleMoves([x, y]) {
+    const c = this.getColor(x, y);
+    const oppCol = V.GetOppCol(c);
+    let moves = [];
+    let i = 0;
+    const castlingKing = this.board[x][y].charAt(1);
+    castlingCheck: for (
+      let castleSide = 0;
+      castleSide < 2;
+      castleSide++ //"large", then "small"
+    ) {
+      if (this.castleFlags[c][castleSide] >= V.size.y) continue;
+      // Rook and king are on initial position
+      const rookPos = this.castleFlags[c][castleSide];
+      const range = (castleSide == 0 ? [rookPos, y] : [y, rookPos]);
+
+      // King and rook must be connected:
+      for (let i = range[0] + 1; i <= range[1] - 1; i++) {
+        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
+      }
+      const step = 2 * castleSide - 1;
+      // No attacks on the path of the king ?
+      for (let i = range[0]; i <= range[1]; i++) {
+        if (i != rookPos && this.isAttacked([x, i], oppCol))
+          continue castlingCheck;
+        if (i != y) {
+          // Found a possible castle move:
+          moves.push(
+            new Move({
+              appear: [
+                new PiPo({
+                  x: x,
+                  y: i,
+                  p: V.KING,
+                  c: c
+                }),
+                new PiPo({
+                  x: x,
+                  y: i - step,
+                  p: V.ROOK,
+                  c: c
+                })
+              ],
+              vanish: [
+                new PiPo({ x: x, y: y, p: V.KING, c: c }),
+                new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
+              ]
+            })
+          );
+        }
+      }
+    }
+
+    return moves;
+  }
+
   isAttacked(sq, color) {
     return (
       super.isAttacked(sq, color) ||
-- 
2.44.0