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 };
}
};
// NOTE: x coords = top to bottom; y = left to right
// (from white player perspective)
export const ChessRules = class ChessRules {
+
//////////////
// MISC UTILS
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) {
return null;
}
+ // In some variants, the player who repeat a position loses
+ 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() {
return null;
// 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
// 2) Check turn
if (!fenParsed.turn || !V.IsGoodTurn(fenParsed.turn)) return false;
// 3) Check moves count
- if (!fenParsed.movesCount || !(parseInt(fenParsed.movesCount) >= 0))
+ if (!fenParsed.movesCount || !(parseInt(fenParsed.movesCount, 10) >= 0))
return false;
// 4) Check flags
if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags)))
if (['K','k'].includes(row[i])) kings[row[i]]++;
if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
else {
- const num = parseInt(row[i]);
- if (isNaN(num)) return false;
+ const num = parseInt(row[i], 10);
+ if (isNaN(num) || num <= 0) return false;
sumElts += num;
}
}
return {
// NOTE: column is always one char => max 26 columns
// row is counted from black side => subtraction
- x: V.size.x - parseInt(sq.substr(1)),
+ x: V.size.x - parseInt(sq.substr(1), 10),
y: sq[0].charCodeAt() - 97
};
}
// 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;
// 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++) {
let j = 0;
for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) {
const character = rows[i][indexInRow];
- const num = parseInt(character);
+ const num = parseInt(character, 10);
// If num is a number, just shift j:
if (!isNaN(num)) j += num;
// Else: something at position i,j
const fenParsed = V.ParseFen(fen);
this.board = V.GetBoard(fenParsed.position);
this.turn = fenParsed.turn;
- this.movesCount = parseInt(fenParsed.movesCount);
+ this.movesCount = parseInt(fenParsed.movesCount, 10);
this.setOtherVariables(fen);
}
// Scan board for kings positions
+ // TODO: should be done from board, no need for the complete FEN
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":
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;
default: {
- const num = parseInt(fenRows[i].charAt(j));
+ const num = parseInt(fenRows[i].charAt(j), 10);
if (!isNaN(num)) k += num - 1;
}
}
// MOVES GENERATION
// All possible moves from selected square
- getPotentialMovesFrom([x, y]) {
- switch (this.getPiece(x, y)) {
- case V.PAWN:
- return this.getPotentialPawnMoves([x, y]);
- case V.ROOK:
- return this.getPotentialRookMoves([x, y]);
- case V.KNIGHT:
- return this.getPotentialKnightMoves([x, y]);
- case V.BISHOP:
- return this.getPotentialBishopMoves([x, y]);
- case V.QUEEN:
- return this.getPotentialQueenMoves([x, y]);
- case V.KING:
- return this.getPotentialKingMoves([x, y]);
+ getPotentialMovesFrom(sq) {
+ switch (this.getPiece(sq[0], sq[1])) {
+ case V.PAWN: return this.getPotentialPawnMoves(sq);
+ case V.ROOK: return this.getPotentialRookMoves(sq);
+ case V.KNIGHT: return this.getPotentialKnightMoves(sq);
+ case V.BISHOP: return this.getPotentialBishopMoves(sq);
+ 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.
// tr: transformation
getBasicMove([sx, sy], [ex, ey], tr) {
const initColor = this.getColor(sx, sy);
- const initPiece = this.getPiece(sx, sy);
+ const initPiece = this.board[sx][sy].charAt(1);
let mv = new Move({
appear: [
new PiPo({
x: ex,
y: ey,
- c: tr ? tr.c : initColor,
- p: tr ? tr.p : initPiece
+ c: !!tr ? tr.c : initColor,
+ p: !!tr ? tr.p : initPiece
})
],
vanish: [
x: ex,
y: ey,
c: this.getColor(ex, ey),
- p: this.getPiece(ex, ey)
+ p: this.board[ex][ey].charAt(1)
})
);
}
let j = y + step[1];
while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
moves.push(this.getBasicMove([x, y], [i, j]));
- if (oneStep) continue outerLoop;
+ if (!!oneStep) continue outerLoop;
i += step[0];
j += step[1];
}
enpassantMove.vanish.push({
x: x,
y: epSquare.y,
- // Captured piece is usually a pawn, but next line seems harmless
- p: this.getPiece(x, epSquare.y),
+ p: this.board[x][epSquare.y].charAt(1),
c: this.getColor(x, epSquare.y)
});
}
V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
"oneStep"
);
- if (V.HasCastle) moves = moves.concat(this.getCastleMoves(sq));
+ if (V.HasCastle && this.castleFlags[this.turn].some(v => v < V.size.y))
+ moves = moves.concat(this.getCastleMoves(sq));
return moves;
}
// "castleInCheck" arg to let some variants castle under check
- getCastleMoves([x, y], castleInCheck) {
+ getCastleMoves([x, y], finalSquares, castleInCheck, castleWith) {
const c = this.getColor(x, y);
- if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
- return []; //x isn't first rank, or king has moved (shortcut)
// Castling ?
const oppCol = V.GetOppCol(c);
let moves = [];
- let i = 0;
// King, then rook:
- const finalSquares = [
- [2, 3],
- [V.size.y - 2, V.size.y - 3]
- ];
+ finalSquares = finalSquares || [ [2, 3], [V.size.y - 2, V.size.y - 3] ];
+ const castlingKing = this.board[x][y].charAt(1);
castlingCheck: for (
let castleSide = 0;
castleSide < 2;
// NOTE: in some variants this is not a rook
const rookPos = this.castleFlags[c][castleSide];
- if (this.board[x][rookPos] == V.EMPTY || this.getColor(x, rookPos) != c)
+ const castlingPiece = this.board[x][rookPos].charAt(1);
+ if (
+ this.board[x][rookPos] == V.EMPTY ||
+ this.getColor(x, rookPos) != c ||
+ (!!castleWith && !castleWith.includes(castlingPiece))
+ ) {
// Rook is not here, or changed color (see Benedict)
continue;
+ }
// Nothing on the path of the king ? (and no checks)
- const castlingPiece = this.getPiece(x, rookPos);
const finDist = finalSquares[castleSide][0] - y;
let step = finDist / Math.max(1, Math.abs(finDist));
- i = y;
+ let i = y;
do {
if (
- // NOTE: "castling" arg is used by some variants (Monster),
- // where "isAttacked" is overloaded in an infinite-recursive way.
- // TODO: not used anymore (Monster + Doublemove2 are simplified).
- (!castleInCheck && this.isAttacked([x, i], oppCol, "castling")) ||
- (this.board[x][i] != V.EMPTY &&
+ (!castleInCheck && this.isAttacked([x, i], oppCol)) ||
+ (
+ this.board[x][i] != V.EMPTY &&
// NOTE: next check is enough, because of chessboard constraints
- (this.getColor(x, i) != c ||
- ![V.KING, castlingPiece].includes(this.getPiece(x, i))))
+ (this.getColor(x, i) != c || ![y, rookPos].includes(i))
+ )
) {
continue castlingCheck;
}
finalSquares[castleSide][i] != rookPos &&
this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
(
- this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
+ finalSquares[castleSide][i] != y ||
this.getColor(x, finalSquares[castleSide][i]) != c
)
) {
new PiPo({
x: x,
y: finalSquares[castleSide][0],
- p: V.KING,
+ p: castlingKing,
c: c
}),
new PiPo({
})
],
vanish: [
- new PiPo({ x: x, y: y, p: V.KING, c: c }),
+ // King might be initially disguised (Titan...)
+ new PiPo({ x: x, y: y, p: castlingKing, c: c }),
new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
],
end:
// 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++) {
}
if (
V.OnBoard(rx, ry) &&
+ this.board[rx][ry] != V.EMPTY &&
this.getPiece(rx, ry) == piece &&
this.getColor(rx, ry) == color
) {
this.postPlay(move);
}
- updateCastleFlags(move, piece) {
- const c = V.GetOppCol(this.turn);
+ 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
const oppCol = this.turn;
)
);
}
+
};