Hopefully Eightpieces is less buggish now
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 14 Mar 2020 20:35:33 +0000 (21:35 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 14 Mar 2020 20:35:33 +0000 (21:35 +0100)
15 files changed:
TODO
client/public/images/icons/SOURCE
client/public/images/icons/discord.svg [new file with mode: 0644]
client/public/images/icons/github.svg [new file with mode: 0644]
client/src/App.vue
client/src/base_rules.js
client/src/components/BaseGame.vue
client/src/components/Board.vue
client/src/components/ContactForm.vue
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/utils/playUndo.js
client/src/variants/Eightpieces.js
client/src/variants/Suction.js

diff --git a/TODO b/TODO
index a993b21..6916bdf 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,19 +1,4 @@
-Revise variants code by using more aggregate/disaggregate flags functions?
-
 # New variants
-8-pieces https://www.youtube.com/watch?v=XZ8K02Da7Ps&list=PLRyjH8DPuzTBiym6lA0r84P8N0HnTtZyN&index=6&t=0s
-https://www.chessvariants.com/rules/8-piece-chess "Eightpieces"
-
-"Ball" Chess: 9x9 board, ball on center square. 2 queens ?
-To take the ball when it's free you need to capture it.
-To take the ball when it's used, u need to take the piece.
-Goal: bring ball to final rank.
-Possibles passes : soit à une pièce, soit sur une case.
-  --> remplace un déplacement de pièce. Par exemple pion a2 passe à cavalier a4 = 1 coup.
-  --> selon le mode de déplacement standard (donc tout droit pour les pions)
-Pas de notion d'échec ou de mat (?)
-Si une pièce est mat elle donne le ballon (?)
-
 Landing pieces from empty board:
 https://www.chessvariants.com/diffsetup.dir/unachess.html
 
@@ -36,6 +21,16 @@ Goal is still checkmate.
 Take(a)n(d)make : if capture a piece, take its power for the last of the turn and make a move like it.
 If a pawn taken: direction of the capturer.
 
+"Ball" Chess: 9x9 board, ball on center square. 2 queens ?
+To take the ball when it's free you need to capture it.
+To take the ball when it's used, u need to take the piece.
+Goal: bring ball to final rank.
+Possibles passes : soit à une pièce, soit sur une case.
+  --> remplace un déplacement de pièce. Par exemple pion a2 passe à cavalier a4 = 1 coup.
+  --> selon le mode de déplacement standard (donc tout droit pour les pions)
+Pas de notion d'échec ou de mat (?)
+Si une pièce est mat elle donne le ballon (?)
+
 Maxima, Interweave, Roccoco
 
 Synchrone Chess: allow to anticipate en-passant capture as well :)
index eeb51d3..88c2502 100644 (file)
@@ -13,3 +13,5 @@ https://www.flaticon.com/free-icon/download_724933?term=download&page=1&position
 https://www.flaticon.com/free-icon/resize_512182?term=resize&page=1&position=49
 https://www.flaticon.com/free-icon/clear_565313?term=delete&page=1&position=33
 https://www.flaticon.com/free-icon/clear_1632708?term=delete&page=1&position=3
+https://iconscout.com/icon/discord-1
+https://www.onlinewebfonts.com/icon/154680
diff --git a/client/public/images/icons/discord.svg b/client/public/images/icons/discord.svg
new file mode 100644 (file)
index 0000000..bf91015
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50" version="1.1"><path d="M41.625 10.77c-3.98-3.204-10.277-3.747-10.547-3.766a.992.992 0 0 0-.988.586 6.63 6.63 0 0 0-.305.832c2.633.445 5.867 1.34 8.793 3.156a1 1 0 1 1-1.055 1.7C32.493 10.155 26.211 10 25 10c-1.21 0-7.496.156-12.523 3.277a1 1 0 0 1-1.055-1.7c2.926-1.811 6.16-2.71 8.793-3.151-.152-.496-.29-.809-.3-.836a.987.987 0 0 0-.993-.586c-.27.02-6.567.562-10.602 3.809C6.215 12.761 2 24.152 2 34c0 .176.047.344.133.496 2.906 5.11 10.84 6.445 12.648 6.504h.031a1 1 0 0 0 .81-.41l1.827-2.516c-4.933-1.273-7.453-3.437-7.597-3.566a1 1 0 1 1 1.324-1.5C11.234 33.063 15.875 37 25 37c9.14 0 13.781-3.953 13.828-3.992a1 1 0 0 1 1.41.094.996.996 0 0 1-.09 1.406c-.144.129-2.664 2.293-7.597 3.566l1.828 2.516a1 1 0 0 0 .809.41h.03c1.81-.059 9.743-1.395 12.65-6.504.085-.152.132-.32.132-.496 0-9.848-4.215-21.238-6.375-23.23zM18.5 30c-1.934 0-3.5-1.79-3.5-4s1.566-4 3.5-4 3.5 1.79 3.5 4-1.566 4-3.5 4zm13 0c-1.934 0-3.5-1.79-3.5-4s1.566-4 3.5-4 3.5 1.79 3.5 4-1.566 4-3.5 4z" id="surface1"/><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description about="https://iconscout.com/legal#licenses" dc:title="discord,filled" dc:description="discord,filled" dc:publisher="Iconscout" dc:date="2017-12-09" dc:format="image/svg+xml" dc:language="en"><dc:creator><rdf:Bag><rdf:li>Icons8</rdf:li></rdf:Bag></dc:creator></rdf:Description></rdf:RDF></metadata></svg>
\ No newline at end of file
diff --git a/client/public/images/icons/github.svg b/client/public/images/icons/github.svg
new file mode 100644 (file)
index 0000000..4c46ca3
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\r
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">\r
+<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>\r
+<g><path d="M500,10C229.4,10,10,229.4,10,500s219.4,490,490,490s490-219.4,490-490S770.6,10,500,10z M791,791c-37.8,37.8-81.8,67.5-130.8,88.2c-12.4,5.3-25.1,9.9-37.9,13.9v-73.4c0-38.6-13.2-67-39.7-85.2c16.6-1.6,31.8-3.8,45.7-6.7c13.9-2.9,28.6-7,44-12.4c15.5-5.4,29.3-11.9,41.6-19.4c12.3-7.5,24.1-17.2,35.4-29.2c11.3-12,20.8-25.5,28.5-40.7c7.7-15.2,13.7-33.3,18.2-54.5s6.7-44.6,6.7-70.1c0-49.5-16.1-91.6-48.3-126.3c14.7-38.3,13.1-79.9-4.8-124.9l-12-1.4c-8.3-1-23.2,2.5-44.7,10.5c-21.5,8-45.7,21.1-72.5,39.2c-38-10.5-77.4-15.8-118.2-15.8c-41.2,0-80.4,5.3-117.7,15.8c-16.9-11.5-32.9-21-48.1-28.5s-27.3-12.6-36.4-15.3s-17.5-4.4-25.4-5s-12.8-0.8-15.1-0.5c-2.2,0.3-3.8,0.6-4.8,1c-17.9,45.3-19.5,86.9-4.8,124.9c-32.2,34.8-48.3,76.9-48.3,126.3c0,25.5,2.2,48.9,6.7,70.1c4.5,21.2,10.5,39.4,18.2,54.5c7.7,15.2,17.1,28.7,28.5,40.7c11.3,12,23.1,21.7,35.4,29.2c12.3,7.5,26.2,14,41.6,19.4c15.5,5.4,30.1,9.6,44,12.4c13.9,2.9,29.1,5.1,45.7,6.7c-26.2,17.9-39.2,46.3-39.2,85.2v74.9c-14.4-4.3-28.7-9.4-42.7-15.3c-49-20.7-93-50.4-130.8-88.2c-37.8-37.8-67.5-81.8-88.2-130.8C99.4,609.5,88.5,555.6,88.5,500c0-55.6,10.9-109.5,32.3-160.2c20.7-49,50.4-93,88.2-130.8c37.8-37.8,81.8-67.5,130.8-88.2C390.6,99.4,444.5,88.5,500,88.5c55.6,0,109.5,10.9,160.2,32.3c49,20.7,93,50.4,130.8,88.2s67.5,81.8,88.2,130.8c21.4,50.7,32.3,104.6,32.3,160.2c0,55.6-10.9,109.5-32.3,160.2C858.5,709.2,828.8,753.2,791,791L791,791z"/></g>\r
+</svg>
\ No newline at end of file
index 8258dc3..d8e395f 100644 (file)
@@ -34,7 +34,9 @@
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       footer
         router-link.menuitem(to="/about") {{ st.tr["About"] }}
-        a.menuitem(href="https://discord.gg/a9ZFKBe") Discord
+        a.menuitem(href="https://github.com/yagu0/vchess")
+          span {{ st.tr["Code"] }}
+          img(src="/images/icons/github.svg")
         router-link.menuitem(to="/news") {{ st.tr["News"] }}
         p.clickable(onClick="window.doClick('modalContact')")
           | {{ st.tr["Contact"] }}
@@ -68,8 +70,8 @@ export default {
 <style lang="sass">
 html, *
   font-family: "Open Sans", Arial, sans-serif
-  --a-link-color: black
-  --a-visited-color: black
+  --a-link-color: darkred
+  --a-visited-color: darkred
 
 body
   padding: 0
@@ -236,13 +238,18 @@ footer
     color: #42b983 !important
     text-decoration: none
   & > .menuitem
-    display: inline-block
     margin: 0 12px
+    display: inline-flex;
+    align-self: center;
     &:link
       color: #2c3e50
     &:visited, &:hover
       color: #2c3e50
       text-decoration: none
+    & > img
+      height: 1.3em
+      display: inline-block
+      margin-left: 5px
   & > p
     display: inline-block
     margin: 0 12px
index 890601d..326da6e 100644 (file)
@@ -238,9 +238,11 @@ export const ChessRules = class ChessRules {
 
   // On which squares is color under check ? (for interface)
   getCheckSquares(color) {
-    return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
-      ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
-      : [];
+    return (
+      this.underCheck(color)
+        ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+        : []
+    );
   }
 
   /////////////
index c85d47e..471c139 100644 (file)
@@ -309,8 +309,6 @@ export default {
       const playSubmove = (smove) => {
         if (!navigate) smove.notation = this.vr.getNotation(smove);
         this.vr.play(smove);
-        // Is opponent in check?
-        this.incheck = this.vr.getCheckSquares(this.vr.turn);
         if (!navigate) {
           if (!this.inMultimove) {
             if (this.cursor < this.moves.length - 1)
@@ -356,6 +354,8 @@ export default {
           if (!smove.fen)
             // NOTE: only FEN of last sub-move is required (thus setting it here)
             smove.fen = this.vr.getFen();
+          // Is opponent in check?
+          this.incheck = this.vr.getCheckSquares(this.vr.turn);
           this.lastMove = smove;
           this.emitFenIfAnalyze();
           this.inMultimove = false;
@@ -442,7 +442,6 @@ export default {
           if (this.cursor < minCursor) return; //no more moves
           move = this.moves[this.cursor];
         }
-        // Caution; if multi-move, undo all submoves from last to first
         undoMove(move, this.vr);
         if (light) this.cursor--;
         else {
index 0d531a6..c2de176 100644 (file)
@@ -538,18 +538,18 @@ img.ghost
 // TODO: no predefined highlight colors, but layers. How?
 
 .light-square.lichess.highlight-light
-  background-color: #cdd26a !important
+  background-color: #cdd26a
 .dark-square.lichess.highlight-dark
-  background-color: #aaa23a !important
+  background-color: #aaa23a
 
 .light-square.chesscom.highlight-light
-  background-color: #f7f783 !important
+  background-color: #f7f783
 .dark-square.chesscom.highlight-dark
-  background-color: #bacb44 !important
+  background-color: #bacb44
 
 .light-square.chesstempo.highlight-light
-  background-color: #9f9fff !important
+  background-color: #9f9fff
 .dark-square.chesstempo.highlight-dark
-  background-color: #557fff !important
+  background-color: #557fff
 
 </style>
index e5d2732..b7aea94 100644 (file)
@@ -10,6 +10,9 @@ div
   )
     .card
       label.modal-close(for="modalContact")
+      a#discordLink(href="https://discord.gg/a9ZFKBe")
+        span {{ st.tr["Discord invitation"] }}
+        img(src="/images/icons/discord.svg")
       fieldset
         label(for="userEmail") {{ st.tr["Email"] }}
         input#userEmail(type="email" :value="st.user.email")
@@ -98,6 +101,16 @@ textarea#mailContent
   width: 100%
   min-height: 100px
 
+#discordLink
+  display: block
+  margin-top: 10px
+  text-align: center
+  font-size: 1.3em
+  & > img
+    height: 1.5em
+    display: inline-block
+    margin-left: 5px
+
 #dialog
   padding: 5px
   color: blue
index 3c0228c..a37a75c 100644 (file)
@@ -27,6 +27,7 @@ export const translations = {
   "Challenge declined": "Challenge declined",
   "Chat here": "Chat here",
   "Clear history": "Clear history",
+  Code: "Code",
   "Connection token sent. Check your emails!": "Connection token sent. Check your emails!",
   Contact: "Contact",
   "Correspondance challenges": "Correspondance challenges",
@@ -34,6 +35,7 @@ export const translations = {
   "Database error: stop private browsing, or update your browser": "Database error: stop private browsing, or update your browser",
   Delete: "Delete",
   Deterministic: "Deterministic",
+  "Discord invitation": "Discord invitation",
   Download: "Download",
   Draw: "Draw",
   "Draw offer only in your turn": "Draw offer only in your turn",
index f31fc20..e1048b7 100644 (file)
@@ -27,6 +27,7 @@ export const translations = {
   "Challenge declined": "Desafío rechazado",
   "Chat here": "Chat aquí",
   "Clear history": "Clara historia",
+  Code: "Código",
   "Connection token sent. Check your emails!": "Token de conexión enviado. ¡Revisa tus correos!",
   Contact: "Contacto",
   "Correspondance challenges": "Desafíos por correspondencia",
@@ -34,6 +35,7 @@ export const translations = {
   "Database error: stop private browsing, or update your browser": "Error de la base de datos: detener la navegación privada, o actualizar su navegador",
   Delete: "Borrar",
   Deterministic: "Determinista",
+  "Discord invitation": "Invitación Discord",
   Download: "Descargar",
   Draw: "Tablas",
   "Draw offer only in your turn": "Oferta de tablas solo en tu turno",
index 0a8d553..20b7b58 100644 (file)
@@ -27,6 +27,7 @@ export const translations = {
   "Challenge declined": "Défi refusé",
   "Chat here": "Chattez ici",
   "Clear history": "Effacer l'historique",
+  Code: "Code",
   "Connection token sent. Check your emails!": "Token de connection envoyé. Allez voir vos emails !",
   Contact: "Contact",
   "Correspondance challenges": "Défis par correspondance",
@@ -34,6 +35,7 @@ export const translations = {
   "Database error: stop private browsing, or update your browser": "Erreur de base de données : arrêtez la navigation privée, ou mettez à jour votre navigateur",
   Delete: "Supprimer",
   Deterministic: "Déterministe",
+  "Discord invitation": "Invitation Discord",
   Download: "Télécharger",
   Draw: "Nulle",
   "Draw offer only in your turn": "Proposition de nulle seulement sur votre temps",
index ab11bff..94e642c 100644 (file)
@@ -5,6 +5,7 @@ export function playMove(move, vr) {
 
 export function undoMove(move, vr) {
   if (!Array.isArray(move)) move = [move];
+  // If multi-move, undo all submoves from last to first
   for (let i = move.length - 1; i >= 0; i--)
     vr.undo(move[i]);
 }
index 88c3739..465fbea 100644 (file)
@@ -214,6 +214,13 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     );
   }
 
+  canTake([x1, y1], [x2, y2]) {
+    if (this.subTurn == 2)
+      // Only self captures on this subturn:
+      return this.getColor(x1, y1) == this.getColor(x2, y2);
+    return super.canTake([x1, y1], [x2, y2]);
+  }
+
   // Is piece on square (x,y) immobilized?
   isImmobilized([x, y]) {
     const color = this.getColor(x, y);
@@ -295,14 +302,6 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         return moves;
       }
     }
-    if (this.subTurn == 2) {
-      // Temporarily change pushed piece color.
-      // (Not using getPiece() because of lancers)
-      var oppCol = this.getColor(x, y);
-      var color = V.GetOppCol(oppCol);
-      var saveXYstate = this.board[x][y];
-      this.board[x][y] = color + this.board[x][y].charAt(1);
-    }
     let moves = [];
     switch (this.getPiece(x, y)) {
       case V.JAILER:
@@ -330,16 +329,12 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         }
         return true;
       });
-    }
-    else if (this.subTurn == 2) {
-      // Don't forget to re-add the sentry on the board:
-      // Also fix color of pushed piece afterward:
+    } else if (this.subTurn == 2) {
+      // Put back the sentinel on board:
+      const color = this.turn;
       moves.forEach(m => {
-        m.appear.unshift({x: x, y: y, p: V.SENTRY, c: color});
-        m.appear[1].c = oppCol;
-        m.vanish[0].c = oppCol;
+        m.appear.push({x: x, y: y, p: V.SENTRY, c: color});
       });
-      this.board[x][y] = saveXYstate;
     }
     return moves;
   }
@@ -348,10 +343,14 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     const color = this.getColor(x, y);
     let moves = [];
     const [sizeX, sizeY] = [V.size.x, V.size.y];
-    const shiftX = color == "w" ? -1 : 1;
+    let shiftX = color == "w" ? -1 : 1;
+    if (this.subTurn == 2) shiftX *= -1;
     const startRank = color == "w" ? sizeX - 2 : 1;
     const lastRank = color == "w" ? 0 : sizeX - 1;
 
+    // Pawns might be pushed on 1st rank and attempt to move again:
+    if (!V.OnBoard(x + shiftX, y)) return [];
+
     const finalPieces =
       // No promotions after pushes!
       x + shiftX == lastRank && this.subTurn == 1
@@ -421,7 +420,12 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
   getPotentialLancerMoves_aux([x, y], step, tr) {
     let moves = [];
     // Add all moves to vacant squares until opponent is met:
-    const oppCol = V.GetOppCol(this.getColor(x, y));
+    const color = this.getColor(x, y);
+    const oppCol =
+      this.subTurn == 1
+        ? V.GetOppCol(color)
+        // at subTurn == 2, consider own pieces as opponent
+        : color;
     let sq = [x + step[0], y + step[1]];
     while (V.OnBoard(sq[0], sq[1]) && this.getColor(sq[0], sq[1]) != oppCol) {
       if (this.board[sq[0]][sq[1]] == V.EMPTY)
@@ -451,7 +455,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         // do not change direction after moving, *except* if I keep the
         // same orientation in which I was pushed.
         const color = this.getColor(x, y);
-        const curDir = V.LANCER_DIRS[this.board[x][x].charAt(1)];
+        const curDir = V.LANCER_DIRS[this.board[x][y].charAt(1)];
         Object.values(V.LANCER_DIRS).forEach(step => {
           const dirCode = Object.keys(V.LANCER_DIRS).find(k => {
             return (
@@ -529,29 +533,13 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         m.vanish.pop();
       }
     });
-    // Can the pushed unit make any move? ...resulting in a non-self-check?
     const color = this.getColor(x, y);
     const fMoves = moves.filter(m => {
-      // Sentry push?
+      // Can the pushed unit make any move? ...resulting in a non-self-check?
       if (m.appear.length == 0) {
         let res = false;
         this.play(m);
-        let potentialMoves = this.getPotentialMovesFrom([m.end.x, m.end.y]);
-        // Add nudges (if any a priori possible)
-        for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
-          if (
-            V.OnBoard(m.end.x + step[0], m.end.y + step[1]) &&
-            this.board[m.end.x + step[0]][m.end.y + step[1]] == V.EMPTY
-          ) {
-            potentialMoves.push(
-              this.getBasicMove(
-                [m.end.x, m.end.y],
-                [m.end.x + step[0], m.end.y + step[1]]
-              )
-            );
-          }
-        }
-        let moves2 = this.filterValid(potentialMoves);
+        let moves2 = this.getPotentialMovesFrom([m.end.x, m.end.y]);
         for (let m2 of moves2) {
           this.play(m2);
           res = !this.underCheck(color);
@@ -573,6 +561,19 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     });
   }
 
+  getPotentialKingMoves(sq) {
+    const moves = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+    return (
+      this.subTurn == 1
+        ? moves.concat(this.getCastleMoves(sq))
+        : moves
+    );
+  }
+
   // Adapted: castle with jailer possible
   getCastleMoves([x, y]) {
     const c = this.getColor(x, y);
@@ -655,30 +656,40 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
   }
 
   filterValid(moves) {
+    if (moves.length == 0) return [];
+    const basicFilter = (m, c) => {
+      this.play(m);
+      const res = !this.underCheck(c);
+      this.undo(m);
+      return res;
+    };
     // Disable check tests for sentry pushes,
     // because in this case the move isn't finished
     let movesWithoutSentryPushes = [];
     let movesWithSentryPushes = [];
     moves.forEach(m => {
-      if (m.appear.length > 0) movesWithoutSentryPushes.push(m);
+      // Second condition below for special king "pass" moves
+      if (m.appear.length > 0 || m.vanish.length == 0)
+        movesWithoutSentryPushes.push(m);
       else movesWithSentryPushes.push(m);
     });
-
-    // TODO: if after move a sentry can take king in 2 times?!
-
-    const filteredMoves = super.filterValid(movesWithoutSentryPushes);
-    // If at least one full move made, everything is allowed:
-    if (this.movesCount >= 2)
-      return filteredMoves.concat(movesWithSentryPushes);
-    // Else, forbid checks and captures:
-    const oppCol = V.GetOppCol(this.turn);
-    return filteredMoves.filter(m => {
-      if (m.vanish.length == 2 && m.appear.length == 1) return false;
-      this.play(m);
-      const res = !this.underCheck(oppCol);
-      this.undo(m);
-      return res;
-    }).concat(movesWithSentryPushes);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const filteredMoves =
+      movesWithoutSentryPushes.filter(m => basicFilter(m, color));
+    // If at least one full move made, everything is allowed.
+    // Else: forbid checks and captures.
+    return (
+      this.movesCount >= 2
+        ? filteredMoves
+        : filteredMoves.filter(m => {
+          return (
+            m.vanish.length <= 1 ||
+            m.appear.length != 1 ||
+            basicFilter(m, oppCol)
+          );
+        })
+    ).concat(movesWithSentryPushes);
   }
 
   getAllValidMoves() {
@@ -689,10 +700,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
   }
 
   prePlay(move) {
-    if (move.appear.length == 0 && move.vanish.length == 1) {
+    if (move.appear.length == 0 && move.vanish.length == 1)
       // The sentry is about to push a piece: subTurn goes from 1 to 2
       this.sentryPos = { x: move.end.x, y: move.end.y };
-    } else if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) {
+    if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) {
       // A piece is pushed: forbid array of squares between start and end
       // of move, included (except if it's a pawn)
       let squares = [];
@@ -708,7 +719,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         ];
         for (
           let sq = {x: move.start.x, y: move.start.y};
-          sq.x != move.end.x && sq.y != move.end.y;
+          sq.x != move.end.x || sq.y != move.end.y;
           sq.x += step[0], sq.y += step[1]
         ) {
           squares.push({ x: sq.x, y: sq.y });
@@ -721,9 +732,9 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
   }
 
   play(move) {
-//    if (!this.states) this.states = [];
-//    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
-//    this.states.push(stateFen);
+    if (!this.states) this.states = [];
+    const stateFen = this.getFen();
+    this.states.push(stateFen);
 
     this.prePlay(move);
     move.flags = JSON.stringify(this.aggregateFlags());
@@ -742,10 +753,35 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
   }
 
   postPlay(move) {
-    if (move.vanish.length == 0)
-      // Special pass move of the king: nothing to update!
+    if (move.vanish.length == 0 || this.subTurn == 2)
+      // Special pass move of the king, or sentry pre-push: nothing to update
       return;
-    super.postPlay(move);
+    const c = move.vanish[0].c;
+    const piece = move.vanish[0].p;
+    const firstRank = c == "w" ? V.size.x - 1 : 0;
+
+    if (piece == V.KING) {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+      this.castleFlags[c] = [V.size.y, V.size.y];
+      return;
+    }
+    // Update castling flags if rooks are moved
+    const oppCol = V.GetOppCol(c);
+    const oppFirstRank = V.size.x - 1 - firstRank;
+    if (
+      move.start.x == firstRank && //our rook moves?
+      this.castleFlags[c].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+      this.castleFlags[c][flagIdx] = V.size.y;
+    } else if (
+      move.end.x == oppFirstRank && //we took opponent rook?
+      this.castleFlags[oppCol].includes(move.end.y)
+    ) {
+      const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+      this.castleFlags[oppCol][flagIdx] = V.size.y;
+    }
   }
 
   undo(move) {
@@ -761,9 +797,9 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     }
     this.postUndo(move);
 
-//    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
-//    if (stateFen != this.states[this.states.length-1]) debugger;
-//    this.states.pop();
+    const stateFen = this.getFen();
+    if (stateFen != this.states[this.states.length-1]) debugger;
+    this.states.pop();
   }
 
   postUndo(move) {
@@ -779,6 +815,46 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     );
   }
 
+  isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
+    for (let step of steps) {
+      let rx = x + step[0],
+          ry = y + step[1];
+      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+        rx += step[0];
+        ry += step[1];
+      }
+      if (
+        V.OnBoard(rx, ry) &&
+        this.getPiece(rx, ry) === piece &&
+        colors.includes(this.getColor(rx, ry)) &&
+        !this.isImmobilized([rx, ry])
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  isAttackedByPawn([x, y], colors) {
+    for (let c of colors) {
+      const pawnShift = c == "w" ? 1 : -1;
+      if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
+        for (let i of [-1, 1]) {
+          if (
+            y + i >= 0 &&
+            y + i < V.size.y &&
+            this.getPiece(x + pawnShift, y + i) == V.PAWN &&
+            this.getColor(x + pawnShift, y + i) == c &&
+            !this.isImmobilized([x + pawnShift, y + i])
+          ) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
   isAttackedByLancer([x, y], colors) {
     for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
       // If in this direction there are only enemy pieces and empty squares,
@@ -793,8 +869,12 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
           colors.includes(this.getColor(coord.x, coord.y))
         )
       ) {
-        if (this.getPiece(coord.x, coord.y) == V.LANCER)
+        if (
+          this.getPiece(coord.x, coord.y) == V.LANCER &&
+          !this.isImmobilized([coord.x, coord.y])
+        ) {
           lancerPos.push({x: coord.x, y: coord.y});
+        }
         coord.x += step[0];
         coord.y += step[1];
       }
@@ -871,7 +951,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       for (let j=0; j<V.size.y; j++) {
         if (
           this.getPiece(i,j) == V.SENTRY &&
-          colors.includes(this.getColor(i,j))
+          colors.includes(this.getColor(i,j)) &&
+          !this.isImmobilized([i, j])
         ) {
           for (let step of V.steps[V.BISHOP]) {
             let sq = [ i + step[0], j + step[1] ];
@@ -962,11 +1043,19 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     return (!choice.second ? choice : [choice, choice.second]);
   }
 
-  // TODO: if subTurn == 2, take some precautions, in particular pawn pushed on 1st rank.
-  // --> should indicate Sxb2,bxc1
   getNotation(move) {
     // Special case "king takes jailer" is a pass move
     if (move.appear.length == 0 && move.vanish.length == 0) return "pass";
+    if (this.subTurn == 2) {
+      // Do not consider appear[1] (sentry) for sentry pushes
+      const simpleMove = {
+        appear: [move.appear[0]],
+        vanish: move.vanish,
+        start: move.start,
+        end: move.end
+      };
+      return super.getNotation(simpleMove);
+    }
     return super.getNotation(move);
   }
 };
index 9ac5b44..a39bec0 100644 (file)
@@ -6,9 +6,6 @@ export const VariantRules = class SuctionRules extends ChessRules {
   }
 
   setOtherVariables(fen) {
-
-console.log(fen);
-
     super.setOtherVariables(fen);
     // Local stack of "captures"
     this.cmoves = [];