From 6c7cbfedc6ecf2b49f6b1e27a174039e92a36365 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 16 Apr 2020 04:26:00 +0200
Subject: [PATCH] Some fixes + first draft of Chakart (just thoughts for now)

---
 client/src/components/BaseGame.vue |  85 ++++++++++++++++--------
 client/src/variants/Chakart.js     | 102 +++++++++++++++++++++++++++++
 client/src/variants/Orda.js        |   3 +-
 client/src/variants/Shogi.js       |  18 +++--
 client/src/views/Hall.vue          |   7 +-
 server/models/Game.js              |  13 ++--
 6 files changed, 185 insertions(+), 43 deletions(-)
 create mode 100644 client/src/variants/Chakart.js

diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue
index 62fae753..e309c5df 100644
--- a/client/src/components/BaseGame.vue
+++ b/client/src/components/BaseGame.vue
@@ -218,7 +218,9 @@ export default {
       // Post-processing: decorate each move with notation and FEN
       this.vr = new V(game.fenStart);
       this.inMultimove = false; //in case of
-      this.$refs["board"].resetCurrentAttempt(); //also in case of
+      if (!!this.$refs["board"])
+        // Also in case of:
+        this.$refs["board"].resetCurrentAttempt();
       let analyseBtn = document.getElementById("analyzeBtn");
       if (!!analyseBtn) analyseBtn.classList.remove("active");
       const parsedFen = V.ParseFen(game.fenStart);
@@ -226,27 +228,35 @@ export default {
       this.firstMoveNumber = Math.floor(parsedFen.movesCount / 2) + 1;
       let L = this.moves.length;
       if (L == 0) {
-        this.incheck = [];
-        this.score = "*";
+        // Could be started on a random position in analysis mode:
+        this.incheck = this.vr.getCheckSquares();
+        this.score = this.vr.getCurrentScore();
+        if (this.score != '*') {
+          // Show score on screen
+          const message = getScoreMessage(this.score);
+          this.showEndgameMsg(this.score + " . " + this.st.tr[message]);
+        }
       }
-      this.moves.forEach((move,idx) => {
-        // Strategy working also for multi-moves:
-        if (!Array.isArray(move)) move = [move];
-        const Lm = move.length;
-        move.forEach((m,idxM) => {
-          m.notation = this.vr.getNotation(m);
-          m.unambiguous = V.GetUnambiguousNotation(m);
-          this.vr.play(m);
-          const checkSquares = this.vr.getCheckSquares();
-          if (checkSquares.length > 0) m.notation += "+";
-          if (idxM == Lm - 1) m.fen = this.vr.getFen();
-          if (idx == L - 1 && idxM == Lm - 1) {
-            this.incheck = checkSquares;
-            this.score = this.vr.getCurrentScore();
-            if (["1-0", "0-1"].includes(this.score)) m.notation += "#";
-          }
+      else {
+        this.moves.forEach((move,idx) => {
+          // Strategy working also for multi-moves:
+          if (!Array.isArray(move)) move = [move];
+          const Lm = move.length;
+          move.forEach((m,idxM) => {
+            m.notation = this.vr.getNotation(m);
+            m.unambiguous = V.GetUnambiguousNotation(m);
+            this.vr.play(m);
+            const checkSquares = this.vr.getCheckSquares();
+            if (checkSquares.length > 0) m.notation += "+";
+            if (idxM == Lm - 1) m.fen = this.vr.getFen();
+            if (idx == L - 1 && idxM == Lm - 1) {
+              this.incheck = checkSquares;
+              this.score = this.vr.getCurrentScore();
+              if (["1-0", "0-1"].includes(this.score)) m.notation += "#";
+            }
+          });
         });
-      });
+      }
       if (firstMoveColor == "b") {
         // 'start' & 'end' is required for Board component
         this.moves.unshift({
@@ -420,7 +430,12 @@ export default {
     // "light": if gotoMove() or gotoEnd()
     play: function(move, received, light, autoplay) {
       // Freeze while choices are shown:
-      if (this.$refs["board"].choices.length > 0) return;
+      if (
+        !!this.$refs["board"].selectedPiece ||
+        this.$refs["board"].choices.length > 0
+      ) {
+        return;
+      }
       const navigate = !move;
       // Forbid navigation during autoplay:
       if (navigate && this.autoplay && !autoplay) return;
@@ -595,8 +610,13 @@ export default {
     },
     // "light": if gotoMove() or gotoBegin()
     undo: function(move, light) {
-      // Freeze while choices are shown:
-      if (this.$refs["board"].choices.length > 0 || this.autoplay) return;
+      if (
+        this.autoplay ||
+        !!this.$refs["board"].selectedPiece ||
+        this.$refs["board"].choices.length > 0
+      ) {
+        return;
+      }
       this.$refs["board"].resetCurrentAttempt();
       if (this.inMultimove) {
         this.cancelCurrentMultimove();
@@ -623,7 +643,13 @@ export default {
       }
     },
     gotoMove: function(index) {
-      if (this.$refs["board"].choices.length > 0 || this.autoplay) return;
+      if (
+        this.autoplay ||
+        !!this.$refs["board"].selectedPiece ||
+        this.$refs["board"].choices.length > 0
+      ) {
+        return;
+      }
       this.$refs["board"].resetCurrentAttempt();
       if (this.inMultimove) this.cancelCurrentMultimove();
       if (index == this.cursor) return;
@@ -642,7 +668,13 @@ export default {
       this.emitFenIfAnalyze();
     },
     gotoBegin: function() {
-      if (this.$refs["board"].choices.length > 0 || this.autoplay) return;
+      if (
+        this.autoplay ||
+        !!this.$refs["board"].selectedPiece ||
+        this.$refs["board"].choices.length > 0
+      ) {
+        return;
+      }
       this.$refs["board"].resetCurrentAttempt();
       if (this.inMultimove) this.cancelCurrentMultimove();
       const minCursor =
@@ -655,11 +687,8 @@ export default {
       this.emitFenIfAnalyze();
     },
     gotoEnd: function() {
-      if (this.$refs["board"].choices.length > 0 || this.autoplay) return;
-      this.$refs["board"].resetCurrentAttempt();
       if (this.cursor == this.moves.length - 1) return;
       this.gotoMove(this.moves.length - 1);
-      this.emitFenIfAnalyze();
     },
     flip: function() {
       if (this.$refs["board"].choices.length > 0) return;
diff --git a/client/src/variants/Chakart.js b/client/src/variants/Chakart.js
new file mode 100644
index 00000000..7b2e77e2
--- /dev/null
+++ b/client/src/variants/Chakart.js
@@ -0,0 +1,102 @@
+import { ChessRules } from "@/base_rules";
+
+export class ChakartRules extends ChessRules {
+  // NOTE: getBasicMove, ajouter les bonus à vanish array
+  // + déterminer leur effet (si cavalier) ou case (si banane ou bombe)
+  // (L'effet doit être caché au joueur : devrait être OK)
+  //
+  // Saut possible par dessus bonus ou champis mais pas bananes ou bombes
+
+  // FEN : immobilized (pas flag), castle flags + flags peach (power used?)
+  // "pièces" supplémentaires : bananes, bombes, champis, bonus --> + couleur ?
+  //   (Semble mieux sans couleur => couleur spéciale indiquant que c'est pas jouable)
+  // (Attention: pas jouables cf. getPotentialMoves...)
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.subTurn = 1;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    // TODO: bananes et bombes limitent les déplacements (agissent comme un mur "capturable")
+    // bananes jaunes et rouges ?! (agissant sur une seule couleur ?) --> mauvaise idée.
+    if (this.subTurn == 2) {
+      // TODO: coup compatible avec firstMove
+    }
+  }
+
+  getPotentialPawnMoves(sq) {
+    //Toad: pion
+    //  laisse sur sa case de départ un champi turbo permettant à Peach d'aller
+    //  une case plus loin, et aux pièces arrivant sur cette case de sauter par
+    //  dessus une pièce immédiatement adjacente (en atterissant juste derrière).
+  }
+
+  // Coups en 2 temps (si pose)
+  getPotentialRookMoves(sq) {
+    //Donkey : tour
+    //  pose une banane (optionnel) sur une case adjacente (diagonale) à celle d'arrivée
+    //  Si une pièce arrive sur la peau de banane, alors elle effectue un déplacement
+    //  aléatoire d'une (2?) case (vertical ou horizontal) depuis sa position finale.
+  }
+
+  // Coups en 2 temps (si pose)
+  getPotentialBishopMoves([x, y]) {
+    //Wario: fou
+    //  pose une bombe (optionnel) sur une case orthogonalement adjacente à la case d'arrivée
+    //  Si une pièce arrive sur une bombe, alors elle effectue un déplacement diagonal
+    //  aléatoire d'une (2?) case depuis sa position finale (juste une case si impossible).
+  }
+
+  getPotentialKnightMoves([x, y]) {
+    //Yoshi: cavalier
+    //  laisse sur sa case de départ un bonus aléatoire
+    //  (NOTE: certains bonus pourraient ne pas être applicables ==> pion bloqué par exemple)
+    //    - i) roi boo(*E*) : échange avec n'importe quelle pièce (choix du joueur, type et/ou couleur différents)
+    //                       à condition que la position résultante ne soit pas un auto-échec
+    //    - i*) koopa(*B*) : ramène sur la case initiale
+    //    - ii) toadette(*R*) : permet de poser une pièce capturée sur le plateau
+    //                         (n'importe où sauf 8eme rangée pour les pions)
+    //    - ii*) chomp(*W*) : mange la pièce ; si c'est Peach, c'est perdu
+    //    - iii) daisy(*T*) : permet de rejouer un coup avec la même pièce --> cumulable si ensuite coup sur bonus Daisy.
+    //    - iii*) bowser(*M*) : immobilise la pièce (marquée jaune/rouge), qui ne pourra pas jouer au tour suivant
+    //    - iv) luigi(*L*) : fait changer de camp une pièce adverse (aléatoire ?) (sauf le roi)
+    //    - iv*) waluigi(*D*) : fait changer de camp une de nos pièces (aléatoire ?) (sauf le roi)
+    //  --> i, ii, iii en deux temps (subTurn 1 & 2)
+    //      iii* : indication dans FEN (immobilized)
+  }
+
+  getPotentialQueenMoves(sq) {
+    //Mario: dame
+    //  pouvoir "casquette ailée" (à chaque coup?) : peut sauter par dessus n'importe quelle pièce (une seule), sans la capturer.
+  }
+
+  getPotentialKingMoves(sq) {
+    //Peach: roi
+    //  Carapace rouge (disons ^^) jouable une seule fois dans la partie,
+    //  au lieu de se déplacer. Capture un ennemi au choix parmi les plus proches,
+    //  à condition qu'ils soient visible (suivant les directions de déplacement d'une dame).
+    //  Profite des accélérateurs posés par les pions (+ 1 case : obligatoire).
+  }
+
+  play(move) {
+    // TODO: subTurn passe à 2 si potentiellement pose (tour, fou) ou si choix (reconnaître i (ok), ii (ok) et iii (si coup normal + pas immobilisé) ?)
+    // voire +2 si plusieurs daisy...
+  }
+
+  undo(move) {
+    // TODO: reconnaissance inverse si subTurn == 1 --> juste impossible ==> marquer pendant play (comme DoubleMove1 : move.turn = ...)
+  }
+
+  //atLeastOneMove() should be OK
+
+  getCurrentScore() {
+    //TODO: But = capturer la princesse adverse. Variante : but = la princesse arrive de l'autre côté de l'échiquier.
+    //==> On peut mixer ces deux conditions : arriver au bout du plateau ou capturer la princesse adverse.
+    return '*';
+  }
+
+  //Détails :
+  //Si une pièce pose quelque chose sur une case ça remplace ce qui y était déjà.
+  //Pas de condition de pat, puisque l'objectif est la capture de la reine :)
+};
diff --git a/client/src/variants/Orda.js b/client/src/variants/Orda.js
index f1bfc911..afb736a7 100644
--- a/client/src/variants/Orda.js
+++ b/client/src/variants/Orda.js
@@ -234,13 +234,14 @@ export class OrdaRules extends ChessRules {
         this.isAttackedByKheshig(sq, color)
       );
     }
-    // Horde: only pawn and queen (if promotions) in common:
+    // Horde: only pawn, king and queen (if promotions) in common:
     return (
       super.isAttackedByPawn(sq, color) ||
       this.isAttackedByLancer(sq, color) ||
       this.isAttackedByKheshig(sq, color) ||
       this.isAttackedByArcher(sq, color) ||
       this.isAttackedByYurt(sq, color) ||
+      super.isAttackedByKing(sq, color) ||
       super.isAttackedByQueen(sq, color)
     );
   }
diff --git a/client/src/variants/Shogi.js b/client/src/variants/Shogi.js
index 082d9391..50d273f2 100644
--- a/client/src/variants/Shogi.js
+++ b/client/src/variants/Shogi.js
@@ -372,6 +372,18 @@ export class ShogiRules extends ChessRules {
     );
   }
 
+  getPotentialLanceMoves(sq) {
+    const forward = (this.turn == 'w' ? -1 : 1);
+    return this.getSlideNJumpMoves(
+      sq,
+      [[forward, 0]],
+      {
+        promote: V.P_LANCE,
+        force: true
+      }
+    );
+  }
+
   getPotentialRookMoves(sq) {
     return this.getSlideNJumpMoves(
       sq, V.steps[V.ROOK], { promote: V.P_ROOK });
@@ -382,12 +394,6 @@ export class ShogiRules extends ChessRules {
       sq, V.steps[V.BISHOP], { promote: V.P_BISHOP });
   }
 
-  getPotentialLanceMoves(sq) {
-    const forward = (this.turn == 'w' ? -1 : 1);
-    return this.getSlideNJumpMoves(
-      sq, [[forward, 0]], { promote: V.P_LANCE });
-  }
-
   getPotentialDragonMoves(sq) {
     return (
       this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index 133a5f9e..01f588f2 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -822,12 +822,13 @@ export default {
           // Ignore games where I play (will go in MyGames page),
           // and also games that I already received.
           if (
+            this.games.findIndex(g => g.id == game.id) == -1 &&
             game.players.every(p => {
               return (
                 p.sid != this.st.user.sid &&
                 (p.id == 0 || p.id != this.st.user.id)
               );
-            }) && this.games.findIndex(g => g.id == game.id) == -1
+            })
           ) {
             let newGame = game;
             newGame.type = this.classifyObject(game);
@@ -837,6 +838,7 @@ export default {
               newGame.score = "*";
             this.games.push(newGame);
             if (
+              newGame.score == '*' &&
               (newGame.type == "live" && this.gdisplay == "corr") ||
               (newGame.type == "corr" && this.gdisplay == "live")
             ) {
@@ -888,7 +890,8 @@ export default {
               if (
                 this.cursor == Number.MAX_SAFE_INTEGER &&
                 this.games.length == 0 &&
-                this.gdisplay == "live"
+                this.gdisplay == "live" &&
+                res.games.some(g => g.score == '*')
               ) {
                 // First loading: show indicators
                 document
diff --git a/server/models/Game.js b/server/models/Game.js
index 401e3653..1d2cd684 100644
--- a/server/models/Game.js
+++ b/server/models/Game.js
@@ -128,12 +128,13 @@ const GameModel =
     db.serialize(function() {
       let query =
         "SELECT id, vid, cadence, created, score, white, black " +
-        "FROM Games ";
-      if (uid > 0) query +=
-        "WHERE " +
-        "  created < " + cursor + " AND " +
-        "  white <> " + uid + " AND " +
-        "  black <> " + uid + " ";
+        "FROM Games " +
+        "WHERE created < " + cursor + " ";
+      if (uid > 0) {
+        query +=
+          "  AND white <> " + uid + " " +
+          "  AND black <> " + uid + " ";
+      }
       query +=
         "ORDER BY created DESC " +
         "LIMIT 20"; //TODO: 20 hard-coded...
-- 
2.44.0