--- /dev/null
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class AvalancheRules extends ChessRules {
+
+  static get PawnSpecs() {
+    return (
+      Object.assign(
+        { promotions: [V.PAWN] },
+        ChessRules.PawnSpecs
+      )
+    );
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParts = fen.split(" ");
+    if (fenParts.length != 5) return false;
+    if (!fenParts[4].match(/^[0-8]$/)) return false;
+    return true;
+  }
+
+  canIplay(side, [x, y]) {
+    if (this.subTurn == 0) return (x >= V.size.x);
+    const c = this.getColor(x, y);
+    return (
+      (this.subTurn == 1 && c == side) ||
+      (this.subTurn == 2 && c != side && this.getPiece(x, y) == V.PAWN)
+    );
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { promoteFile: fenParts[4] }
+    );
+  }
+
+  getPromoteFen() {
+    const L = this.promoteFile.length;
+    return (this.promoteFile[L-1] + 1);
+  }
+
+  getFen() {
+    return super.getFen() + " " + this.getPromoteFen();
+  }
+
+  getFenForRepeat() {
+    return super.getFenForRepeat() + "_" + this.getPromoteFen();
+  }
+
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).slice(0, -1) + "0";
+  }
+
+  getPiece(i, j) {
+    if (i >= V.size.x) return V.RESERVE_PIECES[j];
+    return this.board[i][j].charAt(1);
+  }
+
+  static get RESERVE_PIECES() {
+    // Promotion pieces
+    return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const fenPromoteFile = V.ParseFen(fen).promoteFile;
+    this.promoteFile = [parseInt(fenPromoteFile, 10) - 1];
+    this.reserve = { 'w': null, 'b': null };
+    if (this.promoteFile[0] >= 0) {
+      this.reserve = {
+        [this.turn]: {
+          [V.ROOK]: 1,
+          [V.KNIGHT]: 1,
+          [V.BISHOP]: 1,
+          [V.QUEEN]: 1
+        }
+      };
+      this.subTurn = 0;
+    }
+    else this.subTurn = 1;
+  }
+
+  getReservePpath(index, color) {
+    return color + V.RESERVE_PIECES[index];
+  }
+
+  getReserveMove(y) {
+    // Send a new piece piece to our first rank
+    const color = this.turn;
+    const L = this.promoteFile.length;
+    const [rank, file] = [color == 'w' ? 0 : 7, this.promoteFile[L-1]];
+    return new Move({
+      appear: [
+        new PiPo({ x: rank, y: file, c: color, p: V.RESERVE_PIECES[y] })
+      ],
+      vanish: [
+        new PiPo({ x: rank, y: file, c: color, p: V.PAWN })
+      ],
+      start: { x: 8, y: y },
+      end: { x: rank, y: file }
+    });
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.subTurn == 0)
+      // Reserves, outside of board: x == sizeX(+1)
+      return (x >= 8 ? [this.getReserveMove(y)] : []);
+    if (this.subTurn == 1)
+      // Usual case:
+      return super.getPotentialMovesFrom([x, y]);
+    // subTurn == 2: only allowed to push an opponent's pawn (if possible)
+    const oppPawnShift = (this.turn == 'w' ? 1 : -1);
+    if (
+      V.OnBoard(x + oppPawnShift, y) &&
+      this.board[x + oppPawnShift][y] == V.EMPTY
+    ) {
+      return [this.getBasicMove([x, y], [x + oppPawnShift, y])];
+    }
+    return [];
+  }
+
+  getAllValidMoves() {
+    if (this.subTurn == 0) {
+      let moves = [];
+      for (let y = 0; y < V.RESERVE_PIECES.length; y++)
+        moves.push(this.getReserveMove(y));
+      return moves;
+    }
+    if (this.subTurn == 1)
+      return this.filterValid(super.getAllPotentialMoves());
+    // subTurn == 2: move opponent's pawn only
+    let moves = [];
+    const oppCol = V.GetOppCol(this.turn);
+    for (let i = 0; i < 8; i++) {
+      for (let j = 0; j < 8; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i, j) == oppCol &&
+          this.getPiece(i, j) == V.PAWN
+        ) {
+          Array.prototype.push.apply(
+            moves, this.getPotentialMovesFrom([i, j]));
+        }
+      }
+    }
+    return moves;
+  }
+
+  filterValid(moves) {
+    if (this.subTurn != 1) return moves; //self-checks by pawns are allowed
+    return super.filterValid(moves);
+  }
+
+  atLeastOneMove() {
+    if (this.subTurn == 0) return true; //TODO: never called in this situation
+    if (this.subTurn == 1) {
+      // Cannot use super method: infinite recursive calls
+      const color = this.turn;
+      for (let i = 0; i < 8; i++) {
+        for (let j = 0; j < 8; j++) {
+          if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
+            const moves = this.getPotentialMovesFrom([i, j]);
+            if (moves.length > 0) {
+              for (let k = 0; k < moves.length; k++) {
+                const piece = moves[k].vanish[0].p;
+                if (piece == V.KING) {
+                  this.kingPos[color] =
+                    [moves[k].appear[0].x, moves[k].appear[0].y];
+                }
+                V.PlayOnBoard(this.board, moves[k]);
+                const res = !this.underCheck(color);
+                V.UndoOnBoard(this.board, moves[k]);
+                if (piece == V.KING) {
+                  this.kingPos[color] =
+                    [moves[k].vanish[0].x, moves[k].vanish[0].y];
+                }
+                if (res) return true;
+              }
+            }
+          }
+        }
+      }
+      return false;
+    }
+    // subTurn == 2: need to find an enemy pawn which can advance
+    const oppCol = V.GetOppCol(this.turn);
+    const oppPawnShift = (oppCol == 'w' ? -1 : 1);
+    for (let i = 0; i < 8; i++) {
+      for (let j = 0; j < 8; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i, j) == oppCol &&
+          this.getPiece(i, j) == V.PAWN &&
+          V.OnBoard(i + oppPawnShift, j) &&
+          this.board[i + oppPawnShift][j] == V.EMPTY
+        ) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  getCheckSquares() {
+    if (this.kingPos[this.turn][0] < 0) return [];
+    return super.getCheckSquares();
+  }
+
+  getCurrentScore() {
+    // If my king disappeared: I lost!
+    const c = this.turn;
+    if (this.kingPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0");
+    return super.getCurrentScore();
+  }
+
+  prePlay(move) {
+    if (this.subTurn != 1) return;
+    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] = [move.appear[0].x, move.appear[0].y];
+      this.castleFlags[c] = [V.size.y, V.size.y];
+      return;
+    }
+    const oppCol = V.GetOppCol(c);
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING) {
+      // Opponent's king is captured, game over
+      this.kingPos[oppCol] = [-1, -1];
+      move.captureKing = true;
+    }
+    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;
+    }
+    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;
+    }
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    this.prePlay(move);
+    V.PlayOnBoard(this.board, move);
+    const c = this.turn;
+    move.turn = [c, this.subTurn];
+    const oppCol = V.GetOppCol(c);
+    const oppLastRank = (c == 'w' ? 7 : 0);
+    if (this.subTurn <= 1) this.reserve[oppCol] = null;
+    if (this.subTurn == 0) {
+      this.subTurn++;
+      this.reserve[c] = null;
+    }
+    else if (this.subTurn == 1) {
+      this.subTurn++;
+      if (
+        this.movesCount == 0 ||
+        !!move.captureKing ||
+        !this.atLeastOneMove()
+      ) {
+        this.turn = oppCol;
+        this.movesCount++;
+        this.subTurn = 1;
+        this.promoteFile.push(-1);
+        move.pushPromote = true;
+      }
+    }
+    else {
+      // subTurn == 2
+      this.turn = oppCol;
+      if (move.end.x == oppLastRank) {
+        this.promoteFile.push(move.end.y);
+        this.reserve[oppCol] = {
+          [V.ROOK]: 1,
+          [V.KNIGHT]: 1,
+          [V.BISHOP]: 1,
+          [V.QUEEN]: 1
+        };
+        this.subTurn = 0;
+      }
+      else {
+        this.subTurn = 1;
+        this.promoteFile.push(-1);
+      }
+      move.pushPromote = true;
+      this.movesCount++;
+    }
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    V.UndoOnBoard(this.board, move);
+    const changeTurn = (this.turn != move.turn[0]);
+    this.turn = move.turn[0];
+    this.subTurn = move.turn[1];
+    if (!!move.pushPromote) {
+      const promoteFile = this.promoteFile.pop();
+      if (promoteFile >= 0) this.reserve[V.GetOppCol(this.turn)] = null;
+    }
+    else if (this.subTurn == 0) {
+      this.reserve[this.turn] = {
+        [V.ROOK]: 1,
+        [V.KNIGHT]: 1,
+        [V.BISHOP]: 1,
+        [V.QUEEN]: 1
+      };
+    }
+    if (changeTurn) this.movesCount--;
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    if (this.subTurn != 1) return;
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      // Opponent's king was captured
+      this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
+    super.postUndo(move);
+  }
+
+  getComputerMove() {
+    // Just try to capture as much material as possible (1-half move)
+    const moves = this.getAllValidMoves();
+    if (this.subTurn == 0) {
+      this.play(moves[3]); //HACK... 3 = queen index
+      const res = this.getComputerMove();
+      this.undo(moves[3]);
+      return [moves[3], res];
+    }
+    // subTurn == 1 (necessarily)
+    let candidates = [];
+    let maxValue = -V.INFINITY;
+    for (let m of moves) {
+      let value = 0;
+      if (m.vanish.length == 2) {
+        // Compute delta value, to not give all material on pawns... (TODO)
+        // 0.5 to favor captures (if same type of piece).
+        value = 0.5 +
+          ChessRules.VALUES[m.vanish[1].p] - ChessRules.VALUES[m.vanish[0].p];
+      }
+      if (value > maxValue) {
+        candidates = [m];
+        maxValue = value;
+      }
+      else if (value == maxValue) candidates.push(m);
+    }
+    const m1 = candidates[randInt(candidates.length)];
+    this.play(m1);
+    let m2 = null;
+    if (this.subTurn == 2) {
+      // Just pick a pawn at random
+      const moves2 = this.getAllValidMoves();
+      m2 = moves2[randInt(moves2.length)];
+    }
+    this.undo(m1);
+    if (!m2) return m1;
+    return [m1, m2];
+  }
+
+  getNotation(move) {
+    if (this.subTurn == 0)
+      return move.appear[0].p.toUpperCase() + "@" + V.CoordsToSquare(move.end);
+    if (this.subTurn == 1) return super.getNotation(move);
+    // subTurn == 2: just indicate final square
+    return V.CoordsToSquare(move.end);
+  }
+
+};
 
     return moves.filter(m => !this.oppositeMoves(this.umoves[L - 1], m));
   }
 
-  play(move) {
-    move.flags = JSON.stringify(this.aggregateFlags());
-    this.epSquares.push(this.getEpSquare(move));
-    // Check if the move is the last of the turn: all cases except releases
-    if (!move.released) {
-      // No more union releases available
-      this.turn = V.GetOppCol(this.turn);
-      this.movesCount++;
-      this.lastMoveEnd.push(null);
-    }
-    else this.lastMoveEnd.push(Object.assign({ p: move.released }, move.end));
-    V.PlayOnBoard(this.board, move);
-    this.umoves.push(this.getUmove(move));
-    this.postPlay(move);
-  }
-
   updateCastleFlags(move, piece) {
-    const c = V.GetOppCol(this.turn);
+    const c = this.turn;
     const firstRank = (c == "w" ? 7 : 0);
-    const oppCol = this.turn;
-    const oppFirstRank = 7 - firstRank;
     if (piece == V.KING && move.appear.length > 0)
       this.castleFlags[c] = [V.size.y, V.size.y];
     else if (
       const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
       this.castleFlags[c][flagIdx] = V.size.y;
     }
-    // No more checking: a rook in union can take part in castling.
+    else if (
+      move.end.x == firstRank &&
+      this.castleFlags[c].includes(move.end.y)
+    ) {
+      // Move to our rook: necessary normal piece, to union, releasing
+      // (or the rook was moved before!)
+      const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
+      this.castleFlags[c][flagIdx] = V.size.y;
+    }
   }
 
-  postPlay(move) {
-    if (move.vanish.length == 0)
-      // A released piece just moved. Cannot be the king.
-      return;
-    const c = move.vanish[0].c;
-    const piece = move.vanish[0].p;
+  prePlay(move) {
+    // Easier before move is played in this case (flags are saved)
+    const c = this.turn;
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    const piece = (!!lm ? lm.p : move.vanish[0].p);
     if (piece == V.KING)
       this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
     this.updateCastleFlags(move, piece);
-    if (
-      [1, 6].includes(move.start.x) &&
-      move.vanish.length >= 1 &&
-      move.appear.length == 1
-    ) {
-      // Does this move turn off a 2-squares pawn flag?
-      if (
-        move.vanish[0].p == V.PAWN ||
-        (
-          !(ChessRules.PIECES.includes(move.vanish[0].p)) &&
-          this.getUnionPieces(move.vanish[0].c, move.vanish[0].p)[c] == V.PAWN
-        )
-      ) {
-        this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
-      }
+    const pawnFirstRank = (c == 'w' ? 6 : 1);
+    if (move.start.x == pawnFirstRank)
+      // This move (potentially) turns off a 2-squares pawn flag
+      this.pawnFlags[c][move.start.y] = false;
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    this.prePlay(move);
+    this.epSquares.push(this.getEpSquare(move));
+    // Check if the move is the last of the turn: all cases except releases
+    if (!move.released) {
+      // No more union releases available
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.lastMoveEnd.push(null);
     }
+    else this.lastMoveEnd.push(Object.assign({ p: move.released }, move.end));
+    V.PlayOnBoard(this.board, move);
+    this.umoves.push(this.getUmove(move));
   }
 
   undo(move) {