return true;
}
+ // Allow to take (moving: not disappearing) own pieces?
+ get hasSelfCaptures() {
+ return (
+ this.options["recycle"] ||
+ (this.options["teleport"] && this.subTurnTeleport == 1)
+ );
+ }
+
get hasReserve() {
return (
!!this.options["crazyhouse"] ||
return false;
}
+ // Some variants do not flip board as black
+ get flippedBoard() {
+ return (this.playerColor == 'b');
+ }
+
// Some variants use click infos:
doClick(coords) {
if (typeof coords.x != "number")
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
{
randomness: this.options["randomness"],
- between: {p1: 'k', p2: 'r'},
+ between: [{p1: 'k', p2: 'r'}],
diffCol: ['b'],
- flags: ['r']
+ flags: ['r', 'k']
}
);
return {
}
// Some additional variables from FEN (variant dependant)
- setOtherVariables(fenParsed) {
+ setOtherVariables(fenParsed, pieceArray) {
// Set flags and enpassant:
if (this.hasFlags)
this.setFlags(fenParsed.flags);
if (this.hasEnpassant)
this.epSquare = this.getEpSquare(fenParsed.enpassant);
if (this.hasReserve && !this.isDiagram)
- this.initReserves(fenParsed.reserve);
+ this.initReserves(fenParsed.reserve, pieceArray);
if (this.options["crazyhouse"])
this.initIspawn(fenParsed.ispawn);
if (this.options["teleport"]) {
// Get SVG board (background, no pieces)
getSvgChessboard() {
- const flipped = (this.playerColor == 'b');
+ const flipped = this.flippedBoard;
let board = `
<svg
viewBox="0 0 ${10*this.size.y} ${10*this.size.x}"
}
else {
const sqSize = r.width / this.size.y;
- const flipped = (this.playerColor == 'b');
+ const flipped = this.flippedBoard;
x = (flipped ? this.size.y - 1 - j : j) * sqSize;
y = (flipped ? this.size.x - 1 - i : i) * sqSize;
}
// TODO: (0, 0) is wrong, would need to place an attacker here...
const steps = this.pieces(this.playerColor, 0, 0)["p"].attack[0].steps;
for (let step of steps) {
- const x = this.epSquare.x - step[0],
+ const x = this.epSquare.x - step[0], //NOTE: epSquare.x not on edge
y = this.getY(this.epSquare.y - step[1]);
if (
this.onBoard(x, y) &&
getPawnShift(color) {
return (color == "w" ? -1 : 1);
}
+ isPawnInitRank(x, color) {
+ return (color == 'w' && x >= 6) || (color == 'b' && x <= 1);
+ }
pieces(color, x, y) {
- const pawnShift = this.getPawnShift(color);
- // NOTE: jump 2 squares from first rank (pawns can be here sometimes)
- const initRank = ((color == 'w' && x >= 6) || (color == 'b' && x <= 1));
+ const pawnShift = this.getPawnShift(color || 'w');
return {
'p': {
"class": "pawn",
moves: [
{
steps: [[pawnShift, 0]],
- range: (initRank ? 2 : 1)
+ range: (this.isPawnInitRank(x, color) ? 2 : 1)
}
],
attack: [
res += this.size.y;
return res;
}
+ // Circular?
+ getX(x) {
+ return x; //generally, no
+ }
+
+ increment([x, y], step) {
+ return [
+ this.getX(x + step[0]),
+ this.getY(y + step[1])
+ ];
+ }
getSegments(curSeg, segStart, segEnd) {
if (curSeg.length == 0)
return this.getColor(x1, y1) !== this.getColor(x2, y2);
}
+ // Teleport & Recycle. Assumption: color(x1,y1) == color(x2,y2)
+ canSelfTake([x1, y1], [x2, y2]) {
+ return !this.isKing(x2, y2);
+ }
+
canStepOver(i, j, p) {
// In some variants, objects on boards don't stop movement (Chakart)
return this.board[i][j] == "";
const attacks = stepSpec.both.concat(stepSpec.attack);
for (let a of attacks) {
outerLoop: for (let step of a.steps) {
- let [i, j] = [x + step[0], y + step[1]];
- let stepCounter = 1;
+ let [i, j] = this.increment([x, y], step);
+ let stepCounter = 0;
while (this.onBoard(i, j) && this.board[i][j] == "") {
if (a.range <= stepCounter++)
continue outerLoop;
- i += step[0];
- j = this.getY(j + step[1]);
+ [i, j] = this.increment([i, j], step);
}
if (
this.onBoard(i, j) &&
{
attackOnly: true,
one: true,
- segments: this.options["cylinder"]
},
allowed
)
this.options["zen"] &&
this.findCapturesOn(
[i, j],
- {
- one: true,
- segments: this.options["cylinder"]
- },
+ {one: true},
allowed
)
)
}
// All possible moves from selected square
+ // TODO: generalize usage if arg "color" (e.g. Checkered)
getPotentialMovesFrom([x, y], color) {
if (this.subTurnTeleport == 2)
return [];
vanish: []
});
for (let step of steps) {
- let x = m.end.x + step[0];
- let y = this.getY(m.end.y + step[1]);
+ let [x, y] = this.increment([m.end.x, m.end.y], step);
if (
this.onBoard(x, y) &&
this.board[x][y] != "" &&
m.appear[0].p = this.pawnPromotions[0];
for (let i=1; i<this.pawnPromotions.length; i++) {
let newMv = JSON.parse(JSON.stringify(m));
- newMv.appear[0].p = this.pawnSpecs.promotions[i];
+ newMv.appear[0].p = this.pawnPromotions[i];
newMoves.push(newMv);
}
}
[x, y],
{
attackOnly: true,
- segments: this.options["cylinder"],
stepSpec: stepSpec
},
([i1, j1], [i2, j2]) => {
[x, y],
{
moveOnly: !!stepSpec.attack || this.options["zen"],
- segments: this.options["cylinder"],
stepSpec: stepSpec
}
);
!this.isKing(i1, j1) && this.canTake([i2, j2], [i1, j1])
);
// Technical step: segments (if any) are reversed
- if (this.options["cylinder"]) {
- zenCaptures.forEach(z => {
+ zenCaptures.forEach(z => {
+ if (!!z.segments)
z.segments = z.segments.reverse().map(s => s.reverse())
- });
- }
+ });
Array.prototype.push.apply(squares, zenCaptures);
}
- if (
- this.options["recycle"] ||
- (this.options["teleport"] && this.subTurnTeleport == 1)
- ) {
+ if (this.hasSelfCaptures) {
const selfCaptures = this.findDestSquares(
[x, y],
{
attackOnly: true,
- segments: this.options["cylinder"],
stepSpec: stepSpec
},
- ([i1, j1], [i2, j2]) =>
- this.getColor(i2, j2) == color && !this.isKing(i2, j2)
+ ([i1, j1], [i2, j2]) => {
+ return (
+ this.getColor(i2, j2) == color &&
+ this.canSelfTake([i1, j1], [i2, j2])
+ );
+ }
);
Array.prototype.push.apply(squares, selfCaptures);
}
return squares.map(s => {
let mv = this.getBasicMove([x, y], s.sq);
- if (this.options["cylinder"] && s.segments.length >= 2)
+ if (!!s.segments)
mv.segments = s.segments;
return mv;
});
allowed = (sq1, sq2) => this.canTake(sq1, sq2);
const apparentPiece = this.getPiece(x, y); //how it looks
let res = [];
- // Next 3 for Cylinder mode: (unused if !o.segments)
+ // Next 3 for Cylinder mode or circular (useless otherwise)
let explored = {};
let segments = [];
let segStart = [];
const addSquare = ([i, j]) => {
let elt = {sq: [i, j]};
- if (o.segments)
- elt.segments = this.getSegments(segments, segStart, end);
+ if (segments.length >= 1)
+ elt.segments = this.getSegments(segments, segStart, [i, j]);
res.push(elt);
};
const exploreSteps = (stepArray, mode) => {
for (let s of stepArray) {
outerLoop: for (let step of s.steps) {
- if (o.segments) {
- segments = [];
- segStart = [x, y];
- }
+ segments = [];
+ segStart = [x, y];
let [i, j] = [x, y];
let stepCounter = 0;
while (
if (s.range <= stepCounter++)
continue outerLoop;
const oldIJ = [i, j];
- i += step[0];
- j = this.getY(j + step[1]);
- if (o.segments && Math.abs(j - oldIJ[1]) > 1) {
- // Boundary between segments (cylinder mode)
+ [i, j] = this.increment([i, j], step);
+ if (Math.abs(j - oldIJ[1]) > 1 || Math.abs(i - oldIJ[0]) > 1) {
+ // Boundary between segments (cylinder or circular mode)
segments.push([[segStart[0], segStart[1]], oldIJ]);
segStart = [i, j];
}
{
captureTarget: [x, y],
captureSteps: [{steps: [s], range: a.range}],
- segments: o.segments
},
allowed
);
const oppCols = this.getOppCols(color);
if (
this.epSquare &&
- this.epSquare.x == x + shiftX &&
+ this.epSquare.x == x + shiftX && //NOTE: epSquare.x not on edge
Math.abs(this.getY(this.epSquare.y - y)) == 1 &&
// Doublemove (and Progressive?) guards:
this.board[this.epSquare.x][this.epSquare.y] == "" &&
[x, y],
{
byCol: oppCols,
- segments: this.options["cylinder"],
one: true
}
)
[x, y],
{
attackOnly: true,
- segments: this.options["cylinder"],
one: true
},
([i1, j1], [i2, j2]) => oppCols.includes(this.getColor(i2, j2))
return res;
}
+ // cb: callback returning a boolean (false if king missing)
+ trackKingWrap(move, kingPos, cb) {
+ if (move.appear.length == 0 && move.vanish.length == 0)
+ return true;
+ const color =
+ (move.vanish.length > 0 ? move.vanish[0].c : move.appear[0].c);
+ let newKingPP = null,
+ sqIdx = 0,
+ res = true; //a priori valid
+ const oldKingPP =
+ move.vanish.find(v => this.isKing(0, 0, v.p) && v.c == color);
+ if (oldKingPP) {
+ // Search king in appear array:
+ newKingPP =
+ move.appear.find(a => this.isKing(0, 0, a.p) && a.c == color);
+ if (newKingPP) {
+ sqIdx = kingPos.findIndex(kp =>
+ kp[0] == oldKingPP.x && kp[1] == oldKingPP.y);
+ kingPos[sqIdx] = [newKingPP.x, newKingPP.y];
+ }
+ else
+ res = false; //king vanished
+ }
+ res &&= cb(kingPos);
+ if (oldKingPP && newKingPP)
+ kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
+ return res;
+ }
+
// 'color' arg because some variants (e.g. Refusal) check opponent moves
filterValid(moves, color) {
if (!color)
let kingPos = this.searchKingPos(color);
return moves.filter(m => {
this.playOnBoard(m);
- let newKingPP = null,
- sqIdx = 0,
- res = true; //a priori valid
- const oldKingPP =
- m.vanish.find(v => this.isKing(0, 0, v.p) && v.c == color);
- if (oldKingPP) {
- // Search king in appear array:
- newKingPP =
- m.appear.find(a => this.isKing(0, 0, a.p) && a.c == color);
- if (newKingPP) {
- sqIdx = kingPos.findIndex(kp =>
- kp[0] == oldKingPP.x && kp[1] == oldKingPP.y);
- kingPos[sqIdx] = [newKingPP.x, newKingPP.y];
- }
- else
- res = false; //king vanished
- }
- res &&= !this.underCheck(kingPos, oppCols);
- if (oldKingPP && newKingPP)
- kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
+ const res = this.trackKingWrap(m, kingPos, (kp) => {
+ return !this.underCheck(kp, oppCols);
+ });
this.undoOnBoard(m);
return res;
});
if (this.board[i][j] != "" && this.getColor(i, j) == color) {
// NOTE: in fact searching for all potential moves from i,j.
// I don't believe this is an issue, for now at least.
- const moves = this.getPotentialMovesFrom([i, j]);
+ const moves = this.getPotentialMovesFrom([i, j], color);
if (moves.some(m => this.filterValid([m]).length >= 1))
return true;
}