Fix Xiangqi + a few cosmetics
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 11 Dec 2020 23:57:51 +0000 (00:57 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 11 Dec 2020 23:57:51 +0000 (00:57 +0100)
client/src/App.vue
client/src/base_rules.js
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/variants/Minixiangqi.js
client/src/variants/Xiangqi.js
client/src/views/Game.vue
client/src/views/Hall.vue

index 82c9bf3..67dc225 100644 (file)
@@ -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
index 46c09a5..2c2b705 100644 (file)
@@ -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;
index 708fed0..a523ff8 100644 (file)
@@ -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",
index 9542c28..9fd3076 100644 (file)
@@ -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",
index 4adeff3..466137b 100644 (file)
@@ -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",
index c027aed..34e7d19 100644 (file)
@@ -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);
index 3889b20..1b49ced 100644 (file)
@@ -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,
index d66716c..d88ac1e 100644 (file)
@@ -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
index 0b3bb4e..03d3411 100644 (file)
@@ -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);