On smartphone for Teleport, Chakart, Weiqi and some others: option "confirm moves on touch screen"
(=> comme pour corr) + option "confirm moves in corr games"?
-https://www.chessvariants.com/difftaking.dir/replacement.html
-
-https://www.chessvariants.com/other.dir/pocket.html
https://www.chessvariants.com/other.dir/fugue.html
https://www.chessvariants.com/rules/spartan-chess
https://www.chessvariants.com/mvopponent.dir/avalanche.html
-
https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html
https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html
+Squatter Chess: safe on last rank = win
+Companion Chess : pieces of same nature don't attack each others
+Medusa Chess = Isardam
+Crossing Chess = win when the king cross half-board
+Kingmaker: pawns can promote also into enemy king
+Eightkings: 8 pawns + 8 kings (non-royal until the last remains?)
+Crown Chess: place all units on move 1 (similar to Sittuyin, more freely)
+
=====
fanorona https://fr.wikipedia.org/wiki/Fanorona
this.postPlay(move);
}
- updateCastleFlags(move, piece) {
- const c = V.GetOppCol(this.turn);
+ updateCastleFlags(move, piece, color) {
+ const c = color || V.GetOppCol(this.turn);
const firstRank = (c == "w" ? V.size.x - 1 : 0);
// Update castling flags if rooks are moved
const oppCol = this.turn;
const checkSquares = this.vr.getCheckSquares();
if (checkSquares.length > 0) m.notation += "+";
if (idxM == Lm - 1) m.fen = this.vr.getFen();
- if (idx == L - 1 && idxM == Lm - 1) {
- this.incheck = checkSquares;
- this.score = this.vr.getCurrentScore();
- if (["1-0", "0-1"].includes(this.score)) m.notation += "#";
- }
+ if (idx == L - 1 && idxM == Lm - 1) this.incheck = checkSquares;
});
+ this.score = this.vr.getCurrentScore();
+ if (["1-0", "0-1"].includes(this.score)) m.notation += "#";
});
}
if (firstMoveColor == "b") {
"King of the Hill": "King of the Hill",
"Kings cross the 8x8 board": "Kings cross the 8x8 board",
"Kings cross the 11x11 board": "Kings cross the 11x11 board",
+ "Knight in pocket": "Knight in pocket",
"Landing on the board": "Landing on the board",
"Laws of attraction": "Laws of attraction",
"Long jumps over pieces": "Long jumps over pieces",
"Long live the Queen": "Long live the Queen",
"Lose all pieces": "Lose all pieces",
+ "Rearrange enemy pieces": "Rearrange enemy pieces",
"Mandatory captures": "Mandatory captures",
"Mate any piece (v1)": "Mate any piece (v1)",
"Mate any piece (v2)": "Mate any piece (v2)",
"King of the Hill": "Rey de la Colina",
"Kings cross the 8x8 board": "Los reyes cruzan el 8x8 tablero",
"Kings cross the 11x11 board": "Los reyes cruzan el 11x11 tablero",
+ "Knight in pocket": "Caballo en bolsillo",
"Landing on the board": "Aterrizando en el tablero",
"Laws of attraction": "Las leyes de las atracciones",
"Long jumps over pieces": "Saltos largos sobre las piezas",
"Long live the Queen": "Larga vida a la reina",
"Lose all pieces": "Perder todas las piezas",
+ "Rearrange enemy pieces": "Reorganizar piezas opuestas",
"Mandatory captures": "Capturas obligatorias",
"Mate any piece (v1)": "Matar cualquier pieza (v1)",
"Mate any piece (v2)": "Matar cualquier pieza (v2)",
"King of the Hill": "Roi de la Colline",
"Kings cross the 8x8 board": "Les rois traversent l'échiquier 8x8",
"Kings cross the 11x11 board": "Les rois traversent l'échiquier 11x11",
+ "Knight in pocket": "Cavalier en poche",
"Landing on the board": "Débarquement sur l'échiquier",
"Laws of attraction": "Les lois de l'attraction",
"Long jumps over pieces": "Sauts longs par dessus les pièces",
"Long live the Queen": "Long vie à la Reine",
"Lose all pieces": "Perdez toutes les pièces",
+ "Rearrange enemy pieces": "Réorganisez les pièces adverses",
"Mandatory captures": "Captures obligatoires",
"Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
"Mate any piece (v2)": "Matez n'importe quelle pièce (v2)",
--- /dev/null
+p.boxed
+ | Captured pieces are immediatly repositioned on the board.
+
+p.
+ The orthodox rules apply, with the following exception.
+ Each piece taken from the opponent is placed back on an empty
+ square by the player that made the capture, such that
+ul
+ li Bishops remain on squares of the same color.
+ li Pawns are not placed on the first or last row.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+ .diagram.diag22
+ | fen:r1bqkbnr/pppp1ppp/2B2n2/4p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+ figcaption Bishop takes knight, which is repositioned on g8.
+
+p To place the captured piece, just click on an empty square.
+
+h3 Source
+
+p
+ a(href="https://www.chessvariants.com/difftaking.dir/replacement.html")
+ | Replacement chess
+ | on chessvariants.com.
--- /dev/null
+p.boxed
+ | Las piezas capturadas se reemplazan inmediatamente en el tablero.
+
+p.
+ Se aplican reglas ortodoxas, con la siguiente excepción.
+ Cada pieza tomada del oponente se vuelve a poner inmediatamente en el
+ tablero, en casi cualquier espacio libre:
+ul
+ li Los alfiles se quedan en las casillas del mismo color.
+ li Los peones no se colocan en la primera o última fila.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+ .diagram.diag22
+ | fen:r1bqkbnr/pppp1ppp/2B2n2/4p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+ figcaption Alfil toma el caballo, que se reemplaza en g8.
+
+p Para colocar una pieza, simplemente haga clic en una casilla vacía.
+
+h3 Fuente
+
+p
+ | La
+ a(href="https://www.chessvariants.com/difftaking.dir/replacement.html")
+ | variante Reemplazo
+ | en chessvariants.com.
--- /dev/null
+p.boxed
+ | Les pièces capturées sont immédiatement replacées sur l'échiquier.
+
+p.
+ Les règles orthodoxes s'appliquent, avec l'exception suivante.
+ Chaque pièce prise à l'adversaire est immédiatement remise sur l'échiquier,
+ sur presque n'importe quelle case libre :
+ul
+ li Les fous restent sur les cases de la même couleur.
+ li Les pions ne sont pas posés sur la première ou dernière rangée.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+ .diagram.diag22
+ | fen:r1bqkbnr/pppp1ppp/2B2n2/4p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+ figcaption Fou prend cavalier, qui est replacé en g8.
+
+p Pour poser une pièce, cliquez simplement sur une case vide.
+
+h3 Source
+
+p
+ | La
+ a(href="https://www.chessvariants.com/difftaking.dir/replacement.html")
+ | variante Remplacement
+ | sur chessvariants.com.
--- /dev/null
+p.boxed
+ | Each player has a knight in pocket, which can be dropped at any moment.
+
+p.
+ To use your pocket knight, "capture" the enemy king with yours,
+ and then click on any empty square.
+
+figure.diagram-container
+ .diagram
+ | fen:r1bqkbnr/ppNnpppp/2p5/3p4/2PP1B2/8/PP2PPPP/RN1QKBNR:
+ figcaption After N@c7+, black must give the queen.
+
+h3 Source
+
+p
+ a(href="https://www.chessvariants.com/other.dir/pocket.html")
+ | Pocket Knight
+ | on chessvariants.com.
--- /dev/null
+p.boxed
+ | Cada jugador tiene un caballo en su bolsillo,
+ | que puede poner en cualquier momento.
+
+p.
+ Para usar tu caballo de bolsillo, "captura" al rey contrario con el
+ su, luego haga clic en cualquier casilla vacía.
+
+figure.diagram-container
+ .diagram
+ | fen:r1bqkbnr/ppNnpppp/2p5/3p4/2PP1B2/8/PP2PPPP/RN1QKBNR:
+ figcaption Después de N@c7+, las negras deben dar la dama.
+
+h3 Fuente
+
+p
+ a(href="https://www.chessvariants.com/other.dir/pocket.html")
+ | Pocket Knight
+ | en chessvariants.com.
--- /dev/null
+p.boxed
+ | Chaque jouer a un cavalier en poche, qu'il peut poser à tout moment.
+
+p.
+ Pour utiliser votre cavalier de poche, "capturez" le roi adverse avec le
+ votre, puis cliquez sur n'importe quelle case vide.
+
+figure.diagram-container
+ .diagram
+ | fen:r1bqkbnr/ppNnpppp/2p5/3p4/2PP1B2/8/PP2PPPP/RN1QKBNR:
+ figcaption Après N@c7+, les noirs doivent donner la dame.
+
+h3 Source
+
+p
+ a(href="https://www.chessvariants.com/other.dir/pocket.html")
+ | Pocket Knight
+ | sur chessvariants.com.
this.postPlay(move);
}
- updateCastleFlags(move, piece) {
- const c = V.GetOppCol(this.turn);
- const firstRank = (c == "w" ? V.size.x - 1 : 0);
- // Update castling flags if rooks are moved
- const oppCol = this.turn;
- const oppFirstRank = V.size.x - 1 - firstRank;
- if (piece == V.KING && move.appear.length > 0)
- this.castleFlags[c] = [V.size.y, V.size.y];
- else 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;
- }
- // NOTE: not "else if" because a rook could take an opposing rook
- 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;
- }
- }
-
postPlay(move) {
if (move.appear.length == 0 && move.vanish.length == 0) {
this.stage = 2;
this.kingPos[c][0] = move.appear[0].x;
this.kingPos[c][1] = move.appear[0].y;
}
- this.updateCastleFlags(move, piece);
+ super.updateCastleFlags(move, piece);
// Does this move turn off a 2-squares pawn flag?
if ([1, 6].includes(move.start.x) && move.vanish[0].p == V.PAWN)
this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
}
static GenRandInitFen(randomness) {
- // Add 16 pawns flags + empty cmovei + stage == 1:
+ // Add 16 pawns flags + empty cmove + stage == 1:
return ChessRules.GenRandInitFen(randomness)
.slice(0, -2) + "1111111111111111 - - 1";
}
return potentialMoves;
}
- getCurrentScore() {
- if (this.subTurn == 2)
- // Move not over
- return "*";
- return super.getCurrentScore();
- }
-
doClick(square) {
// A click to promote a piece on subTurn 2 would trigger this.
// For now it would then return [NaN, NaN] because surrounding squares
--- /dev/null
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class MadhouseRules extends ChessRules {
+ hoverHighlight(x, y) {
+ // Testing move validity results in an infinite update loop.
+ // TODO: find a way to test validity anyway.
+ return (this.subTurn == 2 && this.board[x][y] == V.EMPTY);
+ }
+
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ this.subTurn = 1;
+ this.firstMove = [];
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ if (this.subTurn == 1) return super.getPotentialMovesFrom([x, y]);
+ // subTurn == 2: a move is a click, not handled here
+ return [];
+ }
+
+ filterValid(moves) {
+ if (this.subTurn == 2) return super.filterValid(moves);
+ const color = this.turn;
+ return moves.filter(m => {
+ this.play(m);
+ let res = false;
+ if (m.vanish.length == 1 || m.appear.length == 2)
+ // Standard check:
+ res = !this.underCheck(color);
+ else {
+ // Capture: find landing square not resulting in check
+ const boundary = (m.vanish[1].p != V.PAWN ? [0, 7] : [1, 6]);
+ const sqColor =
+ m.vanish[1].p == V.BISHOP
+ ? (m.vanish[1].x + m.vanish[1].y) % 2
+ : null;
+ outerLoop: for (let i = boundary[0]; i <= boundary[1]; i++) {
+ for (let j=0; j<8; j++) {
+ if (
+ this.board[i][j] == V.EMPTY &&
+ (!sqColor || (i + j) % 2 == sqColor)
+ ) {
+ const tMove = new Move({
+ appear: [
+ new PiPo({
+ x: i,
+ y: j,
+ c: m.vanish[1].c,
+ p: m.vanish[1].p
+ })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ this.play(tMove);
+ const moveOk = !this.underCheck(color);
+ this.undo(tMove);
+ if (moveOk) {
+ res = true;
+ break outerLoop;
+ }
+ }
+ }
+ }
+ }
+ this.undo(m);
+ return res;
+ });
+ }
+
+ getAllValidMoves() {
+ if (this.subTurn == 1) return super.getAllValidMoves();
+ // Subturn == 2: only replacements
+ let moves = [];
+ const L = this.firstMove.length;
+ const fm = this.firstMove[L - 1];
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ const boundary = (fm.vanish[1].p != V.PAWN ? [0, 7] : [1, 6]);
+ const sqColor =
+ fm.vanish[1].p == V.BISHOP
+ ? (fm.vanish[1].x + fm.vanish[1].y) % 2
+ : null;
+ for (let i = boundary[0]; i < boundary[1]; i++) {
+ for (let j=0; j<8; j++) {
+ if (
+ this.board[i][j] == V.EMPTY &&
+ (!sqColor || (i + j) % 2 == sqColor)
+ ) {
+ const tMove = new Move({
+ appear: [
+ new PiPo({
+ x: i,
+ y: j,
+ c: oppCol,
+ p: fm.vanish[1].p
+ })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ this.play(tMove);
+ const moveOk = !this.underCheck(color);
+ this.undo(tMove);
+ if (moveOk) moves.push(tMove);
+ }
+ }
+ }
+ return moves;
+ }
+
+ doClick(square) {
+ if (isNaN(square[0])) return null;
+ // If subTurn == 2 && square is empty && !underCheck, then replacement
+ if (this.subTurn == 2 && this.board[square[0]][square[1]] == V.EMPTY) {
+ const L = this.firstMove.length;
+ const fm = this.firstMove[L - 1];
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ if (
+ (fm.vanish[1].p == V.PAWN && [0, 7].includes(square[0])) ||
+ (
+ fm.vanish[1].p == V.BISHOP &&
+ (square[0] + square[1] + fm.vanish[1].x + fm.vanish[1].y) % 2 != 0
+ )
+ ) {
+ // Pawns cannot be replaced on first or last rank,
+ // bishops must be replaced on same square color.
+ return null;
+ }
+ const tMove = new Move({
+ appear: [
+ new PiPo({
+ x: square[0],
+ y: square[1],
+ c: oppCol,
+ p: fm.vanish[1].p
+ })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ this.play(tMove);
+ const moveOk = !this.underCheck(color);
+ this.undo(tMove);
+ if (moveOk) return tMove;
+ }
+ return null;
+ }
+
+ play(move) {
+ move.flags = JSON.stringify(this.aggregateFlags());
+ if (move.vanish.length > 0) {
+ this.epSquares.push(this.getEpSquare(move));
+ this.firstMove.push(move);
+ }
+ V.PlayOnBoard(this.board, move);
+ if (
+ this.subTurn == 2 ||
+ move.vanish.length == 1 ||
+ move.appear.length == 2
+ ) {
+ this.turn = V.GetOppCol(this.turn);
+ this.subTurn = 1;
+ this.movesCount++;
+ }
+ else this.subTurn = 2;
+ if (move.vanish.length > 0) this.postPlay(move);
+ }
+
+ postPlay(move) {
+ if (move.appear[0].p == V.KING)
+ this.kingPos[move.appear[0].c] = [move.appear[0].x, move.appear[0].y];
+ this.updateCastleFlags(move, move.appear[0].p, move.appear[0].c);
+ }
+
+ undo(move) {
+ this.disaggregateFlags(JSON.parse(move.flags));
+ if (move.vanish.length > 0) {
+ this.epSquares.pop();
+ this.firstMove.pop();
+ }
+ V.UndoOnBoard(this.board, move);
+ if (this.subTurn == 2) this.subTurn = 1;
+ else {
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount--;
+ this.subTurn = (move.vanish.length > 0 ? 1 : 2);
+ }
+ if (move.vanish.length > 0) super.postUndo(move);
+ }
+
+ getComputerMove() {
+ let moves = this.getAllValidMoves();
+ if (moves.length == 0) return null;
+ // Custom "search" at depth 1 (for now. TODO?)
+ const maxeval = V.INFINITY;
+ const color = this.turn;
+ const initEval = this.evalPosition();
+ moves.forEach(m => {
+ this.play(m);
+ m.eval = (color == "w" ? -1 : 1) * maxeval;
+ if (m.vanish.length == 2 && m.appear.length == 1) {
+ const moves2 = this.getAllValidMoves();
+ m.next = moves2[0];
+ moves2.forEach(m2 => {
+ this.play(m2);
+ const score = this.getCurrentScore();
+ let mvEval = 0;
+ if (["1-0", "0-1"].includes(score))
+ mvEval = (score == "1-0" ? 1 : -1) * maxeval;
+ else if (score == "*")
+ // Add small fluctuations to avoid dropping pieces always on the
+ // first square available.
+ mvEval = initEval + 0.05 - Math.random() / 10;
+ if (
+ (color == 'w' && mvEval > m.eval) ||
+ (color == 'b' && mvEval < m.eval)
+ ) {
+ m.eval = mvEval;
+ m.next = m2;
+ }
+ this.undo(m2);
+ });
+ }
+ else {
+ const score = this.getCurrentScore();
+ if (score != "1/2") {
+ if (score != "*") m.eval = (score == "1-0" ? 1 : -1) * maxeval;
+ else m.eval = this.evalPosition();
+ }
+ }
+ this.undo(m);
+ });
+ moves.sort((a, b) => {
+ return (color == "w" ? 1 : -1) * (b.eval - a.eval);
+ });
+ let candidates = [0];
+ for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
+ candidates.push(i);
+ const mIdx = candidates[randInt(candidates.length)];
+ if (!moves[mIdx].next) return moves[mIdx];
+ const move2 = moves[mIdx].next;
+ delete moves[mIdx]["next"];
+ return [moves[mIdx], move2];
+ }
+
+ getNotation(move) {
+ if (move.vanish.length > 0) return super.getNotation(move);
+ // Replacement:
+ const piece =
+ move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
+ return piece + "@" + V.CoordsToSquare(move.end);
+ }
+};
--- /dev/null
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class PocketknightRules extends ChessRules {
+ hoverHighlight(x, y) {
+ // Testing move validity results in an infinite update loop.
+ // TODO: find a way to test validity anyway.
+ return (this.subTurn == 2 && this.board[x][y] == V.EMPTY);
+ }
+
+ static IsGoodFlags(flags) {
+ // 4 for castle + 2 for knights
+ return !!flags.match(/^[a-z]{4,4}[01]{2,2}$/);
+ }
+
+ setFlags(fenflags) {
+ super.setFlags(fenflags); //castleFlags
+ this.knightFlags = fenflags.substr(4).split("").map(e => e == "1");
+ }
+
+ aggregateFlags() {
+ return [this.castleFlags, this.knightFlags];
+ }
+
+ disaggregateFlags(flags) {
+ this.castleFlags = flags[0];
+ this.knightFlags = flags[1];
+ }
+
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ this.subTurn = 1;
+ }
+
+ static GenRandInitFen(randomness) {
+ // Add 2 knight flags
+ return ChessRules.GenRandInitFen(randomness)
+ .slice(0, -2) + "11 -";
+ }
+
+ getFlagsFen() {
+ return (
+ super.getFlagsFen() + this.knightFlags.map(e => e ? "1" : "0").join("")
+ );
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ if (this.subTurn == 1) {
+ let moves = super.getPotentialMovesFrom([x, y]);
+ // If flag allow it, add "king capture"
+ if (
+ this.knightFlags[this.turn == 'w' ? 0 : 1] &&
+ this.getPiece(x, y) == V.KING
+ ) {
+ const kp = this.kingPos[V.GetOppCol(this.turn)];
+ moves.push(
+ new Move({
+ appear: [],
+ vanish: [],
+ start: { x: x, y: y },
+ end: { x: kp[0], y: kp[1] }
+ })
+ );
+ }
+ return moves;
+ }
+ // subTurn == 2: a move is a click, not handled here
+ return [];
+ }
+
+ filterValid(moves) {
+ if (this.subTurn == 2) return super.filterValid(moves);
+ const color = this.turn;
+ return moves.filter(m => {
+ this.play(m);
+ let res = false;
+ if (m.appear.length > 0)
+ // Standard check:
+ res = !this.underCheck(color);
+ else {
+ // "Capture king": find landing square not resulting in check
+ outerLoop: for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ if (this.board[i][j] == V.EMPTY) {
+ const tMove = new Move({
+ appear: [
+ new PiPo({
+ x: i,
+ y: j,
+ c: color,
+ p: V.KNIGHT
+ })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ this.play(tMove);
+ const moveOk = !this.underCheck(color);
+ this.undo(tMove);
+ if (moveOk) {
+ res = true;
+ break outerLoop;
+ }
+ }
+ }
+ }
+ }
+ this.undo(m);
+ return res;
+ });
+ }
+
+ getAllValidMoves() {
+ if (this.subTurn == 1) return super.getAllValidMoves();
+ // Subturn == 2: only knight landings
+ let moves = [];
+ 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) {
+ const tMove = new Move({
+ appear: [
+ new PiPo({
+ x: i,
+ y: j,
+ c: color,
+ p: V.KNIGHT
+ })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ this.play(tMove);
+ const moveOk = !this.underCheck(color);
+ this.undo(tMove);
+ if (moveOk) moves.push(tMove);
+ }
+ }
+ }
+ return moves;
+ }
+
+ doClick(square) {
+ if (isNaN(square[0])) return null;
+ // If subTurn == 2 && square is empty && !underCheck, then drop
+ if (this.subTurn == 2 && this.board[square[0]][square[1]] == V.EMPTY) {
+ const color = this.turn;
+ const tMove = new Move({
+ appear: [
+ new PiPo({
+ x: square[0],
+ y: square[1],
+ c: color,
+ p: V.KNIGHT
+ })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ this.play(tMove);
+ const moveOk = !this.underCheck(color);
+ this.undo(tMove);
+ if (moveOk) return tMove;
+ }
+ return null;
+ }
+
+ play(move) {
+ move.flags = JSON.stringify(this.aggregateFlags());
+ if (move.appear.length > 0) {
+ // Usual case or knight landing
+ if (move.vanish.length > 0) this.epSquares.push(this.getEpSquare(move));
+ else this.subTurn = 1;
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount++;
+ V.PlayOnBoard(this.board, move);
+ if (move.vanish.length > 0) this.postPlay(move);
+ }
+ else {
+ // "king capture"
+ this.subTurn = 2;
+ this.knightFlags[this.turn == 'w' ? 0 : 1] = false;
+ }
+ }
+
+ undo(move) {
+ this.disaggregateFlags(JSON.parse(move.flags));
+ if (move.appear.length > 0) {
+ if (move.vanish.length > 0) this.epSquares.pop();
+ else this.subTurn = 2;
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount--;
+ V.UndoOnBoard(this.board, move);
+ if (move.vanish.length > 0) this.postUndo(move);
+ }
+ else this.subTurn = 1;
+ }
+
+ getComputerMove() {
+ let moves = this.getAllValidMoves();
+ if (moves.length == 0) return null;
+ // Custom "search" at depth 1 (for now. TODO?)
+ const maxeval = V.INFINITY;
+ const color = this.turn;
+ const initEval = this.evalPosition();
+ moves.forEach(m => {
+ this.play(m);
+ m.eval = (color == "w" ? -1 : 1) * maxeval;
+ if (m.appear.length == 0) {
+ const moves2 = this.getAllValidMoves();
+ m.next = moves2[0];
+ moves2.forEach(m2 => {
+ this.play(m2);
+ const score = this.getCurrentScore();
+ let mvEval = 0;
+ if (["1-0", "0-1"].includes(score))
+ mvEval = (score == "1-0" ? 1 : -1) * maxeval;
+ else if (score == "*")
+ // Add small fluctuations to avoid dropping pieces always on the
+ // first square available.
+ mvEval = initEval + 0.05 - Math.random() / 10;
+ if (
+ (color == 'w' && mvEval > m.eval) ||
+ (color == 'b' && mvEval < m.eval)
+ ) {
+ m.eval = mvEval;
+ m.next = m2;
+ }
+ this.undo(m2);
+ });
+ }
+ else {
+ const score = this.getCurrentScore();
+ if (score != "1/2") {
+ if (score != "*") m.eval = (score == "1-0" ? 1 : -1) * maxeval;
+ else m.eval = this.evalPosition();
+ }
+ }
+ this.undo(m);
+ });
+ moves.sort((a, b) => {
+ return (color == "w" ? 1 : -1) * (b.eval - a.eval);
+ });
+ let candidates = [0];
+ for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
+ candidates.push(i);
+ const mIdx = candidates[randInt(candidates.length)];
+ if (!moves[mIdx].next) return moves[mIdx];
+ const move2 = moves[mIdx].next;
+ delete moves[mIdx]["next"];
+ return [moves[mIdx], move2];
+ }
+
+ getNotation(move) {
+ if (move.vanish.length > 0)
+ return super.getNotation(move);
+ if (move.appear.length == 0)
+ // "king capture"
+ return "-";
+ // Knight landing:
+ return "N@" + V.CoordsToSquare(move.end);
+ }
+};
this.kingPos[c][0] = move.appear[0].x;
this.kingPos[c][1] = move.appear[0].y;
}
- super.updateCastleFlags(move, piece);
+ super.updateCastleFlags(move, piece, c);
}
undo(move) {
return super.underCheck(color);
}
- getCurrentScore() {
- if (this.subTurn == 2)
- // Move not over
- return "*";
- return super.getCurrentScore();
- }
-
doClick(square) {
if (isNaN(square[0])) return null;
// If subTurn == 2 && square is empty && !underCheck, then teleport
}
}
}
- else {
- // Normal move
- const firstRank = (c == "w" ? V.size.x - 1 : 0);
- const oppCol = V.GetOppCol(c);
- const oppFirstRank = V.size.x - 1 - firstRank;
- if (move.vanish[0].p == V.KING && move.appear.length > 0)
- this.castleFlags[c] = [V.size.y, V.size.y];
- else if (
- move.start.x == firstRank &&
- 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 &&
- 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;
- }
- }
+ // Normal check:
+ super.updateCastleFlags(move, move.vanish[0].p, c);
}
undo(move) {
('Koopa', 'Stun & kick pieces'),
('Koth', 'King of the Hill'),
('Losers', 'Get strong at self-mate'),
+ ('Madhouse', 'Rearrange enemy pieces'),
('Madrasi', 'Paralyzed pieces'),
('Magnetic', 'Laws of attraction'),
('Makruk', 'Thai Chess'),
('Parachute', 'Landing on the board'),
('Pawns', 'Reach the last rank'),
('Perfect', 'Powerful pieces'),
+ ('Pocketknight', 'Knight in pocket'),
('Racingkings', 'Kings cross the 8x8 board'),
('Rampage', 'Move under cover'),
('Rifle', 'Shoot pieces'),