Add Yote
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 17 Jan 2021 23:02:44 +0000 (00:02 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 17 Jan 2021 23:02:44 +0000 (00:02 +0100)
16 files changed:
client/public/images/pieces/Konane/bp.svg [changed from file to symlink]
client/public/images/pieces/Konane/wp.svg [changed from file to symlink]
client/public/images/pieces/Yote/bp.svg [new file with mode: 0644]
client/public/images/pieces/Yote/wp.svg [new file with mode: 0644]
client/src/components/BaseGame.vue
client/src/components/Board.vue
client/src/translations/rules/Konane/en.pug
client/src/translations/rules/Konane/es.pug
client/src/translations/rules/Konane/fr.pug
client/src/translations/rules/Yote/en.pug
client/src/translations/rules/Yote/es.pug
client/src/translations/rules/Yote/fr.pug
client/src/utils/scoring.js
client/src/variants/Crazyhouse.js
client/src/variants/Konane.js
client/src/variants/Yote.js

deleted file mode 100644 (file)
index 75e907e0b0808bc7620a25de90565a1071d85a25..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
-<defs><radialGradient id="rg" cx=".47" cy=".49" r=".48">
-<stop offset=".7" stop-color="#FFF"/>
-<stop offset=".9" stop-color="#DDD"/>
-<stop offset="1" stop-color="#777"/>
-</radialGradient></defs>
-<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
-</svg>
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..dc5717d38b398b0a2ea1a4f60ab0752f77cbd1ea
--- /dev/null
@@ -0,0 +1 @@
+../Yote/wp.svg
\ No newline at end of file
deleted file mode 100644 (file)
index 357079eb5a4a8d0c8ef366c2ceb2ec2e07f85ac5..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
-<defs><radialGradient id="rg" cx=".3" cy=".3" r=".8">
-<stop offset="0" stop-color="#777"/>
-<stop offset=".3" stop-color="#222"/>
-<stop offset="1" stop-color="#000"/>
-</radialGradient></defs>
-<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
-</svg>
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..0d6535fd4fe8a68a1ae3380ae7940ba93a72a4ac
--- /dev/null
@@ -0,0 +1 @@
+../Yote/bp.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Yote/bp.svg b/client/public/images/pieces/Yote/bp.svg
new file mode 100644 (file)
index 0000000..75e907e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".47" cy=".49" r=".48">
+<stop offset=".7" stop-color="#FFF"/>
+<stop offset=".9" stop-color="#DDD"/>
+<stop offset="1" stop-color="#777"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
+</svg>
diff --git a/client/public/images/pieces/Yote/wp.svg b/client/public/images/pieces/Yote/wp.svg
new file mode 100644 (file)
index 0000000..357079e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".3" cy=".3" r=".8">
+<stop offset="0" stop-color="#777"/>
+<stop offset=".3" stop-color="#222"/>
+<stop offset="1" stop-color="#000"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
+</svg>
index f101702..7627f2d 100644 (file)
@@ -569,7 +569,7 @@ export default {
           }
         }
         if (score != "*" && ["analyze", "versus"].includes(this.mode)) {
           }
         }
         if (score != "*" && ["analyze", "versus"].includes(this.mode)) {
-          const message = getScoreMessage(score);
+          const message = getScoreMessage(score, V.ReverseColors);
           // Show score on screen
           this.showEndgameMsg(score + " . " + this.st.tr[message]);
         }
           // Show score on screen
           this.showEndgameMsg(score + " . " + this.st.tr[message]);
         }
index cc3f6fd..b176bd7 100644 (file)
@@ -663,27 +663,29 @@ export default {
             this.$emit("click-square", sq);
             if (withPiece && !this.vr.onlyClick(sq)) {
               this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
             this.$emit("click-square", sq);
             if (withPiece && !this.vr.onlyClick(sq)) {
               this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
-              // For potential drag'n drop, remember start coordinates
-              // (to center the piece on mouse cursor)
-              let parent = e.target.parentNode; //surrounding square
-              const rect = parent.getBoundingClientRect();
-              this.start = {
-                x: rect.x + rect.width / 2,
-                y: rect.y + rect.width / 2,
-                id: parent.id
-              };
-              // Add the moving piece to the board, just after current image
-              this.selectedPiece = e.target.cloneNode();
-              Object.assign(
-                this.selectedPiece.style,
-                {
-                  position: "absolute",
-                  top: 0,
-                  display: "inline-block",
-                  zIndex: 3000
-                }
-              );
-              parent.insertBefore(this.selectedPiece, e.target.nextSibling);
+              if (this.possibleMoves.length > 0) {
+                // For potential drag'n drop, remember start coordinates
+                // (to center the piece on mouse cursor)
+                let parent = e.target.parentNode; //surrounding square
+                const rect = parent.getBoundingClientRect();
+                this.start = {
+                  x: rect.x + rect.width / 2,
+                  y: rect.y + rect.width / 2,
+                  id: parent.id
+                };
+                // Add the moving piece to the board, just after current image
+                this.selectedPiece = e.target.cloneNode();
+                Object.assign(
+                  this.selectedPiece.style,
+                  {
+                    position: "absolute",
+                    top: 0,
+                    display: "inline-block",
+                    zIndex: 3000
+                  }
+                );
+                parent.insertBefore(this.selectedPiece, e.target.nextSibling);
+              }
             }
           }
         }
             }
           }
         }
index 9a15f67..930f8c7 100644 (file)
@@ -2,6 +2,9 @@ p.boxed
   | Capture orthogonally at each turn, "as in Draughts".
   | If you cannot capture, you lose.
 
   | Capture orthogonally at each turn, "as in Draughts".
   | If you cannot capture, you lose.
 
+p
+  a(href="https://en.wikipedia.org/wiki/Konane") Hawaiian Checkers
+
 p.
   To initiate the game, the first player (black) must remove one of his stones
   either in the upper left or lower right corner, or in the center,
 p.
   To initiate the game, the first player (black) must remove one of his stones
   either in the upper left or lower right corner, or in the center,
index 7cb7f73..ca17726 100644 (file)
@@ -2,6 +2,9 @@ p.boxed
   | Captura ortogonalmente en cada turno, "como a las Damas".
   | Si no es posible la captura, ha perdido.
 
   | Captura ortogonalmente en cada turno, "como a las Damas".
   | Si no es posible la captura, ha perdido.
 
+p
+  a(href="https://en.wikipedia.org/wiki/Konane") Damas hawaianas
+
 p.
   Para iniciar el juego, el primer jugador (negras) debe eliminar uno de
   sus piedras en la esquina superior izquierda o inferior derecha,
 p.
   Para iniciar el juego, el primer jugador (negras) debe eliminar uno de
   sus piedras en la esquina superior izquierda o inferior derecha,
index 2329737..7cdc286 100644 (file)
@@ -2,6 +2,9 @@ p.boxed
   | Capturez orthogonalement à chaque tour, "comme aux Dames".
   | Si aucune capture n'est possible, vous avez perdu.
 
   | Capturez orthogonalement à chaque tour, "comme aux Dames".
   | Si aucune capture n'est possible, vous avez perdu.
 
+p
+  a(href="https://en.wikipedia.org/wiki/Konane") Dames hawaïennes
+
 p.
   Pour initialiser la partie, le premier joueur (noirs) doit retirer une de
   ses pierres soit dans le coin supérieur gauche ou inférieur droit,
 p.
   Pour initialiser la partie, le premier joueur (noirs) doit retirer une de
   ses pierres soit dans le coin supérieur gauche ou inférieur droit,
index 3a33838..9689de8 100644 (file)
@@ -1,2 +1,51 @@
 p.boxed
 p.boxed
-  | TODO
+  | Move a stone or capture by jumping orthogonally.
+
+p Traditional game in some Western Africa countries.
+
+p
+  | The rules described here correspond to 
+  a(href="https://www.youtube.com/watch?v=yuqHy8GOZ_Q") this video
+  | , with the alternative voctory condition.
+  | See also 
+  a(href="http://www.cyningstan.com/game/342/yot") this introduction
+  | .
+
+p.
+  It seems that the color of pieces starting the game isn't really determined,
+  so I decided to start with black stones, as in Go game or Othello.
+  The following is mostly copy-paste from Wikipedia:
+
+p.
+  The game is played on a 5×6 board, which is empty at the beginning of the
+  game. Each player has twelve pieces in hand. Players alternate turns,
+  with Black moving first. In a move, a player may either:
+ul
+  li Place a piece in hand on any empty cell of the board.
+  li.
+    Move one of their pieces already on the board orthogonally to an empty
+    adjacent cell.
+  li.
+    Capture an opponent's piece if it is orthogonally adjacent to a player's
+    piece, by jumping to the empty cell immediately beyond it.
+    The captured piece is removed from the board, and the capturing player
+    removes another of the opponent's pieces of his choosing from the board.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:6/1p2P1/3p2/1p1P2/5P d2,d4:
+  .diagram.diag22
+    | fen:6/3PP1/6/1p4/5P:
+  figcaption Before and after the marked capture + removal of b4.
+
+p.
+  The game ends when a player has no stones on the board (after move 1),
+  or if he cannot play a move. He loses in both cases.
+  If only a low number of pieces remain on the board, captures might be
+  impossible and after a sequences of attempts players may agree on a draw.
+
+h4 Notes
+
+p Two consecutive normal moves cannot cancel each other.
+
+p To remove a stone after a capture, simply click on it.
index 3a33838..20170ba 100644 (file)
@@ -1,2 +1,55 @@
 p.boxed
 p.boxed
-  | TODO
+  | Mueve una piedra o captura mediante un salto ortogonal.
+
+p Juego tradicional en algunos países de África Occidental.
+
+p
+  | Las reglas descritas aquí corresponden a 
+  a(href="https://www.youtube.com/watch?v=yuqHy8GOZ_Q") este video
+  | , con la condición de victoria alternativa.
+  | Ver también 
+  a(href="http://www.cyningstan.com/game/342/yot") esta introducción
+  | .
+
+p.
+  Parece que el color de las piezas que comienzan el juego no es realmente
+  decidido, así que decidí comenzar con las piedras negras, como en
+  juego de Go u Othello. Lo siguiente se ha tomado principalmente de Wikipedia:
+
+p.
+  El juego tiene lugar en un tablero inicialmente vacío de 5x6.
+  Cada jugador tiene doce piezas en la mano. Los jugadores se alternan vueltas,
+  comenzando por las negras. Durante un movimiento, podemos:
+ul
+  li Coloque una pieza de la reserva en un espacio vacío.
+  li.
+    Mueve una piedra que ya esté en el tablero,
+    ortogonalmente a un cuadrado vacío.
+  li.
+    Capturar una pieza del oponente si está ortogonalmente adyacente a esa
+    del jugador, saltando al cuadrado vacío inmediatamente después.
+    La piedra capturada se elimina del juego y el jugador también elimina
+    otra pieza opuesta de su elección en el tablero.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:6/1p2P1/3p2/1p1P2/5P d2,d4:
+  .diagram.diag22
+    | fen:6/3PP1/6/1p4/5P:
+  figcaption Antes y después de la captura + eliminación marcada en b4.
+
+p.
+  El juego termina cuando un jugador no tiene más piedras en el tablero
+  (después del primer movimiento), o si no puede ejecutar una jugada.
+  Pierde en ambos casos.
+  Si solo quedan en juego un pequeño número de piezas,
+  las capturas pueden no ser posibles, y en este caso los jugadores pueden
+  después de algunos intentos, decida empatar.
+
+h4 Notas
+
+p Dos jugadas normales consecutivos no pueden anularse entre sí.
+
+p.
+  Para eliminar una piedra después de la captura,
+  simplemente haga clic en ella.
index 3a33838..da87c56 100644 (file)
@@ -1,2 +1,52 @@
 p.boxed
 p.boxed
-  | TODO
+  | Déplacez une pierre ou capturez via un saut orthogonal.
+
+p Jeu traditionnel dans certains pays d'Afrique de l'Ouest.
+
+p
+  | Les règles décrites ici correspondent à 
+  a(href="https://www.youtube.com/watch?v=yuqHy8GOZ_Q") cette vidéo
+  | , avec la condition de victoire alternative.
+  | Voir aussi 
+  a(href="http://www.cyningstan.com/game/342/yot") cette introduction
+  | .
+
+p.
+  Il semble que la couleur des pièces démarrant la partie ne soit pas vraiment
+  déterminée, donc j'ai décidé de commencer avec les pierres noires, comme au
+  jeu de Go ou à Othello. La suite est essentiellement reprise de Wikipedia :
+
+p.
+  Le jeu se déroule sur un plateau de taille 5x6 initialement vide.
+  Chaque joueur dispose de douze pièces en main. Les joueurs alternent les
+  tours, en commençant par les noirs. Lors d'un coup, on peut :
+ul
+  li Placer une pièce de la réserve sur une case vide.
+  li.
+    Déplacer une pierre déjà sur le plateau, orthogonalement vers une case vide.
+  li.
+    Capturer une pièce adverse si elle est orthogonalement adjacente à celle
+    du joueur, en sautant vers la case vide située immédiatement après.
+    La pierre capturée est retirée du jeu, et le joueur enlève aussi une autre
+    pièce adverse de son choix sur le plateau.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:6/1p2P1/3p2/1p1P2/5P d2,d4:
+  .diagram.diag22
+    | fen:6/3PP1/6/1p4/5P:
+  figcaption Avant et après la capture marquée + suppression en b4.
+
+p.
+  La partie se termine quand un joueur n'a plus de pierres sur le plateau
+  (après le premier coup), ou s'il est dans l'incapacité de jouer un coup.
+  Il perd dans les deux cas.
+  Si seulement un petit nombre de pièces restent en jeu, de nouvelles
+  captures pourraient être impossibles, et dans ce cas les joueurs peuvent
+  après quelques essais décider d'un match nul.
+
+h4 Notes
+
+p Deux coups normaux consécutifs ne peuvent s'annuler l'un l'autre.
+
+p Pour supprimer une pierre après capture, cliquez simplement dessus.
index c659b7d..3d54128 100644 (file)
@@ -1,12 +1,12 @@
 // Default score message if none provided
 // Default score message if none provided
-export function getScoreMessage(score) {
+export function getScoreMessage(score, reverseColors) {
   let eogMessage = "Undefined"; //not translated: unused
   switch (score) {
     case "1-0":
   let eogMessage = "Undefined"; //not translated: unused
   switch (score) {
     case "1-0":
-      eogMessage = "White win";
+      eogMessage = (!reverseColors ? "White win" : "Black win");
       break;
     case "0-1":
       break;
     case "0-1":
-      eogMessage = "Black win";
+      eogMessage = (!reverseColors ? "Black win" : "White win");
       break;
     case "1/2":
       eogMessage = "Draw";
       break;
     case "1/2":
       eogMessage = "Draw";
index db9cb57..64ce48b 100644 (file)
@@ -188,7 +188,7 @@ export class CrazyhouseRules extends ChessRules {
     if (!super.atLeastOneMove()) {
       // Search one reserve move
       for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
     if (!super.atLeastOneMove()) {
       // Search one reserve move
       for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
-        let moves = this.filterValid(
+        const moves = this.filterValid(
           this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
         );
         if (moves.length > 0) return true;
           this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
         );
         if (moves.length > 0) return true;
index 8f285ef..1ff77ee 100644 (file)
@@ -1,7 +1,5 @@
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
-// TODO: Maybe more flexible end of game messages (V.ColorsReversed ?!)
-
 export class KonaneRules extends ChessRules {
 
   static get HasFlags() {
 export class KonaneRules extends ChessRules {
 
   static get HasFlags() {
@@ -12,10 +10,18 @@ export class KonaneRules extends ChessRules {
     return false;
   }
 
     return false;
   }
 
+  static get ReverseColors() {
+    return true;
+  }
+
   static get PIECES() {
     return V.PAWN;
   }
 
   static get PIECES() {
     return V.PAWN;
   }
 
+  getPiece() {
+    return V.PAWN;
+  }
+
   getPpath(b) {
     return "Konane/" + b;
   }
   getPpath(b) {
     return "Konane/" + b;
   }
index 0d81759..dc1ef68 100644 (file)
@@ -1,7 +1,398 @@
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class YoteRules extends ChessRules {
 
 
 export class YoteRules extends ChessRules {
 
-  // TODO
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get Monochrome() {
+    return true;
+  }
+
+  static get Notoodark() {
+    return true;
+  }
+
+  static get ReverseColors() {
+    return true;
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 3) Check reserves
+    if (
+      !fenParsed.reserve ||
+      !fenParsed.reserve.match(/^([0-9]{1,2},){2,2}$/)
+    ) {
+      return false;
+    }
+    // 4) Check lastMove
+    if (!fenParsed.lastMove) return false;
+    const lmParts = fenParsed.lastMove.split(",");
+    for (lp of lmParts) {
+      if (lp != "-" && !lp.match(/^([a-f][1-5]){2,2}$/)) return false;
+    }
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      {
+        reserve: fenParts[3],
+        lastMove: fenParts[4]
+      }
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    return "6/6/6/6/6 w 0 12,12 -,-";
+  }
+
+  getFen() {
+    return (
+      super.getFen() + " " +
+      this.getReserveFen() + " " +
+      this.getLastmoveFen()
+    );
+  }
+
+  getFenForRepeat() {
+    return super.getFenForRepeat() + "_" + this.getReserveFen();
+  }
+
+  getReserveFen() {
+    return (
+      (!this.reserve["w"] ? 0 : this.reserve["w"][V.PAWN]) + "," +
+      (!this.reserve["b"] ? 0 : this.reserve["b"][V.PAWN])
+    );
+  }
+
+  getLastmoveFen() {
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return (
+      (
+        !lm['w']
+          ? '-'
+          : V.CoordsToSquare(lm['w'].start) + V.CoordsToSquare(lm['w'].end)
+      )
+      + "," +
+      (
+        !lm['b']
+          ? '-'
+          : V.CoordsToSquare(lm['b'].start) + V.CoordsToSquare(lm['b'].end)
+      )
+    );
+  }
+
+  setOtherVariables(fen) {
+    const fenParsed = V.ParseFen(fen);
+    const reserve = fenParsed.reserve.split(",").map(x => parseInt(x, 10));
+    this.reserve = {
+      w: { [V.PAWN]: reserve[0] },
+      b: { [V.PAWN]: reserve[1] }
+    };
+    // And last moves (to avoid undoing your last move)
+    const lmParts = fenParsed.lastMove.split(",");
+    this.lastMove = [{ w: null, b: null }];
+    ['w', 'b'].forEach((c, i) => {
+      if (lmParts[i] != '-') {
+        this.lastMove[0][c] = {
+          start: V.SquareToCoords(lmParts[i].substr(0, 2)),
+          end: V.SquareToCoords(lmParts[i].substr(2))
+        };
+      }
+    });
+    // Local stack to know if (current) last move captured something
+    this.captures = [false];
+  }
+
+  static get size() {
+    return { x: 5, y: 6 };
+  }
+
+  static get PIECES() {
+    return [V.PAWN];
+  }
+
+  getColor(i, j) {
+    if (i >= V.size.x) return i == V.size.x ? "w" : "b";
+    return this.board[i][j].charAt(0);
+  }
+
+  getPiece() {
+    return V.PAWN;
+  }
+
+  getPpath(b) {
+    return "Yote/" + b;
+  }
+
+  getReservePpath(index, color) {
+    return "Yote/" + color + V.PAWN;
+  }
+
+  static get RESERVE_PIECES() {
+    return [V.PAWN];
+  }
+
+  canIplay(side, [x, y]) {
+    if (this.turn != side) return false;
+    const L = this.captures.length;
+    if (!this.captures[L-1]) return this.getColor(x, y) == side;
+    return (x < V.size.x && this.getColor(x, y) != side);
+  }
+
+  hoverHighlight(x, y) {
+    const L = this.captures.length;
+    if (!this.captures[L-1]) return false;
+    const oppCol = V.GetOppCol(this.turn);
+    return (this.board[x][y] != V.EMPTY && this.getColor(x, y) == oppCol);
+  }
+
+  // TODO: onlyClick() doesn't fulfill exactly its role.
+  // Seems that there is some lag... TOFIX
+  onlyClick([x, y]) {
+    const L = this.captures.length;
+    return (this.captures[L-1] && this.getColor(x, y) != this.turn);
+  }
+
+  // PATCH related to above TO-DO:
+  getPossibleMovesFrom([x, y]) {
+    if (x < V.size.x && this.board[x][y] == V.EMPTY) return [];
+    return super.getPossibleMovesFrom([x, y]);
+  }
+
+  doClick([x, y]) {
+    const L = this.captures.length;
+    if (!this.captures[L-1]) return null;
+    const oppCol = V.GetOppCol(this.turn);
+    if (this.board[x][y] == V.EMPTY || this.getColor(x, y) != oppCol)
+      return null;
+    return new Move({
+      appear: [],
+      vanish: [ new PiPo({ x: x, y: y, c: oppCol, p: V.PAWN }) ],
+      end: { x: x, y: y }
+    });
+  }
+
+  getReserveMoves(x) {
+    const color = this.turn;
+    if (this.reserve[color][V.PAWN] == 0) return [];
+    let moves = [];
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] == V.EMPTY) {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: V.PAWN
+              })
+            ],
+            vanish: [],
+            start: { x: x, y: 0 }, //a bit artificial...
+            end: { x: i, y: j }
+          });
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const L = this.captures.length;
+    if (this.captures[L-1]) {
+      if (x >= V.size.x) return [];
+      const mv = this.doClick([x, y]);
+      return (!!mv ? [mv] : []);
+    }
+    if (x >= V.size.x)
+      return this.getReserveMoves([x, y]);
+    return this.getPotentialPawnMoves([x, y]);
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    let moves = [];
+    const color = this.turn;
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    let forbiddenStep = null;
+    if (!!lm[color]) {
+      forbiddenStep = [
+        lm[color].start.x - lm[color].end.x,
+        lm[color].start.y - lm[color].end.y
+      ];
+    }
+    const oppCol = V.GetOppCol(color);
+    for (let s of V.steps[V.ROOK]) {
+      if (
+        !!forbiddenStep &&
+        s[0] == forbiddenStep[0] && s[1] == forbiddenStep[1]
+      ) {
+        continue;
+      }
+      const [i1, j1] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i1, j1)) {
+        if (this.board[i1][j1] == V.EMPTY)
+          moves.push(super.getBasicMove([x, y], [i1, j1]));
+        else if (this.getColor(i1, j1) == oppCol) {
+          const [i2, j2] = [i1 + s[0], j1 + s[1]];
+          if (V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY) {
+            let mv = new Move({
+              appear: [
+                new PiPo({ x: i2, y: j2, c: color, p: V.PAWN })
+              ],
+              vanish: [
+                new PiPo({ x: x, y: y, c: color, p: V.PAWN }),
+                new PiPo({ x: i1, y: j1, c: oppCol, p: V.PAWN })
+              ]
+            });
+            moves.push(mv);
+          }
+        }
+      }
+    }
+    return moves;
+  }
+
+  getAllPotentialMoves() {
+    let moves = super.getAllPotentialMoves();
+    const color = this.turn;
+    moves = moves.concat(
+      this.getReserveMoves(V.size.x + (color == "w" ? 0 : 1)));
+    return moves;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  atLeastOneMove() {
+    if (!super.atLeastOneMove()) {
+      // Search one reserve move
+      const moves =
+        this.getReserveMoves(V.size.x + (this.turn == "w" ? 0 : 1));
+      if (moves.length > 0) return true;
+      return false;
+    }
+    return true;
+  }
+
+  play(move) {
+    const color = this.turn;
+    move.turn = color; //for undo
+    const L = this.lastMove.length;
+    if (color == 'w')
+      this.lastMove.push({ w: null, b: this.lastMove[L-1]['b'] });
+    if (move.appear.length == move.vanish.length) { //== 1
+      // Normal move (non-capturing, non-dropping, non-removal)
+      let lm = this.lastMove[L - (color == 'w' ? 0 : 1)];
+      if (!lm[color]) lm[color] = {};
+      lm[color].start = move.start;
+      lm[color].end = move.end;
+    }
+    const oppCol = V.GetOppCol(color);
+    V.PlayOnBoard(this.board, move);
+    const captureNotEnding = (
+      move.vanish.length == 2 &&
+      this.board.some(b => b.some(cell => cell != "" && cell[0] == oppCol))
+    );
+    this.captures.push(captureNotEnding);
+    // Change turn unless I just captured something,
+    // and an opponent stone can be removed from board.
+    if (!captureNotEnding) {
+      this.turn = oppCol;
+      this.movesCount++;
+    }
+    this.postPlay(move);
+  }
+
+  undo(move) {
+    V.UndoOnBoard(this.board, move);
+    if (this.turn == 'b') this.lastMove.pop();
+    else this.lastMove['b'] = null;
+    this.captures.pop();
+    if (move.turn != this.turn) {
+      this.turn = move.turn;
+      this.movesCount--;
+    }
+    this.postUndo(move);
+  }
+
+  postPlay(move) {
+    if (move.vanish.length == 0) {
+      const color = move.appear[0].c;
+      this.reserve[color][V.PAWN]--;
+      if (this.reserve[color][V.PAWN] == 0) delete this.reserve[color];
+    }
+  }
+
+  postUndo(move) {
+    if (move.vanish.length == 0) {
+      const color = move.appear[0].c;
+      if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 0 };
+      this.reserve[color][V.PAWN]++;
+    }
+  }
+
+  getCurrentScore() {
+    if (this.movesCount <= 2) return "*";
+    const color = this.turn;
+    // If no stones on board, or no move available, I lose
+    if (
+      this.board.every(b => {
+        return b.every(cell => {
+          return (cell == "" || cell[0] != color);
+        });
+      })
+      ||
+      !this.atLeastOneMove()
+    ) {
+      return (color == 'w' ? "0-1" : "1-0");
+    }
+    return "*";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 4;
+  }
+
+  evalPosition() {
+    let evaluation = super.evalPosition();
+    // Add reserves:
+    evaluation += this.reserve["w"][V.PAWN];
+    evaluation -= this.reserve["b"][V.PAWN];
+    return evaluation;
+  }
+
+  getNotation(move) {
+    if (move.vanish.length == 0)
+      // Placement:
+      return "@" + V.CoordsToSquare(move.end);
+    if (move.appear.length == 0)
+      // Removal after capture:
+      return V.CoordsToSquare(move.start) + "X";
+    return (
+      V.CoordsToSquare(move.start) +
+      (move.vanish.length == 2 ? "x" : "") +
+      V.CoordsToSquare(move.end)
+    );
+  }
 
 };
 
 };