From 9a1e3abe33fff07218b17c7c799eb622a730b7c7 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 12 Dec 2020 00:57:51 +0100
Subject: [PATCH] Fix Xiangqi + a few cosmetics

---
 client/src/App.vue                 |  4 +-
 client/src/base_rules.js           |  5 ++
 client/src/translations/en.js      |  1 +
 client/src/translations/es.js      |  1 +
 client/src/translations/fr.js      |  1 +
 client/src/variants/Minixiangqi.js | 11 ----
 client/src/variants/Xiangqi.js     | 94 +++++++++++++++++++++++++++---
 client/src/views/Game.vue          |  6 +-
 client/src/views/Hall.vue          |  2 +-
 9 files changed, 102 insertions(+), 23 deletions(-)

diff --git a/client/src/App.vue b/client/src/App.vue
index 82c9bf33..67dc225b 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -157,7 +157,9 @@ table
   padding: 0 10px 0 0
   height: 100%
   & > span
-    padding: 0 5px 0 0
+    padding-top: 0
+    padding-bottom: 0
+    padding-right: 5px
     vertical-align: middle
   & > img
     padding: 0
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index 46c09a5c..2c2b705c 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -126,6 +126,11 @@ export const ChessRules = class ChessRules {
     return null;
   }
 
+  // In some variants, the player who repeat a position loses
+  static get LoseOnRepetition() {
+    return false;
+  }
+
   // Some variants use click infos:
   doClick() {
     return null;
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 708fed0a..a523ff82 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -116,6 +116,7 @@ export const translations = {
   Rematch: "Rematch",
   "Rematch in progress": "Rematch in progress",
   "Remove game?": "Remove game?",
+  Repetition: "Repetition",
   Resign: "Resign",
   "Resign the game?": "Resign the game?",
   "Resize board": "Resize board",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 9542c286..9fd30769 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -116,6 +116,7 @@ export const translations = {
   Rematch: "Revancha",
   "Rematch in progress": "Revancha en progreso",
   "Remove game?": "¿Eliminar la partida?",
+  Repetition: "Repetición",
   Resign: "Abandonar",
   "Resign the game?": "¿Abandonar la partida?",
   "Resize board": "Redimensionar el tablero",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 4adeff38..466137b4 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -116,6 +116,7 @@ export const translations = {
   Rematch: "Rejouer",
   "Rematch in progress": "Revanche en cours",
   "Remove game?": "Supprimer la partie ?",
+  Repetition: "Répétition",
   Resign: "Abandonner",
   "Resign the game?": "Abandonner la partie ?",
   "Resize board": "Redimensionner l'échiquier",
diff --git a/client/src/variants/Minixiangqi.js b/client/src/variants/Minixiangqi.js
index c027aedf..34e7d198 100644
--- a/client/src/variants/Minixiangqi.js
+++ b/client/src/variants/Minixiangqi.js
@@ -31,17 +31,6 @@ export class MinixiangqiRules extends XiangqiRules {
     return { x: 7, y: 7};
   }
 
-  getPotentialMovesFrom(sq) {
-    switch (this.getPiece(sq[0], sq[1])) {
-      case V.PAWN: return this.getPotentialPawnMoves(sq);
-      case V.ROOK: return super.getPotentialRookMoves(sq);
-      case V.KNIGHT: return super.getPotentialKnightMoves(sq);
-      case V.KING: return super.getPotentialKingMoves(sq);
-      case V.CANNON: return super.getPotentialCannonMoves(sq);
-    }
-    return []; //never reached
-  }
-
   getPotentialPawnMoves([x, y]) {
     const c = this.getColor(x, y);
     const shiftX = (c == 'w' ? -1 : 1);
diff --git a/client/src/variants/Xiangqi.js b/client/src/variants/Xiangqi.js
index 3889b20a..1b49cedb 100644
--- a/client/src/variants/Xiangqi.js
+++ b/client/src/variants/Xiangqi.js
@@ -36,6 +36,10 @@ export class XiangqiRules extends ChessRules {
     return false;
   }
 
+  static get LoseOnRepetition() {
+    return true;
+  }
+
   static get ELEPHANT() {
     return "e";
   }
@@ -61,16 +65,80 @@ export class XiangqiRules extends ChessRules {
   }
 
   getPotentialMovesFrom(sq) {
-    switch (this.getPiece(sq[0], sq[1])) {
-      case V.PAWN: return this.getPotentialPawnMoves(sq);
-      case V.ROOK: return super.getPotentialRookMoves(sq);
-      case V.KNIGHT: return this.getPotentialKnightMoves(sq);
-      case V.ELEPHANT: return this.getPotentialElephantMoves(sq);
-      case V.ADVISOR: return this.getPotentialAdvisorMoves(sq);
-      case V.KING: return this.getPotentialKingMoves(sq);
-      case V.CANNON: return this.getPotentialCannonMoves(sq);
+    let moves = [];
+    const piece = this.getPiece(sq[0], sq[1]);
+    switch (piece) {
+      case V.PAWN:
+        moves = this.getPotentialPawnMoves(sq);
+        break;
+      case V.ROOK:
+        moves = super.getPotentialRookMoves(sq);
+        break;
+      case V.KNIGHT:
+        moves = this.getPotentialKnightMoves(sq);
+        break;
+      case V.ELEPHANT:
+        moves = this.getPotentialElephantMoves(sq);
+        break;
+      case V.ADVISOR:
+        moves = this.getPotentialAdvisorMoves(sq);
+        break;
+      case V.KING:
+        moves = this.getPotentialKingMoves(sq);
+        break;
+      case V.CANNON:
+        moves = this.getPotentialCannonMoves(sq);
+        break;
+    }
+    if (piece != V.KING && this.kingPos['w'][1] != this.kingPos['b'][1])
+      return moves;
+    if (this.kingPos['w'][1] == this.kingPos['b'][1]) {
+      const colKing = this.kingPos['w'][1];
+      let intercept = 0; //count intercepting pieces
+      for (let i = this.kingPos['b'][0] + 1; i < this.kingPos['w'][0]; i++) {
+        if (this.board[i][colKing] != V.EMPTY) intercept++;
+      }
+      if (intercept >= 2) return moves;
+      // intercept == 1 (0 is impossible):
+      // Any move not removing intercept is OK
+      return moves.filter(m => {
+        return (
+          // From another column?
+          m.start.y != colKing ||
+          // From behind a king? (including kings themselves!)
+          m.start.x <= this.kingPos['b'][0] ||
+          m.start.x >= this.kingPos['w'][0] ||
+          // Intercept piece moving: must remain in-between
+          (
+            m.end.y == colKing &&
+            m.end.x > this.kingPos['b'][0] &&
+            m.end.x < this.kingPos['w'][0]
+          )
+        );
+      });
     }
-    return []; //never reached
+    // piece == king: check only if move.end.y == enemy king column
+    const color = this.getColor(sq[0], sq[1]);
+    const oppCol = V.GetOppCol(color);
+    // colCheck == -1 if unchecked, 1 if checked and occupied,
+    //              0 if checked and clear
+    let colCheck = -1;
+    return moves.filter(m => {
+      if (m.end.y != this.kingPos[oppCol][1]) return true;
+      if (colCheck < 0) {
+        // Do the check:
+        colCheck = 0;
+        for (let i = this.kingPos['b'][0] + 1; i < this.kingPos['w'][0]; i++) {
+          if (this.board[i][m.end.y] != V.EMPTY) {
+            colCheck++;
+            break;
+          }
+        }
+        return colCheck == 1;
+      }
+      // Check already done:
+      return colCheck == 1;
+    });
   }
 
   getPotentialPawnMoves([x, y]) {
@@ -255,6 +323,14 @@ export class XiangqiRules extends ChessRules {
     return false;
   }
 
+  getCurrentScore() {
+    if (this.atLeastOneMove()) return "*";
+    // Game over
+    const color = this.turn;
+    // No valid move: I lose!
+    return (color == "w" ? "0-1" : "1-0");
+  }
+
   static get VALUES() {
     return {
       p: 1,
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index d66716c2..d88ac1e2 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -1391,7 +1391,11 @@ export default {
           !!this.repeat[fenObj]
             ? this.repeat[fenObj] + 1
             : 1;
-        if (this.repeat[fenObj] >= 3) this.drawOffer = "threerep";
+        if (this.repeat[fenObj] >= 3) {
+          if (V.LoseOnRepetition)
+            this.gameOver(moveCol == "w" ? "0-1" : "1-0", "Repetition");
+          else this.drawOffer = "threerep";
+        }
         else if (this.drawOffer == "threerep") this.drawOffer = "";
         if (!!this.game.mycolor && !data.receiveMyMove) {
           // NOTE: 'var' to see that variable outside this block
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index 0b3bb4ef..03d34119 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -1350,7 +1350,7 @@ export default {
                 new Audio("/sounds/newgame.flac").play().catch(() => {});
               notify(
                 "New live game",
-                { body: "vs " + game.players[1-myIdx].name || "@nonymous" }
+                { body: "vs " + (game.players[1-myIdx].name || "@nonymous") }
               );
             }
             this.$router.push("/game/" + gameInfo.id);
-- 
2.44.0