From e727fe31742dfb3e40eb222c94f4199e2be98453 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 10 Mar 2020 01:26:33 +0100
Subject: [PATCH] Fix games ordering in MyGames, fix en-passant mistake in
 Rifle variant

---
 client/src/base_rules.js           | 13 ++---
 client/src/components/GameList.vue | 48 +++--------------
 client/src/utils/alea.js           |  4 +-
 client/src/variants/Atomic.js      |  7 +++
 client/src/variants/Checkered.js   | 78 +++++++++++++++++++++++++++
 client/src/variants/Crazyhouse.js  |  7 +++
 client/src/variants/Recycle.js     |  7 +++
 client/src/variants/Rifle.js       |  7 +++
 client/src/views/Hall.vue          | 20 +++++--
 client/src/views/MyGames.vue       | 87 ++++++++++++++++++------------
 10 files changed, 188 insertions(+), 90 deletions(-)

diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index aec119a0..f0fe9e6a 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -197,14 +197,10 @@ export const ChessRules = class ChessRules {
     const move = moveOrSquare;
     const s = move.start,
           e = move.end;
-    // NOTE: next conditions are first for Crazyhouse, and last for Checkered
-    // TODO: Checkered exceptions are too weird and should move in its own file.
     if (
-      move.vanish.length > 0 &&
       Math.abs(s.x - e.x) == 2 &&
       s.y == e.y &&
-      move.vanish[0].p == V.PAWN &&
-      ["w", "b"].includes(move.vanish[0].c)
+      move.appear[0].p == V.PAWN
     ) {
       return {
         x: (s.x + e.x) / 2,
@@ -645,7 +641,6 @@ export const ChessRules = class ChessRules {
     const firstRank = color == "w" ? sizeX - 1 : 0;
     const startRank = color == "w" ? sizeX - 2 : 1;
     const lastRank = color == "w" ? 0 : sizeX - 1;
-    const pawnColor = this.getColor(x, y); //can be different for checkered
 
     // NOTE: next condition is generally true (no pawn on last rank)
     if (x + shiftX >= 0 && x + shiftX < sizeX) {
@@ -658,7 +653,7 @@ export const ChessRules = class ChessRules {
         for (let piece of finalPieces) {
           moves.push(
             this.getBasicMove([x, y], [x + shiftX, y], {
-              c: pawnColor,
+              c: color,
               p: piece
             })
           );
@@ -683,7 +678,7 @@ export const ChessRules = class ChessRules {
           for (let piece of finalPieces) {
             moves.push(
               this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
-                c: pawnColor,
+                c: color,
                 p: piece
               })
             );
@@ -1001,7 +996,7 @@ export const ChessRules = class ChessRules {
   // After move is played, update variables + flags
   updateVariables(move) {
     let piece = undefined;
-    // TODO: update variables before move is played, and just use this.turn ?
+    // TODO: update variables before move is played, and just use this.turn?
     // (doesn't work in general, think MarseilleChess)
     let c = undefined;
     if (move.vanish.length >= 1) {
diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue
index 183d6463..71602028 100644
--- a/client/src/components/GameList.vue
+++ b/client/src/components/GameList.vue
@@ -11,7 +11,7 @@ div
       tr(
         v-for="g in sortedGames"
         @click="$emit('show-game',g)"
-        :class="{'my-turn': g.myTurn}"
+        :class="{'my-turn': !!g.myTurn}"
       )
         td {{ g.vname }}
         td {{ player_s(g) }}
@@ -54,52 +54,20 @@ export default {
   },
   computed: {
     sortedGames: function() {
-      // Show in order: games where it's my turn, my running games, my games, other games
+      // Show in order: it's my turn, running games, completed games
       let minCreated = Number.MAX_SAFE_INTEGER;
       let maxCreated = 0;
-      const isMyTurn = (g, myColor) => {
-        const rem = g.movesCount % 2;
-        return (
-          (rem == 0 && myColor == "w") ||
-          (rem == 1 && myColor == "b")
-        );
-      };
-      let augmentedGames = this.games
-        .filter(g => !this.deleted[g.id])
-        .map(g => {
-          let priority = 0;
-          let myColor = undefined;
-          if (
-            g.players.some(
-              p => p.uid == this.st.user.id || p.sid == this.st.user.sid
-            )
-          ) {
-            priority++;
-            myColor =
-              g.players[0].uid == this.st.user.id ||
-              g.players[0].sid == this.st.user.sid
-                ? "w"
-                : "b";
-            if (g.score == "*") {
-              priority++;
-              if (g.turn == myColor || isMyTurn(g, myColor)) priority++;
-            }
-          }
-          if (g.created < minCreated) minCreated = g.created;
-          if (g.created > maxCreated) maxCreated = g.created;
-          return Object.assign({}, g, {
-            priority: priority,
-            myTurn: priority == 3,
-            myColor: myColor
-          });
-        });
+      this.games.forEach(g => {
+        if (g.created < minCreated) minCreated = g.created;
+        if (g.created > maxCreated) maxCreated = g.created;
+      });
       const deltaCreated = maxCreated - minCreated;
-      return augmentedGames.sort((g1, g2) => {
+      return this.games.sort((g1, g2) => {
         return (
           g2.priority - g1.priority + (g2.created - g1.created) / deltaCreated
         );
       });
-    }
+    },
   },
   methods: {
     player_s: function(g) {
diff --git a/client/src/utils/alea.js b/client/src/utils/alea.js
index 9ff11593..1f1d1460 100644
--- a/client/src/utils/alea.js
+++ b/client/src/utils/alea.js
@@ -22,9 +22,7 @@ export function sample(arr, n) {
   let cpArr = arr.map(e => e);
   for (let index = 0; index < n; index++) {
     const rand = randInt(index, arr.length);
-    const temp = cpArr[index];
-    cpArr[index] = cpArr[rand];
-    cpArr[rand] = temp;
+    [ cpArr[index], cpArr[rand] ] = [ cpArr[rand], cpArr[index] ];
   }
   return cpArr.slice(0, n);
 }
diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js
index 35def3fa..4aad7b72 100644
--- a/client/src/variants/Atomic.js
+++ b/client/src/variants/Atomic.js
@@ -1,6 +1,13 @@
 import { ChessRules, PiPo } from "@/base_rules";
 
 export const VariantRules = class AtomicRules extends ChessRules {
+  getEpSquare(moveOrSquare) {
+    if (typeof moveOrSquare !== "object" || move.appear.length > 0)
+      return super.getEpSquare(moveOrSquare);
+    // Capturing move: no en-passant
+    return undefined;
+  }
+
   getPotentialMovesFrom([x, y]) {
     let moves = super.getPotentialMovesFrom([x, y]);
 
diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js
index 5f9bf8f6..fa3a6b7e 100644
--- a/client/src/variants/Checkered.js
+++ b/client/src/variants/Checkered.js
@@ -90,6 +90,13 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     this.pawnFlags = flags[1];
   }
 
+  getEpSquare(moveOrSquare) {
+    if (typeof moveOrSquare !== "object" || move.appear[0].c != 'c')
+      return super.getEpSquare(moveOrSquare);
+    // Checkered move: no en-passant
+    return undefined;
+  }
+
   getCmove(move) {
     if (move.appear[0].c == "c" && move.vanish.length == 1)
       return { start: move.start, end: move.end };
@@ -149,6 +156,77 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     return moves;
   }
 
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const shiftX = color == "w" ? -1 : 1;
+    const startRank = color == "w" ? sizeX - 2 : 1;
+    const lastRank = color == "w" ? 0 : sizeX - 1;
+    const pawnColor = this.getColor(x, y); //can be  checkered
+
+    const finalPieces =
+      x + shiftX == lastRank
+        ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
+        : [V.PAWN];
+    if (this.board[x + shiftX][y] == V.EMPTY) {
+      // One square forward
+      for (let piece of finalPieces) {
+        moves.push(
+          this.getBasicMove([x, y], [x + shiftX, y], {
+            c: pawnColor,
+            p: piece
+          })
+        );
+      }
+      if (
+        x == startRank &&
+        this.board[x + 2 * shiftX][y] == V.EMPTY
+      ) {
+        // Two squares jump
+        moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
+      }
+    }
+    // Captures
+    for (let shiftY of [-1, 1]) {
+      if (
+        y + shiftY >= 0 &&
+        y + shiftY < sizeY &&
+        this.board[x + shiftX][y + shiftY] != V.EMPTY &&
+        this.canTake([x, y], [x + shiftX, y + shiftY])
+      ) {
+        for (let piece of finalPieces) {
+          moves.push(
+            this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
+              c: pawnColor,
+              p: piece
+            })
+          );
+        }
+      }
+    }
+
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    if (
+      !!epSquare &&
+      epSquare.x == x + shiftX &&
+      Math.abs(epSquare.y - y) == 1
+    ) {
+      let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare.y,
+        p: "p",
+        c: this.getColor(x, epSquare.y)
+      });
+      moves.push(enpassantMove);
+    }
+
+    return moves;
+  }
+
   canIplay(side, [x, y]) {
     return side == this.turn && [side, "c"].includes(this.getColor(x, y));
   }
diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js
index 16db148d..ff705c73 100644
--- a/client/src/variants/Crazyhouse.js
+++ b/client/src/variants/Crazyhouse.js
@@ -28,6 +28,13 @@ export const VariantRules = class CrazyhouseRules extends ChessRules {
     });
   }
 
+  getEpSquare(moveOrSquare) {
+    if (typeof moveOrSquare !== "object" || move.vanish.length > 0)
+      return super.getEpSquare(moveOrSquare);
+    // Landing move: no en-passant
+    return undefined;
+  }
+
   static GenRandInitFen(randomness) {
     return ChessRules.GenRandInitFen(randomness) + " 0000000000 -";
   }
diff --git a/client/src/variants/Recycle.js b/client/src/variants/Recycle.js
index 9f43ad80..73d91669 100644
--- a/client/src/variants/Recycle.js
+++ b/client/src/variants/Recycle.js
@@ -18,6 +18,13 @@ export const VariantRules = class RecycleRules extends ChessRules {
     });
   }
 
+  getEpSquare(moveOrSquare) {
+    if (typeof moveOrSquare !== "object" || move.vanish.length > 0)
+      return super.getEpSquare(moveOrSquare);
+    // Landing move: no en-passant
+    return undefined;
+  }
+
   static GenRandInitFen(randomness) {
     return ChessRules.GenRandInitFen(randomness) + " 0000000000";
   }
diff --git a/client/src/variants/Rifle.js b/client/src/variants/Rifle.js
index ccec48ef..631fb13a 100644
--- a/client/src/variants/Rifle.js
+++ b/client/src/variants/Rifle.js
@@ -1,6 +1,13 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export const VariantRules = class RifleRules extends ChessRules {
+  getEpSquare(moveOrSquare) {
+    if (typeof moveOrSquare !== "object" || move.appear.length > 0)
+      return super.getEpSquare(moveOrSquare);
+    // Capturing move: no en-passant
+    return undefined;
+  }
+
   getBasicMove([sx, sy], [ex, ey], tr) {
     let mv = new Move({
       appear: [],
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index 5217c34d..92030e74 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -256,7 +256,15 @@ export default {
             response.games.map(g => {
               const type = this.classifyObject(g);
               const vname = this.getVname(g.vid);
-              return Object.assign({}, g, { type: type, vname: vname });
+              return Object.assign(
+                {},
+                g,
+                {
+                  type: type,
+                  vname: vname,
+                  priority: g.score == "*" ? 1 : 0 //for display
+                }
+              );
             })
           );
         }
@@ -694,9 +702,11 @@ export default {
               let newGame = game;
               newGame.type = this.classifyObject(game);
               newGame.vname = this.getVname(game.vid);
+              newGame.priority = 0;
               if (!game.score)
-                //if new game from Hall
+                // New game from Hall
                 newGame.score = "*";
+              if (newGame.score == "*") newGame.priority++;
               newGame.rids = [game.rid];
               delete newGame["rid"];
               this.games.push(newGame);
@@ -717,7 +727,10 @@ export default {
         }
         case "result": {
           let g = this.games.find(g => g.id == data.gid);
-          if (!!g) g.score = data.score;
+          if (!!g) {
+            g.score = data.score;
+            g.priority = 0;
+          }
           break;
         }
         case "startgame": {
@@ -907,6 +920,7 @@ export default {
       }
       this.send("deletechallenge", { data: c.id });
     },
+    // TODO: if several players click same challenge at the same time: problem
     clickChallenge: async function(c) {
       const myChallenge =
         c.from.sid == this.st.user.sid || //live
diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue
index 953c976e..88b69065 100644
--- a/client/src/views/MyGames.vue
+++ b/client/src/views/MyGames.vue
@@ -47,6 +47,7 @@ export default {
   created: function() {
     GameStorage.getAll(true, localGames => {
       localGames.forEach(g => g.type = "live");
+      this.decorate(localGames);
       this.liveGames = localGames;
     });
     if (this.st.user.id > 0) {
@@ -64,6 +65,7 @@ export default {
               return !g["deletedBy" + mySide];
             });
             serverGames.forEach(g => g.type = "corr");
+            this.decorate(serverGames);
             this.corrGames = serverGames;
           }
         }
@@ -112,22 +114,47 @@ export default {
           .classList.add("somethingnew");
       }
     },
+    // Called at loading to augment games with priority + myTurn infos
+    decorate: function(games) {
+      games.forEach(g => {
+        g.priority = 0;
+        if (g.score == "*") {
+          g.priority++;
+          const myColor =
+            (g.type == "corr" && g.players[0].uid == this.st.user.id) ||
+            (g.type == "live" && g.players[0].sid == this.st.user.sid)
+              ? 'w'
+              : 'b';
+          const rem = g.movesCount % 2;
+          if ((rem == 0 && myColor == 'w') || (rem == 1 && myColor == 'b')) {
+            g.myTurn = true;
+            g.priority++;
+          }
+        }
+      });
+    },
     socketMessageListener: function(msg) {
       const data = JSON.parse(msg.data);
+      let gamesArrays = {
+        "corr": this.corrGames,
+        "live": this.liveGames
+      };
       switch (data.code) {
-        // NOTE: no need to increment movesCount: unused if turn is provided
         case "notifyturn":
         case "notifyscore": {
           const info = data.data;
-          let games =
-            !!parseInt(info.gid)
-              ? this.corrGames
-              : this.liveGames;
-          let g = games.find(g => g.id == info.gid);
+          const type = (!!parseInt(info.gid) ? "corr" : "live");
+          let game = gamesArrays[type].find(g => g.id == info.gid);
           // "notifything" --> "thing":
           const thing = data.code.substr(6);
-          this.$set(g, thing, info[thing]);
-          this.tryShowNewsIndicator(g.type);
+          game[thing] = info[thing];
+          if (thing == "score") game.priority = 0;
+          else {
+            game.priority = 3 - game.priority; //toggle turn
+            game.myTurn = !game.myTurn;
+          }
+          this.$forceUpdate();
+          this.tryShowNewsIndicator(type);
           break;
         }
         case "notifynewgame": {
@@ -136,22 +163,27 @@ export default {
           // if unlucky and newgame right after connect:
           const v = this.st.variants.find(v => v.id == gameInfo.vid);
           const vname = !!v ? v.name : "";
-          const type = gameInfo.cadence.indexOf('d') >= 0 ? "corr": "live";
-          const game = Object.assign(
+          const type = (gameInfo.cadence.indexOf('d') >= 0 ? "corr": "live");
+          let game = Object.assign(
             {
               vname: vname,
               type: type,
               score: "*",
-              turn: "w"
+              created: Date.now()
             },
             gameInfo
           );
-          // TODO: the new game isn't sorted. Maybe apply a different strategy:
-          // 1) Sort all at loading,
-          // 2) Insert in place when new games arrive,
-          // 3) Change position when score or turn change.
-          // And GameList just show list unsorted.
-          this[type + "Games"].unshift(game);
+          // Compute priority:
+          game.priority = 1; //at least: my running game
+          if (
+            (type == "corr" && game.players[0].uid == this.st.user.id) ||
+            (type == "live" && game.players[0].sid == this.st.user.sid)
+          ) {
+            game.priority++;
+            game.myTurn = true;
+          }
+          gamesArrays[type].push(game);
+          this.$forceUpdate();
           this.tryShowNewsIndicator(type);
           break;
         }
@@ -163,29 +195,14 @@ export default {
       this.conn.addEventListener("close", this.socketCloseListener);
     },
     showGame: function(game) {
-      // TODO: "isMyTurn" is duplicated (see GameList component). myColor also
-      const isMyTurn = (g) => {
-        if (g.score != "*") return false;
-        const myColor =
-          g.players[0].uid == this.st.user.id ||
-          g.players[0].sid == this.st.user.sid
-            ? "w"
-            : "b";
-        if (!!g.turn) return g.turn == myColor;
-        const rem = g.movesCount % 2;
-        return (
-          (rem == 0 && myColor == "w") ||
-          (rem == 1 && myColor == "b")
-        );
-      };
-      if (game.type == "live" || !isMyTurn(game)) {
+      if (game.type == "live" || !game.myTurn) {
         this.$router.push("/game/" + game.id);
         return;
       }
       // It's my turn in this game. Are there others?
       let nextIds = "";
-      let otherCorrGamesMyTurn = this.corrGames.filter(
-        g => g.id != game.id && isMyTurn(g));
+      let otherCorrGamesMyTurn = this.corrGames.filter(g =>
+        g.id != game.id && !!g.myTurn);
       if (otherCorrGamesMyTurn.length > 0) {
         nextIds += "/?next=[";
         otherCorrGamesMyTurn.forEach(g => { nextIds += g.id + ","; });
-- 
2.44.0