-// TODO: debug, and forbid self-capture of king.
-
import { ChessRules } from "@/base_rules";
import { randInt } from "@/utils/alea";
export class SynchroneRules extends ChessRules {
static get CanAnalyze() {
- return true; //false;
+ return false;
}
static get ShowMoves() {
// 5) Check whiteMove
if (
(
- fenParsed.turn == "w" &&
+ fenParsed.turn == "b" &&
// NOTE: do not check really JSON stringified move...
(!fenParsed.whiteMove || fenParsed.whiteMove == "-")
)
||
- (fenParsed.turn == "b" && fenParsed.whiteMove != "-")
+ (fenParsed.turn == "w" && fenParsed.whiteMove != "-")
) {
return false;
}
});
}
- // NOTE: lazy unefficient implementation (for now. TODO?)
getPossibleMovesFrom([x, y]) {
- const moves = this.getAllValidMoves();
- return moves.filter(m => {
- return m.start.x == x && m.start.y == y;
- });
+ let moves = this.filterValid(super.getPotentialMovesFrom([x, y]));
+ if (!this.underCheck(this.getColor(x, y)))
+ // Augment with potential recaptures, except if we are under check
+ Array.prototype.push.apply(moves, this.getRecaptures([x, y]));
+ return moves;
}
- getCaptures(x, y) {
- const color = this.turn;
+ // Aux function used to find opponent and self captures
+ getCaptures(from, to, color) {
const sliderAttack = (xx, yy, allowedSteps) => {
- const deltaX = xx - x,
+ const deltaX = xx - to[0],
absDeltaX = Math.abs(deltaX);
- const deltaY = yy - y,
+ const deltaY = yy - to[1],
absDeltaY = Math.abs(deltaY);
const step = [ deltaX / absDeltaX || 0, deltaY / absDeltaY || 0 ];
if (
) {
return null;
}
- let sq = [ x + step[0], y + step[1] ];
+ let sq = [ to[0] + step[0], to[1] + step[1] ];
while (sq[0] != xx || sq[1] != yy) {
// NOTE: no need to check OnBoard in this special case
if (this.board[sq[0]][sq[1]] != V.EMPTY) return null;
sq[0] += step[0];
sq[1] += step[1];
}
- return this.getBasicMove([xx, yy], [x, y]);
+ return this.getBasicMove([xx, yy], [to[0], to[1]]);
};
- // Can I take on the square [x, y] ?
+ // Can I take on the square 'to' ?
// If yes, return the (list of) capturing move(s)
+ const getTargetedCaptures = ([i, j]) => {
+ let move = null;
+ // From [i, j]:
+ switch (this.getPiece(i, j)) {
+ case V.PAWN: {
+ // Pushed pawns move as enemy pawns
+ const shift = (color == 'w' ? 1 : -1);
+ if (to[0] + shift == i && Math.abs(to[1] - j) == 1)
+ move = this.getBasicMove([i, j], to);
+ break;
+ }
+ case V.KNIGHT: {
+ const deltaX = Math.abs(i - to[0]);
+ const deltaY = Math.abs(j - to[1]);
+ if (
+ deltaX + deltaY == 3 &&
+ [1, 2].includes(deltaX) &&
+ [1, 2].includes(deltaY)
+ ) {
+ move = this.getBasicMove([i, j], to);
+ }
+ break;
+ }
+ case V.KING:
+ if (Math.abs(i - to[0]) <= 1 && Math.abs(j - to[1]) <= 1)
+ move = this.getBasicMove([i, j], to);
+ break;
+ case V.ROOK: {
+ move = sliderAttack(i, j, V.steps[V.ROOK]);
+ break;
+ }
+ case V.BISHOP: {
+ move = sliderAttack(i, j, V.steps[V.BISHOP]);
+ break;
+ }
+ case V.QUEEN: {
+ move = sliderAttack(i, j, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+ break;
+ }
+ }
+ return move;
+ };
let moves = [];
- for (let i=0; i<8; i++) {
- for (let j=0; j<8; j++) {
- if (this.getColor(i, j) == color) {
- switch (this.getPiece(i, j)) {
- case V.PAWN: {
- // Pushed pawns move as enemy pawns
- const shift = (color == 'w' ? 1 : -1);
- if (x + shift == i && Math.abs(y - j) == 1)
- moves.push(this.getBasicMove([i, j], [x, y]));
- break;
- }
- case V.KNIGHT: {
- const deltaX = Math.abs(i - x);
- const deltaY = Math.abs(j - y);
- if (
- deltaX + deltaY == 3 &&
- [1, 2].includes(deltaX) &&
- [1, 2].includes(deltaY)
- ) {
- moves.push(this.getBasicMove([i, j], [x, y]));
- }
- break;
- }
- case V.KING:
- if (Math.abs(i - x) <= 1 && Math.abs(j - y) <= 1)
- moves.push(this.getBasicMove([i, j], [x, y]));
- break;
- case V.ROOK: {
- const mv = sliderAttack(i, j, V.steps[V.ROOK]);
- if (!!mv) moves.push(mv);
- break;
- }
- case V.BISHOP: {
- const mv = sliderAttack(i, j, V.steps[V.BISHOP]);
- if (!!mv) moves.push(mv);
- break;
- }
- case V.QUEEN: {
- const mv = sliderAttack(
- i, j, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
- if (!!mv) moves.push(mv);
- break;
- }
+ if (!!from) {
+ const theMove = getTargetedCaptures(from);
+ if (!!theMove) moves.push(theMove);
+ }
+ else {
+ for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ if (this.getColor(i, j) == color) {
+ const newMove = getTargetedCaptures([i, j]);
+ if (!!newMove) moves.push(newMove);
}
}
}
return this.filterValid(moves);
}
- getAllValidMoves() {
+ getRecaptures(from) {
+ // 1) Generate all opponent's capturing moves
+ let oppCaptureMoves = [];
const color = this.turn;
- // 0) Generate our possible moves
- let myMoves = super.getAllValidMoves();
+ const oppCol = V.GetOppCol(color);
+ for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ if (
+ this.getColor(i, j) == color &&
+ // Do not consider king captures: self-captures of king are forbidden
+ this.getPiece(i, j) != V.KING
+ ) {
+ Array.prototype.push.apply(
+ oppCaptureMoves,
+ this.getCaptures(null, [i, j], oppCol)
+ );
+ }
+ }
+ }
+ // 2) Play each opponent's capture, and see if back-captures are possible:
// Lookup table to quickly decide if a move is already in list:
let moveSet = {};
- const getMoveHash = (move) => {
- return (
- "m" + move.start.x + move.start.y +
- move.end.x + move.end.y +
- // Also use m.appear[0].p for pawn promotions
- move.appear[0].p
- );
- };
- myMoves.forEach(m => moveSet[getMoveHash(m)] = true);
- // 1) Generate all opponent's moves
- this.turn = V.GetOppCol(color);
- const oppMoves = super.getAllValidMoves();
- this.turn = color;
- // 2) Play each opponent's move, and see if captures are possible:
- // --> capturing moving unit only (otherwise some issues)
- oppMoves.forEach(m => {
- V.PlayOnBoard(this.board, m);
- // Can I take on [m.end.x, m.end.y] ?
- // If yes and not already in list, add it (without the capturing part)
- let capturingMoves = this.getCaptures(m.end.x, m.end.y);
- capturingMoves.forEach(cm => {
- const cmHash = getMoveHash(cm);
- if (!moveSet[cmHash]) {
- // The captured unit hasn't moved yet, so temporarily cancel capture
- cm.vanish.pop();
- // If m is itself a capturing move: then replace by self-capture
- if (m.vanish.length == 2) cm.vanish.push(m.vanish[1]);
- myMoves.push(cm);
- moveSet[cmHash] = true;
- }
- });
- V.UndoOnBoard(this.board, m);
+ let moves = [];
+ oppCaptureMoves.forEach(m => {
+ // If another opponent capture with same endpoint already processed, skip
+ const mHash = "m" + m.end.x + m.end.y;
+ if (!moveSet[mHash]) {
+ moveSet[mHash] = true;
+ // Just make enemy piece disappear, to clear potential path:
+ const justDisappear = {
+ appear: [],
+ vanish: [m.vanish[0]]
+ };
+ V.PlayOnBoard(this.board, justDisappear);
+ // Can I take on [m.end.x, m.end.y] ? If yes, add to list:
+ this.getCaptures(from, [m.end.x, m.end.y], color)
+ .forEach(cm => moves.push(cm));
+ V.UndoOnBoard(this.board, justDisappear);
+ }
});
- return myMoves;
+ return moves;
+ }
+
+ getAllValidMoves() {
+ // Return possible moves + potential recaptures
+ return super.getAllValidMoves().concat(this.getRecaptures());
}
filterValid(moves) {
smove.appear.push(m1.appear[0]);
smove.appear.push(m2.appear[0]);
// "Captured" pieces may have moved:
- if (
+ if (m1.appear.length == 2) {
+ // Castle
+ smove.appear.push(m1.appear[1]);
+ smove.vanish.push(m1.vanish[1]);
+ } else if (
m1.vanish.length == 2 &&
(
- m2.end.x != m1.vanish[1].x ||
- m2.end.y != m1.vanish[1].y
+ m1.vanish[1].x != m2.start.x ||
+ m1.vanish[1].y != m2.start.y
)
) {
smove.vanish.push(m1.vanish[1]);
}
- if (
+ if (m2.appear.length == 2) {
+ // Castle
+ smove.appear.push(m2.appear[1]);
+ smove.vanish.push(m2.vanish[1]);
+ } else if (
m2.vanish.length == 2 &&
(
- m1.end.x != m2.vanish[1].x ||
- m1.end.y != m2.vanish[1].y
+ m2.vanish[1].x != m1.start.x ||
+ m2.vanish[1].y != m1.start.y
)
) {
smove.vanish.push(m2.vanish[1]);
} else {
// One move is a self-capture and the other a normal capture:
// only the self-capture appears
- console.log(m1);
- console.log(m2);
const selfCaptureMove =
m1.vanish[1].c == m1.vanish[0].c
? m1
p: selfCaptureMove.appear[0].p,
c: selfCaptureMove.vanish[0].c
});
+ smove.vanish.push({
+ x: m1.end.x,
+ y: m1.end.y,
+ p: selfCaptureMove.vanish[1].p,
+ c: selfCaptureMove.vanish[0].c
+ });
}
}
return smove;
// A full turn just ended:
const smove = this.resolveSynchroneMove(move);
V.PlayOnBoard(this.board, smove);
+ move.whiteMove = this.whiteMove; //for undo
this.whiteMove = null;
// Update king position + flags
}
postUndo(move) {
- if (this.turn == 'w')
+ if (this.turn == 'w') {
// Reset king positions: scan board
this.scanKings();
+ // Also reset whiteMove
+ this.whiteMove = null;
+ } else this.whiteMove = move.whiteMove;
}
- getCheckSquares(color) {
- if (color == 'b') return [];
+ getCheckSquares() {
+ const color = this.turn;
+ if (color == 'b') {
+ // kingPos must be reset for appropriate highlighting:
+ var lastMove = JSON.parse(JSON.stringify(this.whiteMove));
+ this.undo(lastMove); //will erase whiteMove, thus saved above
+ }
let res = [];
- if (this.underCheck('w'))
+ if (this.kingPos['w'][0] >= 0 && this.underCheck('w'))
res.push(JSON.parse(JSON.stringify(this.kingPos['w'])));
- if (this.underCheck('b'))
+ if (this.kingPos['b'][0] >= 0 && this.underCheck('b'))
res.push(JSON.parse(JSON.stringify(this.kingPos['b'])));
+ if (color == 'b') this.play(lastMove);
return res;
}
candidates.push(i);
return moves[candidates[randInt(candidates.length)]];
}
+
+ getNotation(move) {
+ if (move.appear.length == 2 && move.appear[0].p == V.KING)
+ // Castle
+ return move.end.y < move.start.y ? "0-0-0" : "0-0";
+ // Basic system: piece + init + dest square
+ return (
+ move.vanish[0].p.toUpperCase() +
+ V.CoordsToSquare(move.start) +
+ V.CoordsToSquare(move.end)
+ );
+ }
};