return true;
}
+ // Or castle
+ static get HasCastle() {
+ return V.HasFlags;
+ }
+
// Some variants don't have en-passant
static get HasEnpassant() {
return true;
// For FEN checking
static IsGoodFlags(flags) {
- return !!flags.match(/^[01]{4,4}$/);
+ // NOTE: a little too permissive to work with more variants
+ return !!flags.match(/^[a-z]{4,4}$/);
}
static IsGoodEnpassant(enpassant) {
return b; //usual pieces in pieces/ folder
}
+ // Path to promotion pieces (usually the same)
+ getPPpath(b) {
+ return this.getPpath(b);
+ }
+
// Aggregates flags into one object
aggregateFlags() {
return this.castleFlags;
static GenRandInitFen(randomness) {
if (randomness == 0)
// Deterministic:
- return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 1111 -";
+ return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -";
let pieces = { w: new Array(8), b: new Array(8) };
+ let flags = "";
// Shuffle pieces on first (and last rank if randomness == 2)
for (let c of ["w", "b"]) {
if (c == 'b' && randomness == 1) {
pieces['b'] = pieces['w'];
+ flags += flags;
break;
}
pieces[c][bishop2Pos] = "b";
pieces[c][knight2Pos] = "n";
pieces[c][rook2Pos] = "r";
+ flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
}
// Add turn + flags + enpassant
return (
pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " w 0 1111 -"
+ " w 0 " + flags + " -"
);
}
// Flags part of the FEN string
getFlagsFen() {
let flags = "";
- // Add castling flags
- for (let i of ["w", "b"]) {
- for (let j = 0; j < 2; j++) flags += this.castleFlags[i][j] ? "1" : "0";
- }
+ // Castling flags
+ for (let c of ["w", "b"])
+ flags += this.castleFlags[c].map(V.CoordToColumn).join("");
return flags;
}
setFlags(fenflags) {
// white a-castle, h-castle, black a-castle, h-castle
this.castleFlags = { w: [true, true], b: [true, true] };
- for (let i = 0; i < 4; i++)
- this.castleFlags[i < 2 ? "w" : "b"][i % 2] = fenflags.charAt(i) == "1";
+ for (let i = 0; i < 4; i++) {
+ this.castleFlags[i < 2 ? "w" : "b"][i % 2] =
+ V.ColumnToCoord(fenflags.charAt(i));
+ }
}
//////////////////
this.setOtherVariables(fen);
}
- // Scan board for kings and rooks positions
- scanKingsRooks(fen) {
+ // Scan board for kings positions
+ scanKings(fen) {
this.INIT_COL_KING = { w: -1, b: -1 };
- this.INIT_COL_ROOK = { w: [-1, -1], b: [-1, -1] };
this.kingPos = { w: [-1, -1], b: [-1, -1] }; //squares of white and black king
const fenRows = V.ParseFen(fen).position.split("/");
const startRow = { 'w': V.size.x - 1, 'b': 0 };
this.kingPos["w"] = [i, k];
this.INIT_COL_KING["w"] = k;
break;
- case "r":
- if (i == startRow['b']) {
- if (this.INIT_COL_ROOK["b"][0] < 0) this.INIT_COL_ROOK["b"][0] = k;
- else this.INIT_COL_ROOK["b"][1] = k;
- }
- break;
- case "R":
- if (i == startRow['w']) {
- if (this.INIT_COL_ROOK["w"][0] < 0) this.INIT_COL_ROOK["w"][0] = k;
- else this.INIT_COL_ROOK["w"][1] = k;
- }
- break;
default: {
const num = parseInt(fenRows[i].charAt(j));
if (!isNaN(num)) k += num - 1;
: undefined;
this.epSquares = [epSq];
}
- // Search for king and rooks positions:
- this.scanKingsRooks(fen);
+ // Search for kings positions:
+ this.scanKings(fen);
}
/////////////////////
// What are the king moves from square x,y ?
getPotentialKingMoves(sq) {
// Initialize with normal moves
- let moves = this.getSlideNJumpMoves(
+ const moves = this.getSlideNJumpMoves(
sq,
V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
"oneStep"
castleSide < 2;
castleSide++ //large, then small
) {
- if (!this.castleFlags[c][castleSide]) continue;
+ if (this.castleFlags[c][castleSide] >= V.size.y) continue;
// If this code is reached, rooks and king are on initial position
// Nothing on the path of the king ? (and no checks)
// Nothing on the path to the rook?
step = castleSide == 0 ? -1 : 1;
- for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+ const rookPos = this.castleFlags[c][castleSide];
+ for (i = y + step; i != rookPos; i += step) {
if (this.board[x][i] != V.EMPTY) continue castlingCheck;
}
- const rookPos = this.INIT_COL_ROOK[c][castleSide];
// Nothing on final squares, except maybe king and castling rook?
for (i = 0; i < 2; i++) {
for (let psq of move.vanish) board[psq.x][psq.y] = psq.c + psq.p;
}
+ prePlay() {}
+
+ play(move) {
+ // DEBUG:
+// if (!this.states) this.states = [];
+// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen();
+// this.states.push(stateFen);
+
+ this.prePlay(move);
+ if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
+ if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move));
+ V.PlayOnBoard(this.board, move);
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount++;
+ this.postPlay(move);
+ }
+
// After move is played, update variables + flags
- updateVariables(move) {
+ postPlay(move) {
+ const c = V.GetOppCol(this.turn);
let piece = undefined;
- // TODO: update variables before move is played, and just use this.turn?
- // (doesn't work in general, think MarseilleChess)
- let c = undefined;
- if (move.vanish.length >= 1) {
+ if (move.vanish.length >= 1)
// Usual case, something is moved
piece = move.vanish[0].p;
- c = move.vanish[0].c;
- } else {
+ else
// Crazyhouse-like variants
piece = move.appear[0].p;
- c = move.appear[0].c;
- }
- if (!['w','b'].includes(c)) {
- // Checkered, for example
- c = V.GetOppCol(this.turn);
- }
const firstRank = c == "w" ? V.size.x - 1 : 0;
// Update king position + flags
if (piece == V.KING && move.appear.length > 0) {
this.kingPos[c][0] = move.appear[0].x;
this.kingPos[c][1] = move.appear[0].y;
- if (V.HasFlags) this.castleFlags[c] = [false, false];
+ if (V.HasCastle) this.castleFlags[c] = [V.size.y, V.size.y];
return;
}
- if (V.HasFlags) {
+ if (V.HasCastle) {
// Update castling flags if rooks are moved
const oppCol = V.GetOppCol(c);
const oppFirstRank = V.size.x - 1 - firstRank;
if (
move.start.x == firstRank && //our rook moves?
- this.INIT_COL_ROOK[c].includes(move.start.y)
+ this.castleFlags[c].includes(move.start.y)
) {
- const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1);
- this.castleFlags[c][flagIdx] = false;
+ const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+ this.castleFlags[c][flagIdx] = V.size.y;
} else if (
move.end.x == oppFirstRank && //we took opponent rook?
- this.INIT_COL_ROOK[oppCol].includes(move.end.y)
+ this.castleFlags[oppCol].includes(move.end.y)
) {
- const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1);
- this.castleFlags[oppCol][flagIdx] = false;
+ const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+ this.castleFlags[oppCol][flagIdx] = V.size.y;
}
}
}
- // After move is undo-ed *and flags resetted*, un-update other variables
- // TODO: more symmetry, by storing flags increment in move (?!)
- unupdateVariables(move) {
- // (Potentially) Reset king position
- const c = this.getColor(move.start.x, move.start.y);
- if (this.getPiece(move.start.x, move.start.y) == V.KING)
- this.kingPos[c] = [move.start.x, move.start.y];
- }
-
- play(move) {
- // DEBUG:
-// if (!this.states) this.states = [];
-// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen();
-// this.states.push(stateFen);
-
- if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
- if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move));
- V.PlayOnBoard(this.board, move);
- this.turn = V.GetOppCol(this.turn);
- this.movesCount++;
- this.updateVariables(move);
- }
+ preUndo() {}
undo(move) {
+ this.preUndo(move);
if (V.HasEnpassant) this.epSquares.pop();
if (V.HasFlags) this.disaggregateFlags(JSON.parse(move.flags));
V.UndoOnBoard(this.board, move);
this.turn = V.GetOppCol(this.turn);
this.movesCount--;
- this.unupdateVariables(move);
+ this.postUndo(move);
// DEBUG:
// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen();
// this.states.pop();
}
+ // After move is undo-ed *and flags resetted*, un-update other variables
+ // TODO: more symmetry, by storing flags increment in move (?!)
+ postUndo(move) {
+ // (Potentially) Reset king position
+ const c = this.getColor(move.start.x, move.start.y);
+ if (this.getPiece(move.start.x, move.start.y) == V.KING)
+ this.kingPos[c] = [move.start.x, move.start.y];
+ }
+
///////////////
// END OF GAME
this.moves.unshift({
notation: "...",
start: { x: -1, y: -1 },
- end: { x: -1, y: -1 }
+ end: { x: -1, y: -1 },
+ fen: game.fenStart
});
}
this.positionCursorTo(this.moves.length - 1);
} else {
this.lastMove = this.moves[index];
}
- }
- else
- this.lastMove = null;
+ } else this.lastMove = null;
},
analyzePosition: function() {
let newUrl =
const playSubmove = (smove) => {
if (!navigate) smove.notation = this.vr.getNotation(smove);
this.vr.play(smove);
- this.lastMove = smove;
// Is opponent in check?
this.incheck = this.vr.getCheckSquares(this.vr.turn);
if (!navigate) {
const afterMove = (smove, initurn) => {
if (this.vr.turn != initurn) {
// Turn has changed: move is complete
- if (!smove.fen) {
+ if (!smove.fen)
// NOTE: only FEN of last sub-move is required (thus setting it here)
smove.fen = this.vr.getFen();
- this.emitFenIfAnalyze();
- }
+ this.lastMove = smove;
+ this.emitFenIfAnalyze();
this.inMultimove = false;
if (!noemit) {
var score = this.vr.getCurrentScore();
this.incheck = this.vr.getCheckSquares(this.vr.turn);
} else {
if (!move) {
- if (this.cursor < 0) return; //no more moves
+ const minCursor =
+ this.moves.length > 0 && this.moves[0].notation == "..."
+ ? 1
+ : 0;
+ if (this.cursor < minCursor) return; //no more moves
move = this.moves[this.cursor];
}
// Caution; if multi-move, undo all submoves from last to first
},
gotoBegin: function() {
if (this.inMultimove) this.cancelCurrentMultimove();
- while (this.cursor >= 0)
- this.undo(null, null, "light");
- if (this.moves.length > 0 && this.moves[0].notation == "...") {
- this.cursor = 0;
- this.lastMove = this.moves[0];
- } else {
- this.lastMove = null;
- }
- this.incheck = [];
+ const minCursor =
+ this.moves.length > 0 && this.moves[0].notation == "..."
+ ? 1
+ : 0;
+ while (this.cursor >= minCursor) this.undo(null, null, "light");
+ this.lastMove = (minCursor == 1 ? this.moves[0] : null);
+ this.incheck = this.vr.getCheckSquares(this.vr.turn);
this.emitFenIfAnalyze();
},
gotoEnd: function() {
selectedPiece: null, //moving piece (or clicked piece)
start: null, //pixels coordinates + id of starting square (click or drag)
click: "",
+ clickTime: 0,
settings: store.state.settings
};
},
attrs: {
src:
"/images/pieces/" +
- this.vr.getPpath(this.vr.board[ci][cj], this.userColor, this.score) +
+ this.vr.getPpath(
+ this.vr.board[ci][cj],
+ // Extra args useful for some variants:
+ this.userColor,
+ this.score,
+ this.orientation) +
".svg"
}
})
// A "choice" is a move
const applyMove = (e) => {
e.stopPropagation();
+ // Force a delay between move is shown and clicked
+ // (otherwise a "double-click" bug might occur)
+ if (Date.now() - this.clickTime < 200) return;
this.play(m);
this.choices = [];
};
attrs: {
src:
"/images/pieces/" +
- this.vr.getPpath(m.appear[0].c + m.appear[0].p) +
+ this.vr.getPPpath(
+ m.appear[0].c + m.appear[0].p,
+ // Extra arg useful for some variants:
+ this.orientation) +
".svg"
},
class: { "choice-piece": true },
let endSquare = getSquareFromId(landing.id);
let moves = this.findMatchingMoves(endSquare);
this.possibleMoves = [];
- if (moves.length > 1) this.choices = moves;
- else if (moves.length == 1) this.play(moves[0]);
+ if (moves.length > 1) {
+ this.clickTime = Date.now();
+ this.choices = moves;
+ } else if (moves.length == 1) this.play(moves[0]);
// else: forbidden move attempt
},
findMatchingMoves: function(endSquare) {
return res;
}
- updateVariables(move) {
- super.updateVariables(move); //standard king
+ postPlay(move) {
+ super.postPlay(move); //standard king
const piece = move.vanish[0].p;
const c = move.vanish[0].c;
// "l" = Alice king
if (piece == "l") {
this.kingPos[c][0] = move.appear[0].x;
this.kingPos[c][1] = move.appear[0].y;
- this.castleFlags[c] = [false, false];
+ this.castleFlags[c] = [8, 8];
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
const c = move.vanish[0].c;
- if (move.vanish[0].p == "l") this.kingPos[c] = [move.start.x, move.start.y];
+ if (move.vanish[0].p == "l")
+ this.kingPos[c] = [move.start.x, move.start.y];
}
getCurrentScore() {
}
static GenRandInitFen(randomness) {
- return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
+ return ChessRules.GenRandInitFen(randomness).slice(0, -2);
}
getPotentialMovesFrom([x, y]) {
castleSide < 2;
castleSide++ //large, then small
) {
- if (!this.castleFlags[c][castleSide]) continue;
+ if (this.castleFlags[c][castleSide] >= 8) continue;
// If this code is reached, rooks and king are on initial position
// Nothing on the path of the king ? (and no checks)
// Nothing on the path to the rook?
step = castleSide == 0 ? -1 : 1;
- for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+ const rookPos = this.castleFlags[c][castleSide];
+ for (i = y + step; i != rookPos; i += step) {
if (this.board[x][i] != V.EMPTY) continue castlingCheck;
}
- const rookPos = this.INIT_COL_ROOK[c][castleSide];
// Nothing on final squares, except maybe king and castling rook?
for (i = 0; i < 2; i++) {
});
}
- updateVariables(move) {
- super.updateVariables(move);
- const color = V.GetOppCol(this.turn);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length >= 2 && move.appear.length == 1) {
- move.vanish.forEach(v => {
- if (v.c == color)
- return;
+ for (let i = 1; i<move.vanish.length; i++) {
+ const v = move.vanish[i];
// Did opponent king disappeared?
if (v.p == V.KING)
this.kingPos[this.turn] = [-1, -1];
// Or maybe a rook?
else if (v.p == V.ROOK) {
if (v.y < this.INIT_COL_KING[v.c])
- this.castleFlags[v.c][0] = false;
+ this.castleFlags[v.c][0] = 8;
else
// v.y > this.INIT_COL_KING[v.c]
- this.castleFlags[v.c][1] = false;
+ this.castleFlags[v.c][1] = 8;
}
- });
+ }
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
- const color = this.turn;
+ preUndo(move) {
+ super.preUndo(move);
+ const oppCol = this.turn;
if (move.vanish.length >= 2 && move.appear.length == 1) {
// Did opponent king disappeared?
- const psq = move.vanish.find(v => v.p == V.KING && v.c != color)
+ const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
if (psq)
this.kingPos[psq.c] = [psq.x, psq.y];
}
}
static GenRandInitFen(randomness) {
- return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
+ return ChessRules.GenRandInitFen(randomness).slice(0, -2);
}
getPotentialMovesFrom([x, y]) {
}
// 2) Among attacked pieces, which cannot escape capture?
- // --> without (normal-)capturing: difference with Allmate variant
+ // --> without (normal-)capturing: difference with Allmate1 variant
// Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
outerLoop: for (let i=0; i<V.size.x; i++) {
for (let j=0; j<V.size.y; j++) {
castleSide < 2;
castleSide++ //large, then small
) {
- if (!this.castleFlags[c][castleSide]) continue;
+ if (this.castleFlags[c][castleSide] >= 8) continue;
// If this code is reached, rooks and king are on initial position
// Nothing on the path of the king ? (and no checks)
// Nothing on the path to the rook?
step = castleSide == 0 ? -1 : 1;
- for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+ const rookPos = this.castleFlags[c][castleSide];
+ for (i = y + step; i != rookPos; i += step) {
if (this.board[x][i] != V.EMPTY) continue castlingCheck;
}
- const rookPos = this.INIT_COL_ROOK[c][castleSide];
// Nothing on final squares, except maybe king and castling rook?
for (i = 0; i < 2; i++) {
});
}
- updateVariables(move) {
- super.updateVariables(move);
- const color = V.GetOppCol(this.turn);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length >= 2 && move.appear.length == 1) {
- move.vanish.forEach(v => {
- if (v.c == color)
- return;
+ for (let i = 1; i<move.vanish.length; i++) {
+ const v = move.vanish[i];
// Did opponent king disappeared?
if (v.p == V.KING)
this.kingPos[this.turn] = [-1, -1];
// Or maybe a rook?
else if (v.p == V.ROOK) {
if (v.y < this.INIT_COL_KING[v.c])
- this.castleFlags[v.c][0] = false;
+ this.castleFlags[v.c][0] = 8;
else
// v.y > this.INIT_COL_KING[v.c]
- this.castleFlags[v.c][1] = false;
+ this.castleFlags[v.c][1] = 8;
}
- });
+ }
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
- const color = this.turn;
+ preUndo(move) {
+ super.preUndo(move);
+ const oppCol = this.turn;
if (move.vanish.length >= 2 && move.appear.length == 1) {
// Did opponent king disappeared?
- const psq = move.vanish.find(v => v.p == V.KING && v.c != color)
+ const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
if (psq)
this.kingPos[psq.c] = [psq.x, psq.y];
}
return res;
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
const piece = move.vanish[0].p;
const c = move.vanish[0].c;
// Update antiking position
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
const c = move.vanish[0].c;
if (move.vanish[0].p == V.ANTIKING)
this.antikingPos[c] = [move.start.x, move.start.y];
static GenRandInitFen(randomness) {
if (randomness == 0)
- return "rnbqkbnr/pppppppp/3A4/8/8/3a4/PPPPPPPP/RNBQKBNR w 0 1111 -";
+ return "rnbqkbnr/pppppppp/3A4/8/8/3a4/PPPPPPPP/RNBQKBNR w 0 ahah -";
let pieces = { w: new Array(8), b: new Array(8) };
+ let flags = "";
let antikingPos = { w: -1, b: -1 };
for (let c of ["w", "b"]) {
if (c == 'b' && randomness == 1) {
pieces['b'] = pieces['w'];
antikingPos['b'] = antikingPos['w'];
+ flags += flags;
break;
}
pieces[c][bishop2Pos] = "b";
pieces[c][knight2Pos] = "n";
pieces[c][rook2Pos] = "r";
+ flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
}
const ranks23_black =
"pppppppp/" +
ranks23_white +
"/" +
pieces["w"].join("").toUpperCase() +
- " w 0 1111 -"
+ " w 0 " + flags + " -"
);
}
import { ChessRules } from "@/base_rules";
export const VariantRules = class ArenaRules extends ChessRules {
- static get hasFlags() {
+ static get HasFlags() {
return false;
}
static GenRandInitFen(randomness) {
- return ChessRules
- .GenRandInitFen(randomness)
- .replace("w 0 1111 -", "w 0 -");
+ return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "-";
}
static InArena(x) {
);
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (move.appear.length == 0) {
// Capture
const firstRank = { w: 7, b: 0 };
Math.abs(this.kingPos[c][1] - move.end.y) <= 1
) {
this.kingPos[c] = [-1, -1];
- this.castleFlags[c] = [false, false];
+ this.castleFlags[c] = [8, 8];
} else {
// Now check if init rook(s) exploded
if (Math.abs(move.end.x - firstRank[c]) <= 1) {
- if (Math.abs(move.end.y - this.INIT_COL_ROOK[c][0]) <= 1)
- this.castleFlags[c][0] = false;
- if (Math.abs(move.end.y - this.INIT_COL_ROOK[c][1]) <= 1)
- this.castleFlags[c][1] = false;
+ if (Math.abs(move.end.y - this.castleFlags[c][0]) <= 1)
+ this.castleFlags[c][0] = 8;
+ if (Math.abs(move.end.y - this.castleFlags[c][1]) <= 1)
+ this.castleFlags[c][1] = 8;
}
}
}
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
+ postUndo(move) {
+ super.postUndo(move);
+ const c = this.turn;
const oppCol = V.GetOppCol(c);
- if (
- [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => {
- return e < 0;
- })
- ) {
+ if ([this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)) {
// There is a chance that last move blowed some king away..
for (let psq of move.vanish) {
if (psq.p == "k")
castleSide < 2;
castleSide++ //large, then small
) {
- if (!this.castleFlags[c][castleSide]) continue;
+ if (this.castleFlags[c][castleSide] >= 8) continue;
// If this code is reached, rooks and king are on initial position
- const rookPos = this.INIT_COL_ROOK[c][castleSide];
+ const rookPos = this.castleFlags[c][castleSide];
if (this.getColor(x, rookPos) != c)
// Rook is here but changed color
continue;
// Nothing on the path to the rook?
step = castleSide == 0 ? -1 : 1;
- for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+ for (i = y + step; i != rookPos; i += step) {
if (this.board[x][i] != V.EMPTY) continue castlingCheck;
}
export const VariantRules = class LosersRules extends ChessRules {
// Trim all non-capturing moves
static KeepCaptures(moves) {
- return moves.filter(m => m.vanish.length == 2);
+ return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1);
}
// Stop at the first capture found (if any)
super.setOtherVariables(fen);
// Local stack of non-capturing checkered moves:
this.cmoves = [];
- const cmove = fen.split(" ")[5];
+ const cmove = V.ParseFen(fen).cmove;
if (cmove == "-") this.cmoves.push(null);
else {
this.cmoves.push({
static IsGoodFlags(flags) {
// 4 for castle + 16 for pawns
- return !!flags.match(/^[01]{20,20}$/);
+ return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/);
}
setFlags(fenflags) {
w: [...Array(8).fill(true)], //pawns can move 2 squares?
b: [...Array(8).fill(true)]
};
- const flags = fenflags.substr(4); //skip first 4 digits, for castle
+ const flags = fenflags.substr(4); //skip first 4 letters, for castle
for (let c of ["w", "b"]) {
for (let i = 0; i < 8; i++)
this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1";
return res;
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
// Does this move turn off a 2-squares pawn flag?
- const secondRank = [1, 6];
- if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
+ 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;
+ this.cmoves.push(this.getCmove(move));
+ }
+
+ postUndo(move) {
+ super.postUndo(move);
+ this.cmoves.pop();
}
getCurrentScore() {
}
static GenRandInitFen(randomness) {
+ // Add 16 pawns flags + empty cmove:
return ChessRules.GenRandInitFen(randomness)
- // Add 16 pawns flags + empty cmove:
- .replace(" w 0 1111", " w 0 11111111111111111111 -");
+ .slice(0, -2) + "1111111111111111 - -";
}
static ParseFen(fen) {
return fen;
}
- // TODO (design): this cmove update here or in (un)updateVariables ?
- play(move) {
- this.cmoves.push(this.getCmove(move));
- super.play(move);
- }
-
- undo(move) {
- this.cmoves.pop();
- super.undo(move);
- }
-
static get SEARCH_DEPTH() {
return 2;
}
import { randInt, shuffle } from "@/utils/alea";
export const VariantRules = class CircularRules extends ChessRules {
+ static get HasCastle() {
+ return false;
+ }
+
static get HasEnpassant() {
return false;
}
return flags;
}
- updateVariables(move) {
+ postPlay(move) {
+ super.postPlay(move);
const c = move.vanish[0].c;
- const secondRank = {"w":6, "b":2};
- // Update king position + flags
- if (move.vanish[0].p == V.KING && move.appear.length > 0) {
- this.kingPos[c][0] = move.appear[0].x;
- this.kingPos[c][1] = move.appear[0].y;
- }
- else if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
+ const secondRank = { "w": 6, "b": 2 };
+ if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
// This move turns off a 2-squares pawn flag
this.pawnFlags[c][move.start.y] = false;
}
return true;
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle
const color = move.appear[0].c;
if (move.vanish.length == 0) {
else if (move.vanish.length == 2) this.reserve[color][move.vanish[1].p]++;
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
if (move.vanish.length == 2 && move.appear.length == 2) return;
const color = this.turn;
if (move.vanish.length == 0) {
return [];
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
// We took opponent king (because if castle vanish[1] is a rook)
this.kingPos[this.turn] = [-1, -1];
this.updateEnlightened();
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
const c = move.vanish[0].c;
const oppCol = V.GetOppCol(c);
if (this.kingPos[oppCol][0] < 0)
return "l";
}
- static get PIECES() {
- return ChessRules.PIECES.concat([V.JAILER, V.SENTRY, V.LANCER]);
- }
-
// Lancer directions *from white perspective*
static get LANCER_DIRS() {
return {
};
}
+ static get PIECES() {
+ return ChessRules.PIECES
+ .concat([V.JAILER, V.SENTRY])
+ .concat(Object.keys(V.LANCER_DIRS));
+ }
+
getPiece(i, j) {
const piece = this.board[i][j].charAt(1);
// Special lancer case: 8 possible orientations
return piece;
}
- getPpath(b) {
- if ([V.JAILER, V.SENTRY].concat(Object.keys(V.LANCER_DIRS)).includes(b[1]))
- return "Eightpieces/" + b;
+ getPpath(b, color, score, orientation) {
+ if ([V.JAILER, V.SENTRY].includes(b[1])) return "Eightpieces/" + b;
+ if (Object.keys(V.LANCER_DIRS).includes(b[1])) {
+ if (orientation == 'w') return "Eightpieces/" + b;
+ // Find opposite direction for adequate display:
+ let oppDir = '';
+ switch (b[1]) {
+ case 'c':
+ oppDir = 'g';
+ break;
+ case 'g':
+ oppDir = 'c';
+ break;
+ case 'd':
+ oppDir = 'h';
+ break;
+ case 'h':
+ oppDir = 'd';
+ break;
+ case 'e':
+ oppDir = 'm';
+ break;
+ case 'm':
+ oppDir = 'e';
+ break;
+ case 'f':
+ oppDir = 'o';
+ break;
+ case 'o':
+ oppDir = 'f';
+ break;
+ }
+ return "Eightpieces/" + b[0] + oppDir;
+ }
return b;
}
+ getPPpath(b, orientation) {
+ return this.getPpath(b, null, null, orientation);
+ }
+
static ParseFen(fen) {
const fenParts = fen.split(" ");
- return Object.assign(ChessRules.ParseFen(fen), {
- sentrypush: fenParts[5]
- });
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ { sentrypush: fenParts[5] }
+ );
+ }
+
+ static IsGoodFen(fen) {
+ if (!ChessRules.IsGoodFen(fen)) return false;
+ const fenParsed = V.ParseFen(fen);
+ // 5) Check sentry push (if any)
+ if (
+ fenParsed.sentrypush != "-" &&
+ !fenParsed.sentrypush.match(/^([a-h][1-8],?)+$/)
+ ) {
+ return false;
+ }
+ return true;
}
getFen() {
static GenRandInitFen(randomness) {
if (randomness == 0)
// Deterministic:
- return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -";
+ return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 ahah - -";
let pieces = { w: new Array(8), b: new Array(8) };
+ let flags = "";
// Shuffle pieces on first (and last rank if randomness == 2)
for (let c of ["w", "b"]) {
if (c == 'b' && randomness == 1) {
pieces['w'].slice(0, lancerIdx)
.concat(['g'])
.concat(pieces['w'].slice(lancerIdx + 1));
+ flags += flags;
break;
}
let rookPos = positions[0];
let jailerPos = positions[2];
const kingPos = positions[1];
+ flags += V.CoordToColumn(rookPos) + V.CoordToColumn(jailerPos);
if (Math.random() < 0.5) [rookPos, jailerPos] = [jailerPos, rookPos];
pieces[c][rookPos] = "r";
pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " w 0 1111 - -"
+ " w 0 " + flags + " - -"
);
}
- // Scan kings, rooks and jailers
- scanKingsRooks(fen) {
- this.INIT_COL_KING = { w: -1, b: -1 };
- this.INIT_COL_ROOK = { w: -1, b: -1 };
- this.INIT_COL_JAILER = { w: -1, b: -1 };
- 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;
- for (let j = 0; j < fenRows[i].length; j++) {
- switch (fenRows[i].charAt(j)) {
- case "k":
- this.kingPos["b"] = [i, k];
- this.INIT_COL_KING["b"] = k;
- break;
- case "K":
- this.kingPos["w"] = [i, k];
- this.INIT_COL_KING["w"] = k;
- break;
- case "r":
- if (i == startRow['b'] && this.INIT_COL_ROOK["b"] < 0)
- this.INIT_COL_ROOK["b"] = k;
- break;
- case "R":
- if (i == startRow['w'] && this.INIT_COL_ROOK["w"] < 0)
- this.INIT_COL_ROOK["w"] = k;
- break;
- case "j":
- if (i == startRow['b'] && this.INIT_COL_JAILER["b"] < 0)
- this.INIT_COL_JAILER["b"] = k;
- break;
- case "J":
- if (i == startRow['w'] && this.INIT_COL_JAILER["w"] < 0)
- this.INIT_COL_JAILER["w"] = k;
- break;
- default: {
- const num = parseInt(fenRows[i].charAt(j));
- if (!isNaN(num)) k += num - 1;
- }
- }
- k++;
- }
- }
- }
-
// Is piece on square (x,y) immobilized?
isImmobilized([x, y]) {
const color = this.getColor(x, y);
);
}
- getPotentialMovesFrom([x,y]) {
+ getPotentialMovesFrom([x, y]) {
// At subTurn == 2, jailers aren't effective (Jeff K)
- if (this.subTurn == 1 && !!this.isImmobilized([x, y])) return [];
+ if (this.subTurn == 1) {
+ const jsq = this.isImmobilized([x, y]);
+ if (!!jsq) {
+ let moves = [];
+ // Special pass move if king:
+ if (this.getPiece(x, y) == V.KING) {
+ moves.push(
+ new Move({
+ appear: [],
+ vanish: [],
+ start: { x: x, y: y },
+ end: { x: jsq[0], y: jsq[1] }
+ })
+ );
+ }
+ return moves;
+ }
+ }
if (this.subTurn == 2) {
// Temporarily change pushed piece color.
// (Not using getPiece() because of lancers)
// Don't forget to re-add the sentry on the board:
// Also fix color of pushed piece afterward:
moves.forEach(m => {
- m.appear.push({x: x, y: y, p: V.SENTRY, c: color});
- m.appear[0].c = oppCol;
+ m.appear.unshift({x: x, y: y, p: V.SENTRY, c: color});
+ m.appear[1].c = oppCol;
m.vanish[0].c = oppCol;
});
+ this.board[x][y] = saveXYstate;
}
return moves;
}
// En passant:
const Lep = this.epSquares.length;
- const epSquare = this.epSquares[Lep - 1]; //always at least one element
+ const epSquare = this.epSquares[Lep - 1];
if (
!!epSquare &&
epSquare.x == x + shiftX &&
return moves;
}
- // Obtain all lancer moves in "step" direction,
- // without final re-orientation.
+ // Obtain all lancer moves in "step" direction
getPotentialLancerMoves_aux([x, y], step, tr) {
let moves = [];
// Add all moves to vacant squares until opponent is met:
this.sentryPush[L-1][pl-1].y == y
) {
// I was pushed: allow all directions (for this move only), but
- // do not change direction after moving.
+ // do not change direction after moving, *except* if I keep the
+ // same orientation in which I was pushed.
const color = this.getColor(x, y);
+ const curDir = V.LANCER_DIRS[this.board[x][x].charAt(1)];
Object.values(V.LANCER_DIRS).forEach(step => {
const dirCode = Object.keys(V.LANCER_DIRS).find(k => {
- return (V.LANCER_DIRS[k][0] == step[0] && V.LANCER_DIRS[k][1] == step[1]);
+ return (
+ V.LANCER_DIRS[k][0] == step[0] &&
+ V.LANCER_DIRS[k][1] == step[1]
+ );
});
- Array.prototype.push.apply(
- moves,
- this.getPotentialLancerMoves_aux([x, y], step, { p: dirCode, c: color })
- );
+ const dirMoves =
+ this.getPotentialLancerMoves_aux(
+ [x, y],
+ step,
+ { p: dirCode, c: color }
+ );
+ if (curDir[0] == step[0] && curDir[1] == step[1]) {
+ // Keeping same orientation: can choose after
+ let chooseMoves = [];
+ dirMoves.forEach(m => {
+ Object.keys(V.LANCER_DIRS).forEach(k => {
+ let mk = JSON.parse(JSON.stringify(m));
+ mk.appear[0].p = k;
+ moves.push(mk);
+ });
+ });
+ Array.prototype.push.apply(moves, chooseMoves);
+ } else Array.prototype.push.apply(moves, dirMoves);
});
return moves;
}
});
});
return moves;
- } else return monodirMoves;
+ } else {
+ // I'm pushed: add potential nudges
+ let potentialNudges = [];
+ for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+ if (
+ V.OnBoard(x + step[0], y + step[1]) &&
+ this.board[x + step[0]][y + step[1]] == V.EMPTY
+ ) {
+ potentialNudges.push(
+ this.getBasicMove(
+ [x, y],
+ [x + step[0], y + step[1]]
+ )
+ );
+ }
+ }
+ return monodirMoves.concat(potentialNudges);
+ }
}
getPotentialSentryMoves([x, y]) {
if (m.appear.length == 0) {
let res = false;
this.play(m);
- let moves2 = this.filterValid(
- this.getPotentialMovesFrom([m.end.x, m.end.y]));
+ let potentialMoves = this.getPotentialMovesFrom([m.end.x, m.end.y]);
+ // Add nudges (if any a priori possible)
+ for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+ if (
+ V.OnBoard(m.end.x + step[0], m.end.y + step[1]) &&
+ this.board[m.end.x + step[0]][m.end.y + step[1]] == V.EMPTY
+ ) {
+ potentialMoves.push(
+ this.getBasicMove(
+ [m.end.x, m.end.y],
+ [m.end.x + step[0], m.end.y + step[1]]
+ )
+ );
+ }
+ }
+ let moves2 = this.filterValid(potentialMoves);
for (let m2 of moves2) {
this.play(m2);
res = !this.underCheck(color);
});
}
- getPotentialKingMoves([x, y]) {
- let moves = super.getPotentialKingMoves([x, y]);
- // Augment with pass move is the king is immobilized:
- const jsq = this.isImmobilized([x, y]);
- if (!!jsq) {
- moves.push(
- new Move({
- appear: [],
- vanish: [],
- start: { x: x, y: y },
- end: { x: jsq[0], y: jsq[1] }
- })
- );
- }
- return moves;
- }
-
// Adapted: castle with jailer possible
getCastleMoves([x, y]) {
const c = this.getColor(x, y);
castleSide < 2;
castleSide++
) {
- if (!this.castleFlags[c][castleSide]) continue;
+ if (this.castleFlags[c][castleSide] >= 8) continue;
// Rook (or jailer) and king are on initial position
-
const finDist = finalSquares[castleSide][0] - y;
let step = finDist / Math.max(1, Math.abs(finDist));
i = y;
this.isAttacked([x, i], [oppCol]) ||
(this.board[x][i] != V.EMPTY &&
(this.getColor(x, i) != c ||
- ![V.KING, V.ROOK].includes(this.getPiece(x, i))))
+ ![V.KING, V.ROOK, V.JAILER].includes(this.getPiece(x, i))))
) {
continue castlingCheck;
}
i += step;
} while (i != finalSquares[castleSide][0]);
-
step = castleSide == 0 ? -1 : 1;
- const rookOrJailerPos =
- castleSide == 0
- ? Math.min(this.INIT_COL_ROOK[c], this.INIT_COL_JAILER[c])
- : Math.max(this.INIT_COL_ROOK[c], this.INIT_COL_JAILER[c]);
+ const rookOrJailerPos = this.castleFlags[c][castleSide];
for (i = y + step; i != rookOrJailerPos; i += step)
if (this.board[x][i] != V.EMPTY) continue castlingCheck;
return moves;
}
+ atLeastOneMove() {
+ // If in second-half of a move, we already know that a move is possible
+ if (this.subTurn == 2) return true;
+ return super.atLeastOneMove();
+ }
+
filterValid(moves) {
// Disable check tests for sentry pushes,
// because in this case the move isn't finished
if (m.appear.length > 0) movesWithoutSentryPushes.push(m);
else movesWithSentryPushes.push(m);
});
+
+ // TODO: if after move a sentry can take king in 2 times?!
+
const filteredMoves = super.filterValid(movesWithoutSentryPushes);
// If at least one full move made, everything is allowed:
if (this.movesCount >= 2)
return this.filterValid(this.getPotentialMovesFrom(sentrySq));
}
- updateVariables(move) {
- const c = this.turn;
- const piece = move.vanish[0].p;
- const firstRank = (c == "w" ? V.size.x - 1 : 0);
-
- // Update king position + flags
- if (piece == V.KING) {
- this.kingPos[c][0] = move.appear[0].x;
- this.kingPos[c][1] = move.appear[0].y;
- this.castleFlags[c] = [false, false];
- return;
- }
-
- // Update castling flags if rook or jailer moved (or is captured)
- const oppCol = V.GetOppCol(c);
- const oppFirstRank = V.size.x - 1 - firstRank;
- let flagIdx = 0;
- if (
- // Our rook moves?
- move.start.x == firstRank &&
- this.INIT_COL_ROOK[c] == move.start.y
- ) {
- if (this.INIT_COL_ROOK[c] > this.INIT_COL_JAILER[c]) flagIdx++;
- this.castleFlags[c][flagIdx] = false;
- } else if (
- // Our jailer moves?
- move.start.x == firstRank &&
- this.INIT_COL_JAILER[c] == move.start.y
- ) {
- if (this.INIT_COL_JAILER[c] > this.INIT_COL_ROOK[c]) flagIdx++;
- this.castleFlags[c][flagIdx] = false;
- } else if (
- // We took opponent's rook?
- move.end.x == oppFirstRank &&
- this.INIT_COL_ROOK[oppCol] == move.end.y
- ) {
- if (this.INIT_COL_ROOK[oppCol] > this.INIT_COL_JAILER[oppCol]) flagIdx++;
- this.castleFlags[oppCol][flagIdx] = false;
- } else if (
- // We took opponent's jailer?
- move.end.x == oppFirstRank &&
- this.INIT_COL_JAILER[oppCol] == move.end.y
- ) {
- if (this.INIT_COL_JAILER[oppCol] > this.INIT_COL_ROOK[oppCol]) flagIdx++;
- this.castleFlags[oppCol][flagIdx] = false;
- }
-
+ prePlay(move) {
if (move.appear.length == 0 && move.vanish.length == 1) {
// The sentry is about to push a piece: subTurn goes from 1 to 2
this.sentryPos = { x: move.end.x, y: move.end.y };
- } else if (this.subTurn == 2) {
+ } else if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) {
// A piece is pushed: forbid array of squares between start and end
// of move, included (except if it's a pawn)
let squares = [];
- if (move.vanish[0].p != V.PAWN) {
- if ([V.KNIGHT,V.KING].includes(move.vanish[0].p))
- // short-range pieces: just forbid initial square
- squares.push(move.start);
- else {
- const deltaX = move.end.x - move.start.x;
- const deltaY = move.end.y - move.start.y;
- const step = [
- deltaX / Math.abs(deltaX) || 0,
- deltaY / Math.abs(deltaY) || 0
- ];
- for (
- let sq = {x: move.start.x, y: move.start.y};
- sq.x != move.end.x && sq.y != move.end.y;
- sq.x += step[0], sq.y += step[1]
- ) {
- squares.push(sq);
- }
+ if ([V.KNIGHT,V.KING].includes(move.vanish[0].p))
+ // short-range pieces: just forbid initial square
+ squares.push({ x: move.start.x, y: move.start.y });
+ else {
+ const deltaX = move.end.x - move.start.x;
+ const deltaY = move.end.y - move.start.y;
+ const step = [
+ deltaX / Math.abs(deltaX) || 0,
+ deltaY / Math.abs(deltaY) || 0
+ ];
+ for (
+ let sq = {x: move.start.x, y: move.start.y};
+ sq.x != move.end.x && sq.y != move.end.y;
+ sq.x += step[0], sq.y += step[1]
+ ) {
+ squares.push({ x: sq.x, y: sq.y });
}
- // Add end square as well, to know if I was pushed (useful for lancers)
- squares.push(move.end);
}
+ // Add end square as well, to know if I was pushed (useful for lancers)
+ squares.push({ x: move.end.x, y: move.end.y });
this.sentryPush.push(squares);
} else this.sentryPush.push(null);
}
- // TODO: cleaner (global) update/unupdate variables logic, rename...
- unupdateVariables(move) {
- super.unupdateVariables(move);
- this.sentryPush.pop();
- }
-
play(move) {
+// if (!this.states) this.states = [];
+// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+// this.states.push(stateFen);
+
+ this.prePlay(move);
move.flags = JSON.stringify(this.aggregateFlags());
this.epSquares.push(this.getEpSquare(move));
V.PlayOnBoard(this.board, move);
- this.updateVariables(move);
// Is it a sentry push? (useful for undo)
move.sentryPush = (this.subTurn == 2);
if (this.subTurn == 1) this.movesCount++;
this.turn = V.GetOppCol(this.turn);
this.subTurn = 1;
}
+ this.postPlay(move);
+ }
+
+ postPlay(move) {
+ if (move.vanish.length == 0)
+ // Special pass move of the king: nothing to update!
+ return;
+ super.postPlay(move);
}
undo(move) {
this.epSquares.pop();
this.disaggregateFlags(JSON.parse(move.flags));
V.UndoOnBoard(this.board, move);
- const L = this.sentryPush.length;
// Decrement movesCount except if the move is a sentry push
if (!move.sentryPush) this.movesCount--;
- // Turn changes only if not undoing second part of a sentry push
- if (!move.sentryPush || this.subTurn == 1)
+ if (this.subTurn == 2) this.subTurn = 1;
+ else {
this.turn = V.GetOppCol(this.turn);
- this.unupdateVariables(move);
+ if (move.sentryPush) this.subTurn = 2;
+ }
+ this.postUndo(move);
+
+// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+// if (stateFen != this.states[this.states.length-1]) debugger;
+// this.states.pop();
+ }
+
+ postUndo(move) {
+ super.postUndo(move);
+ this.sentryPush.pop();
}
isAttacked(sq, colors) {
colors.includes(this.getColor(coord.x, coord.y))
)
) {
- lancerPos.push(coord);
+ if (this.getPiece(coord.x, coord.y) == V.LANCER)
+ lancerPos.push({x: coord.x, y: coord.y});
+ coord.x += step[0];
+ coord.y += step[1];
}
for (let xy of lancerPos) {
const dir = V.LANCER_DIRS[this.board[xy.x][xy.y].charAt(1)];
const deltaX = x2 - x1;
const deltaY = y2 - y1;
const step = [ deltaX / Math.abs(deltaX), deltaY / Math.abs(deltaY) ];
- if (allowedStep.every(st => st[0] != step[0] || st[1] != step[1]))
+ if (allowedSteps.every(st => st[0] != step[0] || st[1] != step[1]))
return false;
- let sq = [ x1 = step[0], y1 + step[1] ];
+ let sq = [ x1 + step[0], y1 + step[1] ];
while (sq[0] != x2 && sq[1] != y2) {
if (
+ // NOTE: no need to check OnBoard in this special case
(!lancer && this.board[sq[0]][sq[1]] != V.EMPTY) ||
(!!lancer && this.getColor(sq[0], sq[1]) != color)
) {
return false;
}
+ sq[0] += step[0];
+ sq[1] += step[1];
}
return true;
};
V.OnBoard(sq[0], sq[1]) &&
this.getColor(sq[0], sq[1]) == color
) {
- candidates.push(sq);
+ candidates.push([ sq[0], sq[1] ]);
}
}
}
// TODO: this situation should not happen
return null;
- const setEval = (move) => {
+ const setEval = (move, next) => {
const score = this.getCurrentScore();
+ const curEval = move.eval;
if (score != "*") {
move.eval =
score == "1/2"
? 0
: (score == "1-0" ? 1 : -1) * maxeval;
- } else move[i].eval = this.evalPosition();
+ } else move.eval = this.evalPosition();
+ if (
+ // "next" is defined after sentry pushes
+ !!next && (
+ !curEval ||
+ color == 'w' && move.eval > curEval ||
+ color == 'b' && move.eval < curEval
+ )
+ ) {
+ move.second = next;
+ }
};
// Just search_depth == 1 (because of sentries. TODO: can do better...)
const moves2 = this.getAllValidMoves();
moves2.forEach(m2 => {
this.play(m2);
- setEval(m1);
+ setEval(m1, m2);
this.undo(m2);
});
}
let candidates = [0];
for (let j = 1; j < moves1.length && moves1[j].eval == moves1[0].eval; j++)
candidates.push(j);
- return moves1[candidates[randInt(candidates.length)]];
+ const choice = moves1[candidates[randInt(candidates.length)]];
+ return (!choice.second ? choice : [choice, choice.second]);
}
+ // TODO: if subTurn == 2, take some precautions, in particular pawn pushed on 1st rank.
+ // --> should indicate Sxb2,bxc1
getNotation(move) {
// Special case "king takes jailer" is a pass move
if (move.appear.length == 0 && move.vanish.length == 0) return "pass";
return [];
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
// Treat the promotion case: (not the capture part)
if (move.appear[0].p != move.vanish[0].p) {
this.material[move.appear[0].c][move.appear[0].p]++;
this.material[move.vanish[1].c][move.vanish[1].p]--;
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
if (move.appear[0].p != move.vanish[0].p) {
this.material[move.appear[0].c][move.appear[0].p]--;
this.material[move.appear[0].c][V.PAWN]++;
);
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length == 2 && move.appear.length == 1) {
// Capture: update this.captured
this.captured[move.vanish[1].c][move.vanish[1].p]++;
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
if (move.vanish.length == 2 && move.appear.length == 1)
this.captured[move.vanish[1].c][move.vanish[1].p]--;
if (move.vanish[0].p != move.appear[0].p)
if (randomness == 0) {
// No castling in the official initial setup
return "r8r/1nbqkmcbn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKMCBN1/R8R " +
- "w 0 0000 - 00000000000000";
+ "w 0 zzzz - 00000000000000";
}
let pieces = { w: new Array(10), b: new Array(10) };
static GenRandInitFen(randomness) {
return ChessRules.GenRandInitFen(randomness)
- .replace("w 0 1111 -", "w 0 1111")
+ .slice(0, -2)
.replace(
"/pppppppp/8/8/8/8/PPPPPPPP/",
"/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/"
}
// Scan board for kings positions (no castling)
- scanKingsRooks(fen) {
+ scanKings(fen) {
this.kingPos = { w: [-1, -1], b: [-1, -1] };
const fenRows = V.ParseFen(fen).position.split("/");
for (let i = 0; i < fenRows.length; i++) {
return [];
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (
move.vanish.length >= 2 &&
[V.KING,V.HIDDEN_CODE[V.KING]].includes(move.vanish[1].p)
}
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
const c = move.vanish[0].c;
const oppCol = V.GetOppCol(c);
if (this.kingPos[oppCol][0] < 0)
const color = move.vanish[0].c;
const pawnShift = color == "w" ? -1 : 1;
const startRank = color == "w" ? V.size.x - 2 : 1;
- const lastRank = color == "w" ? 0 : V.size.x - 1;
return (
- // The queen is discovered if she reaches the 8th rank,
- // even if this would be a technically valid pawn move.
- move.end.x != lastRank &&
(
+ move.end.x - move.start.x == pawnShift &&
(
- move.end.x - move.start.x == pawnShift &&
(
- (
- // Normal move
- move.end.y == move.start.y &&
- this.board[move.end.x][move.end.y] == V.EMPTY
- )
- ||
- (
- // Capture
- Math.abs(move.end.y - move.start.y) == 1 &&
- this.board[move.end.x][move.end.y] != V.EMPTY
- )
+ // Normal move
+ move.end.y == move.start.y &&
+ this.board[move.end.x][move.end.y] == V.EMPTY
+ )
+ ||
+ (
+ // Capture
+ Math.abs(move.end.y - move.start.y) == 1 &&
+ this.board[move.end.x][move.end.y] != V.EMPTY
)
)
- ||
- (
- // Two-spaces initial jump
- move.start.x == startRank &&
- move.end.y == move.start.y &&
- move.end.x - move.start.x == 2 * pawnShift &&
- this.board[move.end.x][move.end.y] == V.EMPTY
- )
+ )
+ ||
+ (
+ // Two-spaces initial jump
+ move.start.x == startRank &&
+ move.end.y == move.start.y &&
+ move.end.x - move.start.x == 2 * pawnShift &&
+ this.board[move.end.x][move.end.y] == V.EMPTY
)
);
}
? piece == V.PAWN
? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
: [V.QUEEN] //hidden queen revealed
- : piece;
+ : [piece]; //V.PAWN
if (this.board[x + shiftX][y] == V.EMPTY) {
// One square forward
for (let p of finalPieces) {
}
}
- if (V.HasEnpassant) {
- // En passant
- const Lep = this.epSquares.length;
- const epSquare = this.epSquares[Lep - 1]; //always at least one element
- if (
- !!epSquare &&
- epSquare.x == x + shiftX &&
- Math.abs(epSquare.y - y) == 1
- ) {
- let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
- enpassantMove.vanish.push({
- x: x,
- y: epSquare.y,
- p: "p",
- c: this.getColor(x, epSquare.y)
- });
- moves.push(enpassantMove);
- }
+ // En passant
+ const Lep = this.epSquares.length;
+ const epSquare = this.epSquares[Lep - 1]; //always at least one element
+ if (
+ !!epSquare &&
+ epSquare.x == x + shiftX &&
+ Math.abs(epSquare.y - y) == 1
+ ) {
+ let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+ enpassantMove.vanish.push({
+ x: x,
+ y: epSquare.y,
+ p: "p",
+ c: this.getColor(x, epSquare.y)
+ });
+ moves.push(enpassantMove);
}
return moves;
return fen;
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
// We took opponent king
this.kingPos[this.turn] = [-1, -1];
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
- const oppCol = V.GetOppCol(c);
+ preUndo(move) {
+ super.preUndo(move);
+ const oppCol = this.turn;
if (this.kingPos[oppCol][0] < 0)
- // Last move took opponent's king:
+ // Move takes opponent's king:
this.kingPos[oppCol] = [move.vanish[1].x, move.vanish[1].y];
}
return [];
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
const c = move.vanish[0].c;
if (move.vanish.length >= 2 && move.vanish[1].p == V.KING) {
// We took opponent king !
const oppCol = V.GetOppCol(c);
this.kingPos[oppCol] = [-1, -1];
- this.castleFlags[oppCol] = [false, false];
+ this.castleFlags[oppCol] = [8, 8];
}
// Did we magnetically move our (init) rooks or opponents' ones ?
const firstRank = c == "w" ? 7 : 0;
const oppFirstRank = 7 - firstRank;
const oppCol = V.GetOppCol(c);
move.vanish.forEach(psq => {
- if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
- this.castleFlags[c][psq.y == this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
+ if (
+ psq.x == firstRank &&
+ this.castleFlags[c].includes(psq.y)
+ ) {
+ this.castleFlags[c][psq.y == this.castleFlags[c][0] ? 0 : 1] = 8;
+ }
else if (
psq.x == oppFirstRank &&
- this.INIT_COL_ROOK[oppCol].includes(psq.y)
- )
- this.castleFlags[oppCol][
- psq.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1
- ] = false;
+ this.castleFlags[oppCol].includes(psq.y)
+ ) {
+ this.castleFlags[oppCol][psq.y == this.castleFlags[oppCol][0] ? 0 : 1] = 8;
+ }
});
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
const c = move.vanish[0].c;
const oppCol = V.GetOppCol(c);
if (this.kingPos[oppCol][0] < 0) {
if (sq != "-") return V.SquareToCoords(sq);
return undefined;
})];
- this.scanKingsRooks(fen);
+ this.scanKings(fen);
// Extract subTurn from turn indicator: "w" (first move), or
// "w1" or "w2" white subturn 1 or 2, and same for black
const fullTurn = V.ParseFen(fen).turn;
}
this.subTurn = 3 - this.subTurn;
}
- this.updateVariables(move);
+ this.postPlay(move);
+ }
+
+ postPlay(move) {
+ const c = move.turn.charAt(0);
+ const piece = move.vanish[0].p;
+ const firstRank = c == "w" ? V.size.x - 1 : 0;
+
+ if (piece == V.KING && move.appear.length > 0) {
+ this.kingPos[c][0] = move.appear[0].x;
+ this.kingPos[c][1] = move.appear[0].y;
+ if (V.HasCastle) this.castleFlags[c] = [V.size.y, V.size.y];
+ return;
+ }
+ const oppCol = V.GetOppCol(c);
+ const oppFirstRank = V.size.x - 1 - firstRank;
+ if (
+ move.start.x == firstRank && //our rook moves?
+ this.castleFlags[c].includes(move.start.y)
+ ) {
+ const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+ this.castleFlags[c][flagIdx] = V.size.y;
+ } else if (
+ move.end.x == oppFirstRank && //we took opponent rook?
+ this.castleFlags[oppCol].includes(move.end.y)
+ ) {
+ const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+ this.castleFlags[oppCol][flagIdx] = V.size.y;
+ }
}
undo(move) {
}
this.turn = move.turn[0];
this.subTurn = parseInt(move.turn[1]);
- this.unupdateVariables(move);
+ super.postUndo(move);
}
// NOTE: GenRandInitFen() is OK,
return this.getPiece(x2, y2) != V.KING;
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle
- const color = V.GetOppCol(this.turn);
- if (move.vanish.length == 0) {
- this.reserve[color][move.appear[0].p]--;
- return;
- }
- else if (move.vanish.length == 2 && move.vanish[1].c == color) {
+ const color = move.appear[0].c;
+ if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
+ else if (move.vanish.length == 2 && move.vanish[1].c == color)
// Self-capture
this.reserve[color][move.vanish[1].p]++;
- }
}
- unupdateVariables(move) {
- super.unupdateVariables(move);
+ postUndo(move) {
+ super.postUndo(move);
if (move.vanish.length == 2 && move.appear.length == 2) return;
const color = this.turn;
- if (move.vanish.length == 0) {
- this.reserve[color][move.appear[0].p]++;
- return;
- }
- else if (move.vanish.length == 2 && move.vanish[1].c == color) {
+ if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
+ else if (move.vanish.length == 2 && move.vanish[1].c == color)
this.reserve[color][move.vanish[1].p]--;
- }
}
static get SEARCH_DEPTH() {
}
static GenRandInitFen(randomness) {
- return ChessRules.GenRandInitFen(randomness).replace("w 1111 -", "w");
+ // Remove castle flags and en-passant indication
+ return ChessRules.GenRandInitFen(randomness).slice(0, -7);
}
getPotentialPawnMoves([x, y]) {
import { ChessRules, PiPo, Move } from "@/base_rules";
export const VariantRules = class SuctionRules extends ChessRules {
+ static get HasFlags() {
+ return false;
+ }
+
setOtherVariables(fen) {
+
+console.log(fen);
+
super.setOtherVariables(fen);
- // Local stack of captures
+ // Local stack of "captures"
this.cmoves = [];
- const cmove = fen.split(" ")[5];
+ const cmove = V.ParseFen(fen).cmove;
if (cmove == "-") this.cmoves.push(null);
else {
this.cmoves.push({
}
}
+ static ParseFen(fen) {
+ return Object.assign({}, ChessRules.ParseFen(fen), {
+ cmove: fen.split(" ")[4]
+ });
+ }
+
static IsGoodFen(fen) {
if (!ChessRules.IsGoodFen(fen)) return false;
const fenParts = fen.split(" ");
});
}
- updateVariables(move) {
- super.updateVariables(move);
- if (move.vanish.length == 2) {
- // Was opponent king swapped?
- if (move.vanish[1].p == V.KING)
- this.kingPos[this.turn] = [move.appear[1].x, move.appear[1].y];
- }
- }
-
- unupdateVariables(move) {
- super.unupdateVariables(move);
- if (move.appear.length == 2) {
- // Was opponent king swapped?
- if (move.appear[1].p == V.KING)
- this.kingPos[move.vanish[1].c] = [move.vanish[1].x,move.vanish[1].y];
- }
- }
-
static GenRandInitFen(randomness) {
// Add empty cmove:
- return ChessRules.GenRandInitFen(randomness) + " -";
+ return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "- -";
}
getFen() {
return super.getFen() + " " + cmoveFen;
}
- play(move) {
+ postPlay(move) {
+ super.postPlay(move);
+ if (move.vanish.length == 2) {
+ // Was opponent king swapped?
+ if (move.vanish[1].p == V.KING)
+ this.kingPos[this.turn] = [move.appear[1].x, move.appear[1].y];
+ }
this.cmoves.push(this.getCmove(move));
- super.play(move);
}
- undo(move) {
+ postUndo(move) {
+ super.postUndo(move);
+ if (move.appear.length == 2) {
+ if (move.appear[1].p == V.KING)
+ this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
+ }
this.cmoves.pop();
- super.undo(move);
}
atLeastOneMove() {
}
// No variables update because no royal king + no castling
- updateVariables() {}
- unupdateVariables() {}
+ prePlay() {}
+ postPlay() {}
+ preUndo() {}
+ postUndo() {}
getCurrentScore() {
if (this.atLeastOneMove()) return "*";
return b;
}
- updateVariables(move) {
- super.updateVariables(move);
+ postPlay(move) {
+ super.postPlay(move);
// Does this move give check?
const oppCol = this.turn;
if (this.underCheck(oppCol))
}
static GenRandInitFen(randomness) {
- return ChessRules.GenRandInitFen(randomness)
- // Add check flags (at 0)
- .replace(" w 0 1111", " w 0 111100");
+ // Add check flags (at 0)
+ return ChessRules.GenRandInitFen(randomness).slice(0, -2) + "00";
}
getFlagsFen() {
let fen = super.getFlagsFen();
// Add check flags
- for (let c of ["w", "b"])
- fen += this.checkFlags[c];
+ for (let c of ["w", "b"]) fen += this.checkFlags[c];
return fen;
}
static GenRandInitFen(randomness) {
if (!randomness) randomness = 2;
if (randomness == 0)
- return "rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w 0 1111 -";
+ return "rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w 0 akak -";
let pieces = { w: new Array(10), b: new Array(10) };
+ let flags = "";
for (let c of ["w", "b"]) {
if (c == 'b' && randomness == 1) {
pieces['b'] = pieces['w'];
+ flags += flags;
break;
}
pieces[c][bishop2Pos] = "b";
pieces[c][knight2Pos] = "n";
pieces[c][rook2Pos] = "r";
+ flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
}
return (
pieces["b"].join("") +
"/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " w 0 1111 -"
+ " w 0 " + flags + " -"
);
}
};
return this.turn == "w" ? "0-1" : "1-0";
}
+ static get SEARCH_DEPTH() {
+ return 2;
+ }
+
evalPosition() {
let evaluation = 0;
for (let i = 0; i < V.size.x; i++) {
},
adjustFenSize: function() {
let fenInput = document.getElementById("fen");
- fenInput.style.width = this.curFen.length + "ch";
+ fenInput.style.width = (this.curFen.length+1) + "ch";
},
tryGotoFen: function() {
- if (V.IsGoodFen(this.curFen))
- {
+ if (V.IsGoodFen(this.curFen)) {
this.gameRef.fen = this.curFen;
this.loadGame();
}
if (game.score == "*") {
// Set clocks + initime
game.initime = [0, 0];
- if (L >= 1) {
- const gameLastupdate = game.moves[L-1].played;
- game.initime[L % 2] = gameLastupdate;
- if (L >= 2) {
- game.clocks[L % 2] =
- tc.mainTime - (Date.now() - gameLastupdate) / 1000;
- }
- }
+ if (L >= 1) game.initime[L % 2] = game.moves[L-1].played;
+ // NOTE: game.clocks shouldn't be computed right now:
+ // job will be done in re_setClocks() called soon below.
}
// Sort chat messages from newest to oldest
game.chats.sort((c1, c2) => {