import { randInt } from "@/utils/alea";
import { ChessRules, PiPo, Move } from "@/base_rules";
-export const VariantRules = class EightpiecesRules extends ChessRules {
+export class EightpiecesRules extends ChessRules {
static get JAILER() {
return "j";
}
return "l";
}
+ static get IMAGE_EXTENSION() {
+ // Temporarily, for the time SVG pieces are being designed:
+ return ".png";
+ }
+
// Lancer directions *from white perspective*
static get LANCER_DIRS() {
return {
}
getPpath(b, color, score, orientation) {
- if ([V.JAILER, V.SENTRY].includes(b[1])) return "Eightpieces/" + b;
+ if ([V.JAILER, V.SENTRY].includes(b[1])) return "Eightpieces/tmp_png/" + b;
if (Object.keys(V.LANCER_DIRS).includes(b[1])) {
- if (orientation == 'w') return "Eightpieces/" + b;
+ if (orientation == 'w') return "Eightpieces/tmp_png/" + b;
// Find opposite direction for adequate display:
let oppDir = '';
switch (b[1]) {
oppDir = 'f';
break;
}
- return "Eightpieces/" + b[0] + oppDir;
+ return "Eightpieces/tmp_png/" + b[0] + oppDir;
}
- return b;
+ // TODO: after we have SVG pieces, remove the folder and next prefix:
+ return "Eightpieces/tmp_png/" + b;
}
- getPPpath(b, orientation) {
- return this.getPpath(b, null, null, orientation);
+ getPPpath(m, orientation) {
+ return (
+ this.getPpath(
+ m.appear[0].c + m.appear[0].p,
+ null,
+ null,
+ orientation
+ )
+ );
}
static ParseFen(fen) {
// 5) Check sentry push (if any)
if (
fenParsed.sentrypush != "-" &&
- !fenParsed.sentrypush.match(/^([a-h][1-8],?)+$/)
+ !fenParsed.sentrypush.match(/^([a-h][1-8]){2,2}$/)
) {
return false;
}
const L = this.sentryPush.length;
if (!this.sentryPush[L-1]) return "-";
let res = "";
- this.sentryPush[L-1].forEach(coords =>
- res += V.CoordsToSquare(coords) + ",");
- return res.slice(0, -1);
+ const spL = this.sentryPush[L-1].length;
+ // Condensate path: just need initial and final squares:
+ return [0, spL - 1]
+ .map(i => V.CoordsToSquare(this.sentryPush[L-1][i]))
+ .join("");
}
setOtherVariables(fen) {
const parsedFen = V.ParseFen(fen);
if (parsedFen.sentrypush == "-") this.sentryPush = [null];
else {
- this.sentryPush = [
- parsedFen.sentrypush.split(",").map(sq => {
- return V.SquareToCoords(sq);
- })
- ];
+ // Expand init + dest squares into a full path:
+ const init = V.SquareToCoords(parsedFen.sentrypush.substr(0, 2)),
+ dest = V.SquareToCoords(parsedFen.sentrypush.substr(2));
+ let newPath = [init];
+ const delta = ['x', 'y'].map(i => Math.abs(dest[i] - init[i]));
+ // Check that it's not a knight movement:
+ if (delta[0] == 0 || delta[1] == 0 || delta[0] == delta[1]) {
+ const step = ['x', 'y'].map((i, idx) => {
+ return (dest[i] - init[i]) / delta[idx] || 0
+ });
+ let x = init.x + step[0],
+ y = init.y + step[1];
+ while (x != dest.x || y != dest.y) {
+ newPath.push({ x: x, y: y });
+ x += step[0];
+ y += step[1];
+ }
+ }
+ newPath.push(dest);
+ this.sentryPush = [newPath];
}
}
static GenRandInitFen(randomness) {
if (randomness == 0)
// Deterministic:
- return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 ahah - -";
+ return "jfsqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JDSQKBNR w 0 ahah - -";
let pieces = { w: new Array(8), b: new Array(8) };
let flags = "";
if (c == 'b') {
// Check if white sentry is on the same color as ours.
// If yes: swap bishop and sentry positions.
- if ((pieces['w'].indexOf('s') - sentryPos) % 2 == 0)
+ // NOTE: test % 2 == 1 because there are 7 slashes.
+ if ((pieces['w'].indexOf('s') - sentryPos) % 2 == 1)
[bishopPos, sentryPos] = [sentryPos, bishopPos];
}
positions.splice(Math.max(randIndex, randIndex_tmp), 1);
positions.splice(randIndex, 1);
// Rook, jailer and king positions are now almost fixed,
- // only the ordering rook-> jailer or jailer->rook must be decided.
+ // only the ordering rook->jailer or jailer->rook must be decided.
let rookPos = positions[0];
let jailerPos = positions[2];
const kingPos = positions[1];
// Because of the lancers, getPiece() could be wrong:
// use board[x][y][1] instead (always valid).
getBasicMove([sx, sy], [ex, ey], tr) {
+ const initColor = this.getColor(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 : this.getColor(sx, sy),
- p: tr ? tr.p : this.board[sx][sy].charAt(1)
+ c: tr ? tr.c : initColor,
+ p: tr ? tr.p : initPiece
})
],
vanish: [
new PiPo({
x: sx,
y: sy,
- c: this.getColor(sx, sy),
- p: this.board[sx][sy].charAt(1)
+ c: initColor,
+ p: initPiece
})
]
});
canIplay(side, [x, y]) {
return (
- (this.subTurn == 1 && this.turn == side && this.getColor(x, y) == side) ||
+ (this.subTurn == 1 && this.turn == side && this.getColor(x, y) == side)
+ ||
(this.subTurn == 2 && x == this.sentryPos.x && y == this.sentryPos.y)
);
}
getPotentialMovesFrom([x, y]) {
// At subTurn == 2, jailers aren't effective (Jeff K)
+ const piece = this.getPiece(x, y);
+ const L = this.sentryPush.length;
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) {
+ if (piece == V.KING) {
moves.push(
new Move({
appear: [],
})
);
}
+ else if (piece == V.LANCER && !!this.sentryPush[L-1]) {
+ // A pushed lancer next to the jailer: reorient
+ const color = this.getColor(x, y);
+ const curDir = this.board[x][y].charAt(1);
+ Object.keys(V.LANCER_DIRS).forEach(k => {
+ moves.push(
+ new Move({
+ appear: [{ x: x, y: y, c: color, p: k }],
+ vanish: [{ x: x, y: y, c: color, p: curDir }],
+ start: { x: x, y: y },
+ end: { x: jsq[0], y: jsq[1] }
+ })
+ );
+ });
+ }
return moves;
}
}
let moves = [];
- switch (this.getPiece(x, y)) {
+ switch (piece) {
case V.JAILER:
moves = this.getPotentialJailerMoves([x, y]);
break;
moves = super.getPotentialMovesFrom([x, y]);
break;
}
- const L = this.sentryPush.length;
if (!!this.sentryPush[L-1]) {
- // Delete moves walking back on sentry push path
+ // Delete moves walking back on sentry push path,
+ // only if not a pawn, and the piece is the pushed one.
+ const pl = this.sentryPush[L-1].length;
+ const finalPushedSq = this.sentryPush[L-1][pl-1];
moves = moves.filter(m => {
if (
m.vanish[0].p != V.PAWN &&
+ m.start.x == finalPushedSq.x && m.start.y == finalPushedSq.y &&
this.sentryPush[L-1].some(sq => sq.x == m.end.x && sq.y == m.end.y)
) {
return false;
const color = this.getColor(x, y);
let moves = [];
const [sizeX, sizeY] = [V.size.x, V.size.y];
- let shiftX = color == "w" ? -1 : 1;
+ let shiftX = (color == "w" ? -1 : 1);
if (this.subTurn == 2) shiftX *= -1;
+ const firstRank = color == "w" ? sizeX - 1 : 0;
const startRank = color == "w" ? sizeX - 2 : 1;
const lastRank = color == "w" ? 0 : sizeX - 1;
if (!V.OnBoard(x + shiftX, y)) return [];
const finalPieces =
- // No promotions after pushes!
- x + shiftX == lastRank && this.subTurn == 1
- ?
- Object.keys(V.LANCER_DIRS).concat(
+ // A push cannot put a pawn on last rank (it goes backward)
+ x + shiftX == lastRank
+ ? Object.keys(V.LANCER_DIRS).concat(
[V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.SENTRY, V.JAILER])
: [V.PAWN];
if (this.board[x + shiftX][y] == V.EMPTY) {
);
}
if (
- x == startRank &&
+ // 2-squares jumps forbidden if pawn push
+ this.subTurn == 1 &&
+ [startRank, firstRank].includes(x) &&
this.board[x + 2 * shiftX][y] == V.EMPTY
) {
// Two squares jump
}
}
- // En passant:
+ // En passant: only on subTurn == 1
const Lep = this.epSquares.length;
const epSquare = this.epSquares[Lep - 1];
if (
+ this.subTurn == 1 &&
!!epSquare &&
epSquare.x == x + shiftX &&
Math.abs(epSquare.y - y) == 1
// Add all lancer possible orientations, similar to pawn promotions.
// Except if just after a push: allow all movements from init square then
const L = this.sentryPush.length;
+ const color = this.getColor(x, y);
if (!!this.sentryPush[L-1]) {
// Maybe I was pushed
const pl = this.sentryPush[L-1].length;
// I was pushed: allow all directions (for this move only), but
// 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][y].charAt(1)];
Object.values(V.LANCER_DIRS).forEach(step => {
const dirCode = Object.keys(V.LANCER_DIRS).find(k => {
V.OnBoard(x + step[0], y + step[1]) &&
this.board[x + step[0]][y + step[1]] == V.EMPTY
) {
+ const newDirCode = Object.keys(V.LANCER_DIRS).find(k => {
+ const codeStep = V.LANCER_DIRS[k];
+ return (codeStep[0] == step[0] && codeStep[1] == step[1]);
+ });
potentialNudges.push(
this.getBasicMove(
[x, y],
- [x + step[0], y + step[1]]
+ [x + step[0], y + step[1]],
+ { c: color, p: newDirCode }
)
);
}
);
}
- // Adapted: castle with jailer possible
- getCastleMoves([x, y]) {
- const c = this.getColor(x, y);
- const firstRank = (c == "w" ? V.size.x - 1 : 0);
- if (x != firstRank || y != this.INIT_COL_KING[c])
- return [];
-
- const oppCol = V.GetOppCol(c);
- let moves = [];
- let i = 0;
- // King, then rook or jailer:
- const finalSquares = [
- [2, 3],
- [V.size.y - 2, V.size.y - 3]
- ];
- castlingCheck: for (
- let castleSide = 0;
- castleSide < 2;
- castleSide++
- ) {
- 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;
- do {
- if (
- this.isAttacked([x, i], [oppCol]) ||
- (this.board[x][i] != V.EMPTY &&
- (this.getColor(x, i) != c ||
- ![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 = this.castleFlags[c][castleSide];
- for (i = y + step; i != rookOrJailerPos; i += step)
- if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-
- // Nothing on final squares, except maybe king and castling rook or jailer?
- for (i = 0; i < 2; i++) {
- if (
- this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
- this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
- finalSquares[castleSide][i] != rookOrJailerPos
- ) {
- continue castlingCheck;
- }
- }
-
- // If this code is reached, castle is valid
- const castlingPiece = this.getPiece(firstRank, rookOrJailerPos);
- moves.push(
- new Move({
- appear: [
- new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
- new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c })
- ],
- vanish: [
- new PiPo({ x: x, y: y, p: V.KING, c: c }),
- new PiPo({ x: x, y: rookOrJailerPos, p: castlingPiece, c: c })
- ],
- end:
- Math.abs(y - rookOrJailerPos) <= 2
- ? { x: x, y: rookOrJailerPos }
- : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
- })
- );
- }
-
- 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 this.filterValid(this.getPotentialMovesFrom(sentrySq));
}
- 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 };
- 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 ([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({ x: move.end.x, y: move.end.y });
- this.sentryPush.push(squares);
- } else this.sentryPush.push(null);
- }
-
- play(move) {
- if (!this.states) this.states = [];
- const stateFen = this.getFen();
- this.states.push(stateFen);
-
- this.prePlay(move);
- move.flags = JSON.stringify(this.aggregateFlags());
- this.epSquares.push(this.getEpSquare(move));
- V.PlayOnBoard(this.board, move);
- // Is it a sentry push? (useful for undo)
- move.sentryPush = (this.subTurn == 2);
- if (this.subTurn == 1) this.movesCount++;
- if (move.appear.length == 0 && move.vanish.length == 1) this.subTurn = 2;
- else {
- // Turn changes only if not a sentry "pre-push"
- this.turn = V.GetOppCol(this.turn);
- this.subTurn = 1;
- }
- this.postPlay(move);
- }
-
- postPlay(move) {
- if (move.vanish.length == 0 || this.subTurn == 2)
- // Special pass move of the king, or sentry pre-push: nothing to update
- return;
- const c = move.vanish[0].c;
- const piece = move.vanish[0].p;
- const firstRank = c == "w" ? V.size.x - 1 : 0;
-
- if (piece == V.KING) {
- this.kingPos[c][0] = move.appear[0].x;
- this.kingPos[c][1] = move.appear[0].y;
- this.castleFlags[c] = [V.size.y, V.size.y];
- return;
- }
- // 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.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.epSquares.pop();
- this.disaggregateFlags(JSON.parse(move.flags));
- V.UndoOnBoard(this.board, move);
- // Decrement movesCount except if the move is a sentry push
- if (!move.sentryPush) this.movesCount--;
- if (this.subTurn == 2) this.subTurn = 1;
- else {
- this.turn = V.GetOppCol(this.turn);
- if (move.sentryPush) this.subTurn = 2;
- }
- this.postUndo(move);
-
- const stateFen = this.getFen();
- if (stateFen != this.states[this.states.length-1]) debugger;
- this.states.pop();
- }
-
- postUndo(move) {
- super.postUndo(move);
- this.sentryPush.pop();
- }
-
- isAttacked(sq, colors) {
+ isAttacked(sq, color) {
return (
- super.isAttacked(sq, colors) ||
- this.isAttackedByLancer(sq, colors) ||
- this.isAttackedBySentry(sq, colors)
+ super.isAttacked(sq, color) ||
+ this.isAttackedByLancer(sq, color) ||
+ this.isAttackedBySentry(sq, color)
+ // The jailer doesn't capture.
);
}
- isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
+ isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
for (let step of steps) {
let rx = x + step[0],
ry = y + step[1];
}
if (
V.OnBoard(rx, ry) &&
- this.getPiece(rx, ry) === piece &&
- colors.includes(this.getColor(rx, ry)) &&
+ this.getPiece(rx, ry) == piece &&
+ this.getColor(rx, ry) == color &&
!this.isImmobilized([rx, ry])
) {
return true;
return false;
}
- isAttackedByPawn([x, y], colors) {
- for (let c of colors) {
- const pawnShift = c == "w" ? 1 : -1;
- if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
- for (let i of [-1, 1]) {
- if (
- y + i >= 0 &&
- y + i < V.size.y &&
- this.getPiece(x + pawnShift, y + i) == V.PAWN &&
- this.getColor(x + pawnShift, y + i) == c &&
- !this.isImmobilized([x + pawnShift, y + i])
- ) {
- return true;
- }
+ isAttackedByPawn([x, y], color) {
+ const pawnShift = (color == "w" ? 1 : -1);
+ if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
+ for (let i of [-1, 1]) {
+ if (
+ y + i >= 0 &&
+ y + i < V.size.y &&
+ this.getPiece(x + pawnShift, y + i) == V.PAWN &&
+ this.getColor(x + pawnShift, y + i) == color &&
+ !this.isImmobilized([x + pawnShift, y + i])
+ ) {
+ return true;
}
}
}
return false;
}
- isAttackedByLancer([x, y], colors) {
+ isAttackedByLancer([x, y], color) {
for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
// If in this direction there are only enemy pieces and empty squares,
// and we meet a lancer: can he reach us?
V.OnBoard(coord.x, coord.y) &&
(
this.board[coord.x][coord.y] == V.EMPTY ||
- colors.includes(this.getColor(coord.x, coord.y))
+ this.getColor(coord.x, coord.y) == color
)
) {
if (
// Helper to check sentries attacks:
selfAttack([x1, y1], [x2, y2]) {
const color = this.getColor(x1, y1);
+ const oppCol = V.GetOppCol(color);
const sliderAttack = (allowedSteps, lancer) => {
- const deltaX = x2 - x1;
- const deltaY = y2 - y1;
- const step = [ deltaX / Math.abs(deltaX), deltaY / Math.abs(deltaY) ];
- if (allowedSteps.every(st => st[0] != step[0] || st[1] != step[1]))
+ const deltaX = x2 - x1,
+ absDeltaX = Math.abs(deltaX);
+ const deltaY = y2 - y1,
+ absDeltaY = Math.abs(deltaY);
+ const step = [ deltaX / absDeltaX || 0, deltaY / absDeltaY || 0 ];
+ if (
+ // Check that the step is a priori valid:
+ (absDeltaX != absDeltaY && deltaX != 0 && deltaY != 0) ||
+ allowedSteps.every(st => st[0] != step[0] || st[1] != step[1])
+ ) {
return false;
+ }
let sq = [ x1 + step[0], y1 + step[1] ];
- while (sq[0] != x2 && sq[1] != y2) {
+ 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)
+ (!!lancer && this.getColor(sq[0], sq[1]) == oppCol)
) {
return false;
}
case V.QUEEN:
return sliderAttack(V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
case V.LANCER: {
- // Special case: as long as no enemy units stands in-between, it attacks
- // (if it points toward the king).
+ // Special case: as long as no enemy units stands in-between,
+ // it attacks (if it points toward the king).
const allowedStep = V.LANCER_DIRS[this.board[x1][y1].charAt(1)];
return sliderAttack([allowedStep], "lancer");
}
return false;
}
- isAttackedBySentry([x, y], colors) {
+ isAttackedBySentry([x, y], color) {
// Attacked by sentry means it can self-take our king.
// Just check diagonals of enemy sentry(ies), and if it reaches
// one of our pieces: can I self-take?
- const color = V.GetOppCol(colors[0]);
+ const myColor = V.GetOppCol(color);
let candidates = [];
for (let i=0; i<V.size.x; i++) {
for (let j=0; j<V.size.y; j++) {
if (
this.getPiece(i,j) == V.SENTRY &&
- colors.includes(this.getColor(i,j)) &&
+ this.getColor(i,j) == color &&
!this.isImmobilized([i, j])
) {
for (let step of V.steps[V.BISHOP]) {
}
if (
V.OnBoard(sq[0], sq[1]) &&
- this.getColor(sq[0], sq[1]) == color
+ this.getColor(sq[0], sq[1]) == myColor
) {
candidates.push([ sq[0], sq[1] ]);
}
// Jailer doesn't capture or give check
+ 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 };
+ 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 ([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({ x: move.end.x, y: move.end.y });
+ this.sentryPush.push(squares);
+ } else this.sentryPush.push(null);
+ }
+
+ play(move) {
+ this.prePlay(move);
+ move.flags = JSON.stringify(this.aggregateFlags());
+ this.epSquares.push(this.getEpSquare(move));
+ V.PlayOnBoard(this.board, move);
+ // Is it a sentry push? (useful for undo)
+ move.sentryPush = (this.subTurn == 2);
+ if (this.subTurn == 1) this.movesCount++;
+ if (move.appear.length == 0 && move.vanish.length == 1) this.subTurn = 2;
+ else {
+ // Turn changes only if not a sentry "pre-push"
+ this.turn = V.GetOppCol(this.turn);
+ this.subTurn = 1;
+ }
+ this.postPlay(move);
+ }
+
+ postPlay(move) {
+ if (move.vanish.length == 0 || this.subTurn == 2)
+ // Special pass move of the king, or sentry pre-push: nothing to update
+ return;
+ const c = move.vanish[0].c;
+ const piece = move.vanish[0].p;
+ const firstRank = c == "w" ? V.size.x - 1 : 0;
+
+ if (piece == V.KING) {
+ this.kingPos[c][0] = move.appear[0].x;
+ this.kingPos[c][1] = move.appear[0].y;
+ this.castleFlags[c] = [V.size.y, V.size.y];
+ return;
+ }
+ // 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.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.epSquares.pop();
+ this.disaggregateFlags(JSON.parse(move.flags));
+ V.UndoOnBoard(this.board, move);
+ // Decrement movesCount except if the move is a sentry push
+ if (!move.sentryPush) this.movesCount--;
+ if (this.subTurn == 2) this.subTurn = 1;
+ else {
+ this.turn = V.GetOppCol(this.turn);
+ if (move.sentryPush) this.subTurn = 2;
+ }
+ this.postUndo(move);
+ }
+
+ postUndo(move) {
+ super.postUndo(move);
+ this.sentryPush.pop();
+ }
+
static get VALUES() {
return Object.assign(
{ l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations
return (!choice.second ? choice : [choice, choice.second]);
}
+ // For moves notation:
+ static get LANCER_DIRNAMES() {
+ return {
+ 'c': "N",
+ 'd': "NE",
+ 'e': "E",
+ 'f': "SE",
+ 'g': "S",
+ 'h': "SW",
+ 'm': "W",
+ 'o': "NW"
+ };
+ }
+
getNotation(move) {
// Special case "king takes jailer" is a pass move
if (move.appear.length == 0 && move.vanish.length == 0) return "pass";
+ let notation = undefined;
if (this.subTurn == 2) {
// Do not consider appear[1] (sentry) for sentry pushes
const simpleMove = {
start: move.start,
end: move.end
};
- return super.getNotation(simpleMove);
+ notation = super.getNotation(simpleMove);
+ } else notation = super.getNotation(move);
+ if (Object.keys(V.LANCER_DIRNAMES).includes(move.vanish[0].p))
+ // Lancer: add direction info
+ notation += "=" + V.LANCER_DIRNAMES[move.appear[0].p];
+ else if (
+ move.vanish[0].p == V.PAWN &&
+ Object.keys(V.LANCER_DIRNAMES).includes(move.appear[0].p)
+ ) {
+ // Fix promotions in lancer:
+ notation = notation.slice(0, -1) +
+ "L:" + V.LANCER_DIRNAMES[move.appear[0].p];
}
- return super.getNotation(move);
+ return notation;
}
};