constructor(o) {
this.appear = o.appear;
this.vanish = o.vanish;
- this.start = o.start ? o.start : { x: o.vanish[0].x, y: o.vanish[0].y };
- this.end = o.end ? o.end : { x: o.appear[0].x, y: o.appear[0].y };
+ this.start = o.start || { x: o.vanish[0].x, y: o.vanish[0].y };
+ this.end = o.end || { x: o.appear[0].x, y: o.appear[0].y };
}
};
//////////////
// MISC UTILS
+ static get Options() {
+ return {
+ select: [
+ {
+ label: "Randomness",
+ variable: "randomness",
+ defaut: 0,
+ options: [
+ { label: "Deterministic", value: 0 },
+ { label: "Symmetric random", value: 1 },
+ { label: "Asymmetric random", value: 2 }
+ ]
+ }
+ ]
+ };
+ }
+
+ static AbbreviateOptions(opts) {
+ return "";
+ // Randomness is a special option: (TODO?)
+ //return "R" + opts.randomness;
+ }
+
+ static IsValidOptions(opts) {
+ return true;
+ }
+
// Some variants don't have flags:
static get HasFlags() {
return true;
return V.CanFlip;
}
+ // NOTE: these will disappear once each variant has its dedicated SVG board.
// For (generally old) variants without checkered board
static get Monochrome() {
return false;
}
-
// Some games are drawn unusually (bottom right corner is black)
static get DarkBottomRight() {
return false;
}
-
// Some variants require lines drawing
static get Lines() {
if (V.Monochrome) {
static get LoseOnRepetition() {
return false;
}
+ // And in some others (Iceage), repetitions should be ignored:
+ static get IgnoreRepetition() {
+ return false;
+ }
+ loseOnRepetition() {
+ // In some variants, result depends on the position:
+ return V.LoseOnRepetition;
+ }
+
+ // At some stages, some games could wait clicks only:
+ onlyClick() {
+ return false;
+ }
// Some variants use click infos:
doClick() {
// Turn "p" into "bp" (for board)
static fen2board(f) {
- return f.charCodeAt() <= 90 ? "w" + f.toLowerCase() : "b" + f;
+ return f.charCodeAt(0) <= 90 ? "w" + f.toLowerCase() : "b" + f;
}
// Check if FEN describes a board situation correctly
// En-passant square, if any
getEpSquare(moveOrSquare) {
- if (!moveOrSquare) return undefined;
+ if (!moveOrSquare) return undefined; //TODO: necessary line?!
if (typeof moveOrSquare === "string") {
const square = moveOrSquare;
if (square == "-") return undefined;
// FEN UTILS
// Setup the initial random (asymmetric) position
- static GenRandInitFen(randomness) {
- if (randomness == 0)
+ static GenRandInitFen(options) {
+ if (!options.randomness || options.randomness == 0)
// Deterministic:
return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -";
let flags = "";
// Shuffle pieces on first (and last rank if randomness == 2)
for (let c of ["w", "b"]) {
- if (c == 'b' && randomness == 1) {
+ if (c == 'b' && options.randomness == 1) {
pieces['b'] = pieces['w'];
flags += flags;
break;
// if more than 9 consecutive free spaces, break the integer,
// otherwise FEN parsing will fail.
if (count <= 9) return count;
- // Currently only boards of size up to 11 or 12:
- return "9" + (count - 9);
+ // Most boards of size < 18:
+ if (count <= 18) return "9" + (count - 9);
+ // Except Gomoku:
+ return "99" + (count - 18);
};
let position = "";
for (let i = 0; i < V.size.x; i++) {
}
// Scan board for kings positions
+ // TODO: should be done from board, no need for the complete FEN
scanKings(fen) {
// 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++) {
case V.QUEEN: return this.getPotentialQueenMoves(sq);
case V.KING: return this.getPotentialKingMoves(sq);
}
- return []; //never reached
+ return []; //never reached (but some variants may use it: Bario...)
}
// Build a regular move from its initial and destination squares.
// Generic method to find possible moves of non-pawn pieces:
// "sliding or jumping"
- getSlideNJumpMoves([x, y], steps, oneStep) {
+ getSlideNJumpMoves([x, y], steps, nbSteps) {
let moves = [];
outerLoop: for (let step of steps) {
let i = x + step[0];
let j = y + step[1];
+ let stepCounter = 0;
while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
moves.push(this.getBasicMove([x, y], [i, j]));
- if (oneStep) continue outerLoop;
+ if (nbSteps && ++stepCounter >= nbSteps) continue outerLoop;
i += step[0];
j += step[1];
}
if (!!promotions) finalPieces = promotions;
else if (!!V.PawnSpecs.promotions) finalPieces = V.PawnSpecs.promotions;
}
- let tr = null;
for (let piece of finalPieces) {
- tr = (piece != V.PAWN ? { c: color, p: piece } : null);
+ const tr = (piece != V.PAWN ? { c: color, p: piece } : null);
moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
}
}
// What are the knight moves from square x,y ?
getPotentialKnightMoves(sq) {
- return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
+ return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1);
}
// What are the bishop moves from square x,y ?
// What are the queen moves from square x,y ?
getPotentialQueenMoves(sq) {
return this.getSlideNJumpMoves(
- sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP])
- );
+ sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
}
// What are the king moves from square x,y ?
getPotentialKingMoves(sq) {
// Initialize with normal moves
let moves = this.getSlideNJumpMoves(
- sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
- "oneStep"
- );
+ sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
if (V.HasCastle && this.castleFlags[this.turn].some(v => v < V.size.y))
moves = moves.concat(this.getCastleMoves(sq));
return moves;
// Castling ?
const oppCol = V.GetOppCol(c);
let moves = [];
- let i = 0;
// King, then rook:
finalSquares = finalSquares || [ [2, 3], [V.size.y - 2, V.size.y - 3] ];
const castlingKing = this.board[x][y].charAt(1);
// Nothing on the path of the king ? (and no checks)
const finDist = finalSquares[castleSide][0] - y;
let step = finDist / Math.max(1, Math.abs(finDist));
- i = y;
+ let i = y;
do {
if (
(!castleInCheck && this.isAttacked([x, i], oppCol)) ||
// Stop at the first move found
// TODO: not really, it explores all moves from a square (one is enough).
+ // Possible fix: add extra arg "oneMove" to getPotentialMovesFrom,
+ // and then return only boolean true at first move found
+ // (in all getPotentialXXXMoves() ... for all variants ...)
atLeastOneMove() {
const color = this.turn;
for (let i = 0; i < V.size.x; i++) {
// Generic method for non-pawn pieces ("sliding or jumping"):
// is x,y attacked by a piece of given color ?
- isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
+ isAttackedBySlideNJump([x, y], color, piece, steps, nbSteps) {
for (let step of steps) {
let rx = x + step[0],
ry = y + step[1];
- while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+ let stepCounter = 1;
+ while (
+ V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY &&
+ (!nbSteps || stepCounter < nbSteps)
+ ) {
rx += step[0];
ry += step[1];
+ stepCounter++;
}
if (
V.OnBoard(rx, ry) &&
+ this.board[rx][ry] != V.EMPTY &&
this.getPiece(rx, ry) == piece &&
this.getColor(rx, ry) == color
) {
}
updateCastleFlags(move, piece, color) {
+ // TODO: check flags. If already off, no need to always re-evaluate
const c = color || V.GetOppCol(this.turn);
const firstRank = (c == "w" ? V.size.x - 1 : 0);
// Update castling flags if rooks are moved