--- /dev/null
+../Alice/bc.svg
\ No newline at end of file
--- /dev/null
+../Alice/bl.svg
\ No newline at end of file
--- /dev/null
+../Alice/bo.svg
\ No newline at end of file
--- /dev/null
+../Alice/bs.svg
\ No newline at end of file
--- /dev/null
+../Alice/bt.svg
\ No newline at end of file
--- /dev/null
+../Alice/bu.svg
\ No newline at end of file
--- /dev/null
+../Alice/wc.svg
\ No newline at end of file
--- /dev/null
+../Alice/wl.svg
\ No newline at end of file
--- /dev/null
+../Alice/wo.svg
\ No newline at end of file
--- /dev/null
+../Alice/ws.svg
\ No newline at end of file
--- /dev/null
+../Alice/wt.svg
\ No newline at end of file
--- /dev/null
+../Alice/wu.svg
\ No newline at end of file
lmHighlights[m.start.x + sizeX * m.start.y] = true;
if (!m.end.noHighlight && V.OnBoard(m.end.x, m.end.y))
lmHighlights[m.end.x + sizeX * m.end.y] = true;
+ if (!!m.start.toplay)
+ // For Dice variant (at least?)
+ lmHighlights[m.start.toplay[0] + sizeX * m.start.toplay[1]] = true;
});
}
const showLight = (
Play the piece type determined by a dice roll.
p.
- At each turn, click on any empty square first: you will see a piece type
- written in the moves list, and a piece of this nature will be highlighted on
- the board. You then have to play a move with this piece's type.
+ The first white move is chosen freely. Then, at each turn, you must play a
+ piece of the highlighted type on the board.
+ The piece's type to play is indicated in the moves list as well.
p There is no check or checkmate: the goal is to capture the king.
Juega la pieza cuyo tipo está determinado por una tirada de dado.
p.
- En cada turno, haga clic en una casilla vacía: verá un tipo de pieza
- escrito en la lista de jugadas, y se indicará una pieza de esta naturaleza
- en el tablero. Luego debes jugar un movimiento con una pieza de este tipo.
+ El primer movimiento blanco se elige libremente. Luego, en cada ronda, usted
+ debe jugar una pieza del tipo resaltado en el tablero de ajedrez.
+ El tipo de pieza a jugar también se indica en la lista de movimientos.
p No hay jaque ni jaque mate: el objetivo es capturar al rey.
Jouez la pièce dont le type est déterminé par un lancer de dé.
p.
- À chaque tour, cliquez sur une case vide : vous verrez un type de pièce
- écrit dans la liste des coups, et une pièce de cette nature sera indiquée
- sur l'échiquier. Vous devez ensuite jouer un coup avec une pièce de ce type.
+ Le premier coup blanc est choisi librement. Ensuite, à chaque tour, vous
+ devez jouer une pièce du type mis en valeur sur l'échiquier.
+ Le type de pièce à jouer est également indiqué dans la liste des coups.
p Il n'y a ni échec ni mat : l'objectif est de capturer le roi.
-// TODO
-// Pritchard middle page 45, section 3.6
-//https://www.chessvariants.com/crossover.dir/koopachess.html
+p.boxed
+ | Captured pieces are stunned. They disappear if you capture them again.
+
+p
+ | This variant is inspired by the
+ a(href="https://en.wikipedia.org/wiki/Super_Mario") Super Mario
+ | universe.
+ | When a piece captures another, it "bounce" on it until the next square in
+ | the movement's direction. If this next square is occupied, then it keeps
+ | bouncing and reach the following square, until either a free square or the
+ | edge of the board is met. In this last case, the capturer is lost.
+
+p.
+ Pieces bounced over are then "stunned" for two moves (four half-moves):
+ they cannot move during this period. If they get captured again while being
+ stunned, they are "kicked" out of the board, and all pieces standing on their
+ way vanish as well.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:nrkqnrbb/ppp2ppp/3p4/4p3/1P3P2/8/P1PPP1PP/BNQRNBKR:
+ .diagram.diag22
+ | fen:nukqnrbb/pps2ppp/3s4/4s3/1P6/8/P1PPP1PP/BNQRNBKR:
+ figcaption Before and after 1.fxe5
+
+p.
+ After the move 1.fxe5 on the diagram, the red pieces are stunned for two
+ moves. So white can then play 2.Bxe5, kicking the pawn and capturing (for
+ real) at least the h8 bishop. An option for black may be 1...Qg5, such
+ that after 2.Bxe5 Qxe5 black threaten to kick the white stunned bishop out.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:nuk1nrb1/pps2p1p/3s4/4B1q1/1P6/8/P1PPP1PP/1NQRNBKR:
+ .diagram.diag22
+ | fen:nuk1nrb1/pps2p1p/3s4/3qC3/1P6/8/P1PPP1PP/1NQRNBKR:
+ figcaption After 1...Qg5 2.Bxe5 Qxe5: white bishop is stunned.
+
+p.
+ The goal is to capture the enemy king. Moves which kick your own king
+ out are forbidden, but stunning him is allowed.
+
+h3 Source
+
+p
+ a(href="https://www.jsbeasley.co.uk/encyc.htm")
+ | The Classified Encyclopedia of Chess Variants
+ | , section 3.6. This variant is also listed on chessvariants.com:
+ a(href="https://www.chessvariants.com/crossover.dir/koopachess.html")
+ | Koopa chess
+ | .
+p.boxed
+ | Las piezas capturadas quedan aturdidas.
+ | Desaparecerán si los captura de nuevo.
+
+p
+ | Esta variante está inspirada en el universo
+ a(href="https://fr.wikipedia.org/wiki/Super_Mario") Super Mario
+ | . Cuando una pieza captura a otra, "rebota" sobre ella hasta que
+ | siguiente casilla en la dirección del viaje. Si está ocupada
+ | entonces la pieza continúa rebotando en la misma dirección, hasta que
+ | encontrarse con una casilla vacía o el borde del tablero. En este ultimo
+ | caso, la pieza de captura se pierde.
+
+p.
+ Las piezas que rebotan son aturdidas por dos jugadas
+ (cuatro medios movimientos): no pueden moverse durante este período.
+ Si son capturados nuevamente durante su aturdimiento, entonces
+ son enviados fuera del tablero de ajedrez, y todas las piezas en camino
+ también desaparecen.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:nrkqnrbb/ppp2ppp/3p4/4p3/1P3P2/8/P1PPP1PP/BNQRNBKR:
+ .diagram.diag22
+ | fen:nukqnrbb/pps2ppp/3s4/4s3/1P6/8/P1PPP1PP/BNQRNBKR:
+ figcaption Antes y después de 1.fxe5
+
+p.
+ Después del movimiento 1.fxe5 en el diagrama, las piezas rojas quedan
+ aturdidas por dos jugadas. Entonces las blancas pueden jugar 2.Bxe5,
+ sacando a relucir el peón que lleva al menos el loco h8 con él.
+ Una posibilidad para las negras serían jugar 1...Qg5, de modo que después
+ de 2.Bxe5 Qxe5 las negras amenacen para sacar el alfil mareado.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:nuk1nrb1/pps2p1p/3s4/4B1q1/1P6/8/P1PPP1PP/1NQRNBKR:
+ .diagram.diag22
+ | fen:nuk1nrb1/pps2p1p/3s4/3qC3/1P6/8/P1PPP1PP/1NQRNBKR:
+ figcaption Después de 1...Qg5 2.Bxe5 Qxe5: el alfil blanco está mareado.
+
+p.
+ El objetivo es capturar al rey contrario. Los movimientos que hacen sacar
+ tu propio rey están prohibidos.
+
+h3 Fuente
+
+p
+ a(href="https://www.jsbeasley.co.uk/encyc.htm")
+ | The Classified Encyclopedia of Chess Variants
+ | , sección 3.6. Esta variante también aparece en chessvariants.com:
+ a(href="https://www.chessvariants.com/crossover.dir/koopachess.html")
+ | Koopa chess
+ | .
+p.boxed
+ | Les pièces capturées sont étourdies.
+ | Elles disparaissent si vous les capturez à nouveau.
+
+p
+ | Cette variante est inspirée de l'univers
+ a(href="https://fr.wikipedia.org/wiki/Super_Mario") Super Mario
+ | . Quand une pièce en capture une autre, elle "rebondit" dessus jusqu'à la
+ | case suivante dans la direction du déplacement. Si celle-ci est occupée,
+ | alors la pièce continue de rebondir dans la même direction, jusqu'à
+ | rencontrer une case vide ou bien le bord de l'échiquier. Dans ce dernier
+ | cas, la pièce capturante est perdue.
+
+p.
+ Les pièces sur lesquelles on rebondit sont étourdies pendant deux coups
+ (quatre demi-coups) : elles ne peuvent pas bouger pendant cette période.
+ Si elles sont capturées à nouveau pendant leur étourdissement, alors elles
+ sont envoyées en dehors de l'échiquier, et toutes les pièces sur leur chemin
+ disparaissent également.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:nrkqnrbb/ppp2ppp/3p4/4p3/1P3P2/8/P1PPP1PP/BNQRNBKR:
+ .diagram.diag22
+ | fen:nukqnrbb/pps2ppp/3s4/4s3/1P6/8/P1PPP1PP/BNQRNBKR:
+ figcaption Avant et après 1.fxe5
+
+p.
+ Après le coup 1.fxe5 sur le diagramme, les pièces rouges sont étourdies
+ pendant deux coups. Ainsi les blancs peuvent jouer 2.Bxe5, faisant sortir
+ le pion qui emmène au moins le fou h8 avec lui. Une possibilité pour les
+ noirs serait de jouer 1...Qg5, pour qu'après 2.Bxe5 Qxe5 les noirs menacent
+ de faire sortir le fou étourdi.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:nuk1nrb1/pps2p1p/3s4/4B1q1/1P6/8/P1PPP1PP/1NQRNBKR:
+ .diagram.diag22
+ | fen:nuk1nrb1/pps2p1p/3s4/3qC3/1P6/8/P1PPP1PP/1NQRNBKR:
+ figcaption Après 1...Qg5 2.Bxe5 Qxe5 : le fou blanc est étourdi.
+
+p.
+ L'objectif est de capturer le roi adverse. Les coups qui font sortir votre
+ propre roi sont interdits.
+
+h3 Source
+
+p
+ a(href="https://www.jsbeasley.co.uk/encyc.htm")
+ | The Classified Encyclopedia of Chess Variants
+ | , section 3.6. Cette variante est également listée sur chessvariants.com :
+ a(href="https://www.chessvariants.com/crossover.dir/koopachess.html")
+ | Koopa chess
+ | .
const index = arr.findIndex(rfun);
if (index >= 0) {
arr.splice(index, 1);
- if (all) {
+ if (!!all) {
// Reverse loop because of the splice below
for (let i = arr.length - 1; i >= index; i--) {
if (rfun(arr[i])) arr.splice(i, 1);
// Piece or pawn movement
let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
- if (["s", "p"].includes(piece) && !["s", "p"].includes(move.appear[0].p)) {
+ if (["s", "p"].includes(piece) && !["s", "p"].includes(move.appear[0].p))
// Promotion
notation += "=" + move.appear[0].p.toUpperCase();
- }
return notation;
}
};
start: this.whiteMove.start,
end: this.whiteMove.end,
appear: this.whiteMove.appear,
- vanish: this.whiteMove.vanish,
- illegal: this.whiteMove.illegal
+ vanish: this.whiteMove.vanish
});
}
const mHash = "m" + vm.start.x + vm.start.y + vm.end.x + vm.end.y;
if (!moveSet[mHash]) {
moveSet[mHash] = true;
- vm.illegal = true; //potentially illegal!
+ vm.end.illegal = true; //potentially illegal!
speculations.push(vm);
}
});
m.vanish[1].c != m.vanish[0].c ||
// Self-capture attempt
(
- !other.illegal &&
+ !other.end.illegal &&
other.end.x == m.end.x &&
other.end.y == m.end.y
)
||
(
m.vanish[0].p == V.PAWN &&
- !other.illegal &&
+ !other.end.illegal &&
(
(
// Promotion attempt
)
);
};
- if (!!m1.illegal && !isPossible(m1, m2)) {
+ if (!!m1.end.illegal && !isPossible(m1, m2)) {
// Either an anticipated capture of something which didn't move
// (or not to the right square), or a push through blocus.
// ==> Just discard the move, and add a penalty point
this.penaltyFlags[m1.vanish[0].c]++;
m1.isNull = true;
}
- if (!!m2.illegal && !isPossible(m2, m1)) {
+ if (!!m2.end.illegal && !isPossible(m2, m1)) {
this.penaltyFlags[m2.vanish[0].c]++;
m2.isNull = true;
}
let remain = null;
const p1 = m1.vanish[0].p;
const p2 = m2.vanish[0].p;
- if (!!m1.illegal && !m2.illegal) remain = { c: 'w', p: p1 };
- else if (!!m2.illegal && !m1.illegal) remain = { c: 'b', p: p2 };
+ if (!!m1.end.illegal && !m2.end.illegal) remain = { c: 'w', p: p1 };
+ else if (!!m2.end.illegal && !m1.end.illegal) remain = { c: 'b', p: p2 };
if (!remain) {
// Either both are illegal or both are legal
if (p1 == V.KNIGHT && p2 == V.PAWN) remain = { c: 'w', p: p1 };
let illegalMoves = [];
moves.forEach(m => {
// Warning: m might be illegal!
- if (!m.illegal) {
+ if (!m.end.illegal) {
V.PlayOnBoard(this.board, m);
m.eval = this.evalPosition();
V.UndoOnBoard(this.board, m);
return false;
}
- doClick(square) {
- if (
- this.subTurn == 2 ||
- isNaN(square[0]) ||
- this.board[square[0]][square[1]] != V.EMPTY
- ) {
- return null;
- }
- // Announce the piece' type to be played:
- return this.getRandPieceMove();
+ static ParseFen(fen) {
+ const fenParts = fen.split(" ");
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ { toplay: fenParts[5] }
+ );
}
- getPotentialMovesFrom([x, y]) {
- if (this.subTurn == 1) return [];
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ this.p2play = [];
+ const toplay = V.ParseFen(fen).toplay;
+ if (toplay != "-") this.p2play.push(toplay);
+ }
+
+ getFen() {
+ return super.getFen() + " " + this.getToplayFen();
+ }
+
+ getFen() {
+ return super.getFenForRepeat() + "_" + this.getToplayFen();
+ }
+
+ getToplayFen() {
const L = this.p2play.length;
- const piece = this.getPiece(x, y);
- if (piece == V.PAWN && this.p2play[L-1] != V.PAWN) {
- // The piece must be a pawn about to promote.
- const color = this.turn;
- const beforeLastRank = (color == 'w' ? 1 : 0);
+ return (L > 0 ? this.p2play[L-1] : "-");
+ }
+
+ static GenRandInitFen(randomness) {
+ return ChessRules.GenRandInitFen(randomness) + " -";
+ }
+
+ canMove(piece, color, [x, y]) {
+ const oppCol = V.GetOppCol(color);
+ if (piece == V.PAWN) {
const forward = (color == 'w' ? -1 : 1);
- let moves = [];
- if (this.board[x + forward][y] == V.EMPTY) {
- moves.push(
- this.getBasicMove(
- [x, y], [x + forward], { c: color, p: this.p2play[L-1] })
- );
- }
+ if (this.board[x + forward][y] == V.EMPTY) return true;
for (let shift of [-1, 1]) {
const [i, j] = [x + forward, y + shift];
if (
V.OnBoard(i, j) &&
this.board[i][j] != V.EMPTY &&
- this.getColor(i, j) != color
+ this.getColor(i, j) == oppCol
) {
+ return true;
+ }
+ }
+ }
+ else {
+ const steps =
+ [V.KING, V.QUEEN].includes(piece)
+ ? V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+ : V.steps[piece];
+ for (let s of steps) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ (this.board[i][j] == V.EMPTY || this.getColor(i, j) == oppCol)
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ getRandPiece(color) {
+ // Find pieces which can move and roll a dice
+ let canMove = {};
+ 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 piece = this.getPiece(i, j);
+ if (!canMove[piece] && this.canMove(piece, color, [i, j]))
+ canMove[piece] = [i, j];
+ }
+ }
+ }
+ const options = Object.keys(canMove);
+ const randPiece = options[randInt(options.length)];
+ return [randPiece, canMove[randPiece]];
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ const color = this.turn;
+ let moves = undefined;
+ if (this.movesCount == 0) moves = super.getPotentialMovesFrom([x, y]);
+ else {
+ const L = this.p2play.length; //L is >= 1
+ const piece = this.getPiece(x, y);
+ if (
+ piece == V.PAWN &&
+ this.p2play[L-1] != V.PAWN &&
+ ((color == 'w' && x == 1) || (color == 'b' && x == 6))
+ ) {
+ // The piece is a pawn about to promote
+ const destX = (color == 'w' ? 0 : 7);
+ moves = [];
+ if (this.board[destX][y] == V.EMPTY) {
moves.push(
this.getBasicMove(
- [x, y], [i, j], { c: color, p: this.p2play[L-1] })
+ [x, y], [destX, y], { c: color, p: this.p2play[L-1] })
);
}
+ for (let shift of [-1, 1]) {
+ const [i, j] = [destX, y + shift];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] != V.EMPTY &&
+ this.getColor(i, j) != color
+ ) {
+ moves.push(
+ this.getBasicMove(
+ [x, y], [i, j], { c: color, p: this.p2play[L-1] })
+ );
+ }
+ }
}
- return moves;
+ else if (piece != this.p2play[L-1])
+ // The piece type must match last p2play
+ return [];
+ else moves = super.getPotentialMovesFrom([x, y]);
}
- if (piece != this.p2play[L-1])
- // The piece type must match last p2play
- return [];
- return super.getPotentialMovesFrom([x, y]);
- }
-
- setOtherVariables(fen) {
- super.setOtherVariables(fen);
- this.p2play = [];
- this.subTurn = 1;
+ // Decide which piece the opponent will play:
+ const oppCol = V.GetOppCol(color);
+ moves.forEach(m => {
+ V.PlayOnBoard(this.board, m);
+ const [piece, square] = this.getRandPiece(oppCol);
+ m.start.toplay = square;
+ m.end.piece = piece;
+ V.UndoOnBoard(this.board, m);
+ });
+ return moves;
}
filterValid(moves) {
return "*";
}
- play(move) {
- if (this.subTurn == 1) {
- this.subTurn = 2;
- this.p2play.push(move.appear[0].p);
- return;
- }
- // Subturn == 2 means the (dice-constrained) move is played
- move.flags = JSON.stringify(this.aggregateFlags());
- V.PlayOnBoard(this.board, move);
- this.epSquares.push(this.getEpSquare(move));
- this.movesCount++;
- this.turn = V.GetOppCol(this.turn);
- this.subTurn = 1;
- this.postPlay(move);
- }
-
postPlay(move) {
+ this.p2play.push(move.end.piece);
if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
this.kingPos[move.vanish[1].c] = [-1, -1];
// Castle flags for captured king won't be updated (not important...)
super.postPlay(move);
}
- undo(move) {
- if (this.subTurn == 2) {
- this.subTurn = 1;
- this.p2play.pop();
- return;
- }
- this.disaggregateFlags(JSON.parse(move.flags));
- V.UndoOnBoard(this.board, move);
- this.epSquares.pop();
- this.movesCount--;
- this.turn = V.GetOppCol(this.turn);
- this.subTurn = 2;
- this.postUndo(move);
- }
-
postUndo(move) {
+ this.p2play.pop();
if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
super.postUndo(move);
}
- getRandPieceMove() {
- // For current turn, find pieces which can move and roll a dice
- let canMove = {};
- 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 piece = this.getPiece(i, j);
- if (
- !canMove[piece] &&
- super.getPotentialMovesFrom([i, j]).length > 0
- ) {
- canMove[piece] = [i, j];
- }
- }
- }
- }
- const options = Object.keys(canMove);
- const randPiece = options[randInt(options.length)];
- return (
- new Move({
- appear: [{ p: randPiece }],
- vanish: [],
- start: { x: -1, y: -1 },
- end: { x: canMove[randPiece][0], y: canMove[randPiece][1] }
- })
- );
- }
-
- // Random mover
- getComputerMove() {
- const toPlay = this.getRandPieceMove();
- this.play(toPlay);
- const moves = this.getAllValidMoves();
- const choice = moves[randInt(moves.length)];
- this.undo(toPlay);
- return [toPlay, choice];
+ static get SEARCH_DEPTH() {
+ return 1;
}
getNotation(move) {
- if (this.subTurn == 1) return move.appear[0].p.toUpperCase();
- return super.getNotation(move);
+ return super.getNotation(move) + "/" + move.end.piece.toUpperCase();
}
};
setOtherVariables(fen) {
super.setOtherVariables(fen);
- const fenParsed = V.ParseFen(fen);
+ const captured = V.ParseFen(fen).captured.split("").map(parseInt);
// Initialize captured pieces' counts from FEN
this.captured = {
w: {
- [V.ROOK]: parseInt(fenParsed.captured[0]),
- [V.KNIGHT]: parseInt(fenParsed.captured[1]),
- [V.BISHOP]: parseInt(fenParsed.captured[2]),
- [V.QUEEN]: parseInt(fenParsed.captured[3]),
- [V.MARSHALL]: parseInt(fenParsed.captured[4]),
- [V.CARDINAL]: parseInt(fenParsed.captured[5])
+ [V.ROOK]: captured[0],
+ [V.KNIGHT]: captured[1],
+ [V.BISHOP]: captured[2],
+ [V.QUEEN]: captured[3],
+ [V.MARSHALL]: captured[4],
+ [V.CARDINAL]: captured[5]
},
b: {
- [V.ROOK]: parseInt(fenParsed.captured[6]),
- [V.KNIGHT]: parseInt(fenParsed.captured[7]),
- [V.BISHOP]: parseInt(fenParsed.captured[8]),
- [V.QUEEN]: parseInt(fenParsed.captured[9]),
- [V.MARSHALL]: parseInt(fenParsed.captured[10]),
- [V.CARDINAL]: parseInt(fenParsed.captured[11])
+ [V.ROOK]: captured[6],
+ [V.KNIGHT]: captured[7],
+ [V.BISHOP]: captured[8],
+ [V.QUEEN]: captured[9],
+ [V.MARSHALL]: captured[10],
+ [V.CARDINAL]: captured[11]
}
};
}
-import { ChessRulesi, PiPo } from "@/base_rules";
+import { ChessRules, PiPo } from "@/base_rules";
export class KoopaRules extends ChessRules {
static get HasEnpassant() {
return false;
}
- // Between stun time and stun + 1 move
- static get STUNNED_1() {
+ static get STUNNED() {
return ['s', 'u', 'o', 'c', 't', 'l'];
}
- // Between stun + 1 move and stun + 2 moves
- static get STUNNED_2() {
- return ['v', 'x', 'a', 'd', 'w', 'm'];
+ static get PIECES() {
+ return ChessRules.PIECES.concat(V.STUNNED);
}
- static get PIECES() {
- return ChessRules.PIECES.concat(V.STUNNED_1).concat(V.STUNNED_2);
+ static ParseFen(fen) {
+ let res = ChessRules.ParseFen(fen);
+ const fenParts = fen.split(" ");
+ res.stunned = fenParts[4];
+ return res;
+ }
+
+ static IsGoodFen(fen) {
+ if (!ChessRules.IsGoodFen(fen)) return false;
+ const fenParsed = V.ParseFen(fen);
+ // 5) Check "stunned"
+ if (
+ !fenParsed.stunned ||
+ (
+ fenParsed.stunned != "-" &&
+ !fenParsed.stunned.match(/^([a-h][1-8][1-4],?)*$/)
+ )
+ ) {
+ return false;
+ }
+ return true;
+ }
+
+ getPpath(b) {
+ return (V.STUNNED.includes(b[1]) ? "Koopa/" : "") + b;
+ }
+
+ getFen() {
+ return super.getFen() + " " + this.getStunnedFen();
+ }
+
+ getFenForRepeat() {
+ return super.getFenForRepeat() + "_" + this.getStunnedFen();
+ }
+
+ getStunnedFen() {
+ return (
+ Object.keys(this.stunned)
+ .map(square => square + this.stunned[square])
+ .join(",")
+ );
+ }
+
+ // Base GenRandInitFen() is fine because en-passant indicator will
+ // stand for stunned indicator.
+
+ scanKings(fen) {
+ this.INIT_COL_KING = { w: -1, b: -1 };
+ // Squares of white and black king:
+ this.kingPos = { w: [-1, -1], b: [-1, -1] };
+ const fenRows = V.ParseFen(fen).position.split("/");
+ const startRow = { 'w': V.size.x - 1, 'b': 0 };
+ for (let i = 0; i < fenRows.length; i++) {
+ let k = 0; //column index on board
+ for (let j = 0; j < fenRows[i].length; j++) {
+ switch (fenRows[i].charAt(j)) {
+ case "k":
+ case "l":
+ this.kingPos["b"] = [i, k];
+ this.INIT_COL_KING["b"] = k;
+ break;
+ case "K":
+ case "L":
+ this.kingPos["w"] = [i, k];
+ this.INIT_COL_KING["w"] = k;
+ break;
+ default: {
+ const num = parseInt(fenRows[i].charAt(j));
+ if (!isNaN(num)) k += num - 1;
+ }
+ }
+ k++;
+ }
+ }
+ }
+
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ let stunnedArray = [];
+ const stunnedFen = V.ParseFen(fen).stunned;
+ if (stunnedFen != "-") {
+ stunnedArray =
+ stunnedFen
+ .split(",")
+ .map(s => {
+ return {
+ square: s.substr(0, 2),
+ state: parseInt(s[2])
+ };
+ });
+ }
+ this.stunned = {};
+ stunnedArray.forEach(s => {
+ this.stunned[s.square] = s.state;
+ });
}
getNormalizedStep(step) {
getPotentialMovesFrom([x, y]) {
let moves = super.getPotentialMovesFrom([x, y]);
// Complete moves: stuns & kicks
- const stun = V.STUNNED_1.concat(V.STUNNED_2);
+ let promoteAfterStun = [];
+ const color = this.turn;
moves.forEach(m => {
if (m.vanish.length == 2 && m.appear.length == 1) {
const step =
this.getNormalizedStep([m.end.x - m.start.x, m.end.y - m.start.y]);
// "Capture" something: is target stunned?
- if (stun.includes(m.vanish[1].p)) {
+ if (V.STUNNED.includes(m.vanish[1].p)) {
// Kick it: continue movement in the same direction,
// destroying all on its path.
let [i, j] = [m.end.x + step[0], m.end.y + step[1]];
}
else {
// The piece is now stunned
- m.appear.push(m.vanish.pop());
+ m.appear.push(JSON.parse(JSON.stringify(m.vanish[1])));
const pIdx = ChessRules.PIECES.findIndex(p => p == m.appear[1].p);
- m.appear[1].p = V.STUNNED_1[pIdx];
+ m.appear[1].p = V.STUNNED[pIdx];
// And the capturer continue in the same direction until an empty
// square or the edge of the board, maybe stunning other pieces.
let [i, j] = [m.end.x + step[0], m.end.y + step[1]];
while (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) {
const colIJ = this.getColor(i, j);
const pieceIJ = this.getPiece(i, j);
- m.vanish.push(
- new PiPo({
- x: i,
- y: j,
- c: colIJ,
- p: pieceIJ
- })
- );
- const pIdx = ChessRules.PIECES.findIndex(p => p == pieceIJ);
- m.appear.push(
- new PiPo({
- x: i,
- y: j,
- c: colIJ,
- p: V.STUNNED_1[pIdx]
- })
- );
+ let pIdx = ChessRules.PIECES.findIndex(p => p == pieceIJ);
+ if (pIdx >= 0) {
+ // The piece isn't already stunned
+ m.vanish.push(
+ new PiPo({
+ x: i,
+ y: j,
+ c: colIJ,
+ p: pieceIJ
+ })
+ );
+ m.appear.push(
+ new PiPo({
+ x: i,
+ y: j,
+ c: colIJ,
+ p: V.STUNNED[pIdx]
+ })
+ );
+ }
i += step[0];
j += step[1];
}
m.appear[0].x = i;
m.appear[0].y = j;
// Is it a pawn on last rank?
+ if ((color == 'w' && i == 0) || (color == 'b' && i == 7)) {
+ m.appear[0].p = V.ROOK;
+ for (let ppiece of [V.KNIGHT, V.BISHOP, V.QUEEN]) {
+ let mp = JSON.parse(JSON.stringify(m));
+ mp.appear[0].p = ppiece;
+ promoteAfterStun.push(mp);
+ }
+ }
}
- else {
+ else
// The piece is out
m.appear.shift();
- }
}
}
});
- return moves;
- }
-
- static GenRandInitFen(randomness) {
- // No en-passant:
- return ChessRules.GenRandInitFen(randomness).slice(0, -2);
+ return moves.concat(promoteAfterStun);
}
filterValid(moves) {
// Forbid kicking own king out
const color = this.turn;
return moves.filter(m => {
- return m.vanish.every(v => v.c != color || !(['l','m'].includes(v.p)));
+ const kingAppear = m.appear.some(a => a.c == color && a.p == V.KING);
+ return m.vanish.every(v => {
+ return (
+ v.c != color ||
+ !["k", "l"].includes(v.p) ||
+ (v.p == "k" && kingAppear)
+ );
+ });
});
}
}
postPlay(move) {
- // TODO: toutes les pièces "stunned" by me (turn) avancent d'un niveau
- // --> alter board
- move.wasStunned = array of stunned stage 2 pieces (just back to normal then)
+ // Base method is fine because a stunned king (which won't be detected)
+ // can still castle after going back to normal.
+ super.postPlay(move);
+ const kIdx = move.vanish.findIndex(v => v.p == "l");
+ if (kIdx >= 0)
+ // A stunned king vanish (game over)
+ this.kingPos[move.vanish[kIdx].c] = [-1, -1];
+ move.stunned = JSON.stringify(this.stunned);
+ // Array of stunned stage 1 pieces (just back to normal then)
+ Object.keys(this.stunned).forEach(square => {
+ // All (formerly) stunned pieces progress by 1 level, if still on board
+ const coords = V.SquareToCoords(square);
+ const [x, y] = [coords.x, coords.y];
+ if (V.STUNNED.includes(this.board[x][y][1])) {
+ // Stunned piece still on board
+ this.stunned[square]--;
+ if (this.stunned[square] == 0) {
+ delete this.stunned[square];
+ const color = this.getColor(x, y);
+ const piece = this.getPiece(x, y);
+ const pIdx = V.STUNNED.findIndex(p => p == piece);
+ this.board[x][y] = color + ChessRules.PIECES[pIdx];
+ }
+ }
+ else delete this.stunned[square];
+ });
+ // Any new stunned pieces?
+ move.appear.forEach(a => {
+ if (V.STUNNED.includes(a.p))
+ // Set to maximum stun level:
+ this.stunned[V.CoordsToSquare({ x: a.x, y: a.y })] = 4;
+ });
}
postUndo(move) {
- if (wasStunned
- STUNNED_2
+ super.postUndo(move);
+ const kIdx = move.vanish.findIndex(v => v.p == "l");
+ if (kIdx >= 0) {
+ // A stunned king vanished
+ this.kingPos[move.vanish[kIdx].c] =
+ [move.vanish[kIdx].x, move.vanish[kIdx].y];
+ }
+ this.stunned = JSON.parse(move.stunned);
+ for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ const square = V.CoordsToSquare({ x: i, y: j });
+ const pieceIJ = this.getPiece(i, j);
+ if (!this.stunned[square]) {
+ const pIdx = V.STUNNED.findIndex(p => p == pieceIJ);
+ if (pIdx >= 0)
+ this.board[i][j] = this.getColor(i, j) + ChessRules.PIECES[pIdx];
+ }
+ else {
+ const pIdx = ChessRules.PIECES.findIndex(p => p == pieceIJ);
+ if (pIdx >= 0)
+ this.board[i][j] = this.getColor(i, j) + V.STUNNED[pIdx];
+ }
+ }
+ }
+ }
+
+ static get VALUES() {
+ return Object.assign(
+ {
+ s: 1,
+ u: 5,
+ o: 3,
+ c: 3,
+ t: 9,
+ l: 1000
+ },
+ ChessRules.VALUES
+ );
+ }
+
+ static get SEARCH_DEPTH() {
+ return 2;
+ }
+
+ getNotation(move) {
+ if (
+ move.appear.length == 2 &&
+ move.vanish.length == 2 &&
+ move.appear.concat(move.vanish).every(
+ av => ChessRules.PIECES.includes(av.p)) &&
+ move.appear[0].p == V.KING
+ ) {
+ if (move.end.y < move.start.y) return "0-0-0";
+ return "0-0";
+ }
+ const finalSquare = V.CoordsToSquare(move.end);
+ const piece = this.getPiece(move.start.x, move.start.y);
+ const captureMark = move.vanish.length >= 2 ? "x" : "";
+ let pawnMark = "";
+ if (piece == 'p' && captureMark.length == 1)
+ pawnMark = V.CoordToColumn(move.start.y); //start column
+ // Piece or pawn movement
+ let notation =
+ (piece == V.PAWN ? pawnMark : piece.toUpperCase()) +
+ captureMark + finalSquare;
+ if (
+ piece == 'p' &&
+ move.appear[0].c == move.vanish[0].c &&
+ move.appear[0].p != 'p'
+ ) {
+ // Promotion
+ notation += "=" + move.appear[0].p.toUpperCase();
+ }
+ return notation;
}
};
<style lang="sass">
#faqDiv
+ margin-bottom: 10px
@media screen and (max-width: 767px)
margin-left: var(--universal-margin)
margin-right: var(--universal-margin)
button.tabbtn#liveGames(@click="setDisplay('live',$event)")
| {{ st.tr["Live games"] }}
button.tabbtn#corrGames(@click="setDisplay('corr',$event)")
- | {{ st.tr["Correspondance games"] }}
+ | {{ st.tr["Correspondence games"] }}
button.tabbtn#importGames(@click="setDisplay('import',$event)")
| {{ st.tr["Imported games"] }}
GameList(
query =
"UPDATE Users " +
// Also empty the login token to invalidate future attempts
- "SET loginToken = NULL" +
+ "SET loginToken = NULL, loginTime = NULL " +
setSessionToken + " " +
"WHERE id = " + id;
db.run(query);