import {Random} from "/utils/alea.js";
import {ArrayFun} from "/utils/array.js";
+import {FenUtil} from "/utils/setupPieces.js";
import PiPo from "/utils/PiPo.js";
import Move from "/utils/Move.js";
// Setup the initial random-or-not (asymmetric-or-not) position
genRandInitBaseFen() {
- let fen, flags = "0707";
- if (!this.options.randomness)
- // Deterministic:
- fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
-
- else {
- // Randomize
- let pieces = {w: new Array(8), b: new Array(8)};
- flags = "";
- // Shuffle pieces on first (and last rank if randomness == 2)
- for (let c of ["w", "b"]) {
- if (c == 'b' && this.options.randomness == 1) {
- pieces['b'] = pieces['w'];
- flags += flags;
- break;
- }
- let positions = ArrayFun.range(8);
- // Get random squares for bishops
- let randIndex = 2 * Random.randInt(4);
- const bishop1Pos = positions[randIndex];
- // The second bishop must be on a square of different color
- let randIndex_tmp = 2 * Random.randInt(4) + 1;
- const bishop2Pos = positions[randIndex_tmp];
- // Remove chosen squares
- positions.splice(Math.max(randIndex, randIndex_tmp), 1);
- positions.splice(Math.min(randIndex, randIndex_tmp), 1);
- // Get random squares for knights
- randIndex = Random.randInt(6);
- const knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = Random.randInt(5);
- const knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- // Get random square for queen
- randIndex = Random.randInt(4);
- const queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
- // Rooks and king positions are now fixed,
- // because of the ordering rook-king-rook
- const rook1Pos = positions[0];
- const kingPos = positions[1];
- const rook2Pos = positions[2];
- // Finally put the shuffled pieces in the board array
- pieces[c][rook1Pos] = "r";
- pieces[c][knight1Pos] = "n";
- pieces[c][bishop1Pos] = "b";
- pieces[c][queenPos] = "q";
- pieces[c][kingPos] = "k";
- pieces[c][bishop2Pos] = "b";
- pieces[c][knight2Pos] = "n";
- pieces[c][rook2Pos] = "r";
- flags += rook1Pos.toString() + rook2Pos.toString();
+ const s = FenUtil.setupPieces(
+ ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
+ {
+ randomness: this.options["randomness"],
+ between: {p1: 'k', p2: 'r'},
+ diffCol: ['b'],
+ flags: ['r']
}
- fen = (
- pieces["b"].join("") +
- "/pppppppp/8/8/8/8/PPPPPPPP/" +
- pieces["w"].join("").toUpperCase()
- );
- }
- return { fen: fen, o: {flags: flags} };
+ );
+ return {
+ fen: s.b.join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" +
+ s.w.join("").toUpperCase(),
+ o: {flags: s.flags}
+ };
}
// "Parse" FEN: just return untransformed string data
// Position part of the FEN string
getPosition() {
let position = "";
- for (let i = 0; i < this.size.y; i++) {
+ for (let i = 0; i < this.size.x; i++) {
let emptyCount = 0;
- for (let j = 0; j < this.size.x; j++) {
+ for (let j = 0; j < this.size.y; j++) {
if (this.board[i][j] == "")
emptyCount++;
else {
if (emptyCount > 0)
// "Flush remainder"
position += C.FenEmptySquares(emptyCount);
- if (i < this.size.y - 1)
+ if (i < this.size.x - 1)
position += "/"; //separate rows
}
return position;
if (this.options[opt.variable] === undefined)
this.options[opt.variable] = opt.defaut;
});
- if (o.genFenOnly)
- // This object will be used only for initial FEN generation
- return;
// Some variables
this.playerColor = o.color;
}
getRankInReserve(c, p) {
- const pieces = Object.keys(this.pieces());
+ const pieces = Object.keys(this.pieces(c, c, p));
const lastIndex = pieces.findIndex(pp => pp == p)
let toTest = pieces.slice(0, lastIndex);
return toTest.reduce(
re_drawBoardElements() {
const board = this.getSvgChessboard();
- const oppCol = C.GetOppCol(this.playerColor);
const container = document.getElementById(this.containerId);
const rc = container.getBoundingClientRect();
let chessboard = container.querySelector(".chessboard");
chessboard.style.top = spaceTop + "px";
// Give sizes instead of recomputing them,
// because chessboard might not be drawn yet.
- this.setupPieces({
+ this.setupVisualPieces({
width: cbWidth,
height: cbHeight,
x: spaceLeft,
return board;
}
- setupPieces(r) {
+ setupVisualPieces(r) {
let chessboard =
document.getElementById(this.containerId).querySelector(".chessboard");
if (!r)
const color = this.getColor(i, j);
const piece = this.getPiece(i, j);
addPiece(i, j, "g_pieces", this.pieces(color, i, j)[piece]["class"]);
- this.g_pieces[i][j].classList.add(C.GetColorClass(color));
+ this.g_pieces[i][j].classList.add(V.GetColorClass(color));
if (this.enlightened && !this.enlightened[i][j])
this.g_pieces[i][j].classList.add("hidden");
}
rcontainer.appendChild(r_cell);
let piece = document.createElement("piece");
C.AddClass_es(piece, this.pieces(c, c, p)[p]["class"]);
- piece.classList.add(C.GetColorClass(c));
+ piece.classList.add(V.GetColorClass(c));
piece.style.width = "100%";
piece.style.height = "100%";
this.r_pieces[c][p] = piece;
piece = "k"; //capturing cannibal king: back to king form
const oldCount = this.reserve[color][piece];
this.reserve[color][piece] = count;
- // Redrawing is much easier if count==0
- if ([oldCount, count].includes(0))
+ // Redrawing is much easier if count==0 (or undefined)
+ if ([oldCount, count].some(item => !item))
this.re_drawReserve([color]);
else {
const numId = this.getReserveNumId(color, piece);
// TODO: onpointerdown/move/up ? See reveal.js /controllers/touch.js
}
+ // NOTE: not called if isDiagram
removeListeners() {
let container = document.getElementById(this.containerId);
this.windowResizeObs.unobserve(container);
- if (this.isDiagram)
- return; //no listeners in this case
if ('onmousedown' in window) {
this.mouseListeners.forEach(ml => {
document.removeEventListener(ml.type, ml.listener);
const cdisp = moves[i].choice || moves[i].appear[0].p;
C.AddClass_es(piece,
this.pieces(color, moves[i].end.x, moves[i].end.y)[cdisp]["class"]);
- piece.classList.add(C.GetColorClass(color));
+ piece.classList.add(V.GetColorClass(color));
piece.style.width = "100%";
piece.style.height = "100%";
choice.appendChild(piece);
// Include square of the en-passant capturing square:
enlightEnpassant() {
// NOTE: shortcut, pawn has only one attack type, doesn't depend on square
- const steps = this.pieces(this.playerColor)["p"].attack[0].steps;
+ // 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],
y = this.getY(this.epSquare.y - step[1]);
getPieceType(x, y, p) {
if (!p)
p = this.getPiece(x, y);
- return this.pieces()[p].moveas || p;
+ return this.pieces(this.getColor(x, y), x, y)[p].moveas || p;
}
isKing(x, y, p) {
return !!C.CannibalKings[p];
}
- // Get opponent color
- static GetOppCol(color) {
+ static GetOppTurn(color) {
+ return (color == 'w' ? 'b' : 'w');
+ }
+
+ // Get opponent color(s): may differ from turn (e.g. Checkered)
+ getOppCols(color) {
return (color == "w" ? "b" : "w");
}
////////////////////////
// PIECES SPECIFICATIONS
+ getPawnShift(color) {
+ return (color == "w" ? -1 : 1);
+ }
+
pieces(color, x, y) {
- const pawnShift = (color == "w" ? -1 : 1);
+ 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));
return {
},
'r': {
"class": "rook",
- moves: [
+ both: [
{steps: [[0, 1], [0, -1], [1, 0], [-1, 0]]}
]
},
'n': {
"class": "knight",
- moves: [
+ both: [
{
steps: [
[1, 2], [1, -2], [-1, 2], [-1, -2],
},
'b': {
"class": "bishop",
- moves: [
+ both: [
{steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]]}
]
},
'q': {
"class": "queen",
- moves: [
+ both: [
{
steps: [
[0, 1], [0, -1], [1, 0], [-1, 0],
},
'k': {
"class": "king",
- moves: [
+ both: [
{
steps: [
[0, 1], [0, -1], [1, 0], [-1, 0],
}
getStepSpec(color, x, y, piece) {
- return this.pieces(color, x, y)[piece || this.getPieceType(x, y)];
+ let pieceType = piece;
+ let allSpecs = this.pieces(color, x, y);
+ if (!piece)
+ pieceType = this.getPieceType(x, y);
+ else if (allSpecs[piece].moveas)
+ pieceType = allSpecs[piece].moveas;
+ let res = allSpecs[pieceType];
+ if (!res["both"])
+ res.both = [];
+ if (!res["moves"])
+ res.moves = [];
+ if (!res["attack"])
+ res.attack = [];
+ return res;
}
// Can thing on square1 capture thing on square2?
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] == "";
if (!this.options["madrasi"])
return false;
const color = this.getColor(x, y);
- const oppCol = C.GetOppCol(color);
+ const oppCols = this.getOppCols(color);
const piece = this.getPieceType(x, y);
const stepSpec = this.getStepSpec(color, x, y, piece);
- const attacks = stepSpec.attack || stepSpec.moves;
+ 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]];
}
if (
this.onBoard(i, j) &&
- this.getColor(i, j) == oppCol &&
+ oppCols.includes(this.getColor(i, j)) &&
this.getPieceType(i, j) == piece
) {
return true;
// Stop at the first capture found
atLeastOneCapture(color) {
- const oppCol = C.GetOppCol(color);
const allowed = (sq1, sq2) => {
return (
// NOTE: canTake is reversed for Zen.
}
// All possible moves from selected square
+ // TODO: generalize usage if arg "color" (e.g. Checkered)
getPotentialMovesFrom([x, y], color) {
if (this.subTurnTeleport == 2)
return [];
if (moves.length == 0)
return [];
const color = this.getColor(moves[0].start.x, moves[0].start.y);
- const oppCol = C.GetOppCol(color);
+ const oppCols = this.getOppCols(color);
if (this.options["capture"] && this.atLeastOneCapture(color))
- moves = this.capturePostProcess(moves, oppCol);
+ moves = this.capturePostProcess(moves, oppCols);
if (this.options["atomic"])
- moves = this.atomicPostProcess(moves, color, oppCol);
+ moves = this.atomicPostProcess(moves, color, oppCols);
if (
moves.length > 0 &&
this.getPieceType(moves[0].start.x, moves[0].start.y) == "p"
) {
- moves = this.pawnPostProcess(moves, color, oppCol);
+ moves = this.pawnPostProcess(moves, color, oppCols);
}
if (this.options["cannibal"] && this.options["rifle"])
return moves;
}
- capturePostProcess(moves, oppCol) {
+ capturePostProcess(moves, oppCols) {
// Filter out non-capturing moves (not using m.vanish because of
// self captures of Recycle and Teleport).
return moves.filter(m => {
return (
this.board[m.end.x][m.end.y] != "" &&
- this.getColor(m.end.x, m.end.y) == oppCol
+ oppCols.includes(this.getColor(m.end.x, m.end.y))
);
});
}
- atomicPostProcess(moves, color, oppCol) {
+ atomicPostProcess(moves, color, oppCols) {
moves.forEach(m => {
if (
this.board[m.end.x][m.end.y] != "" &&
- this.getColor(m.end.x, m.end.y) == oppCol
+ oppCols.includes(this.getColor(m.end.x, m.end.y))
) {
// Explosion!
let steps = [
return moves;
}
- pawnPostProcess(moves, color, oppCol) {
+ pawnPostProcess(moves, color, oppCols) {
let moreMoves = [];
const lastRank = (color == "w" ? 0 : this.size.x - 1);
const initPiece = this.getPiece(moves[0].start.x, moves[0].start.y);
if (
this.options["cannibal"] &&
this.board[x2][y2] != "" &&
- this.getColor(x2, y2) == oppCol
+ oppCols.includes(this.getColor(x2, y2))
) {
finalPieces = [this.getPieceType(x2, y2)];
}
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 (this.options["cylinder"] && !!s.segments && s.segments.length >= 2)
mv.segments = s.segments;
return mv;
});
const addSquare = ([i, j]) => {
let elt = {sq: [i, j]};
if (o.segments)
- elt.segments = this.getSegments(segments, segStart, end);
+ elt.segments = this.getSegments(segments, segStart, [i, j]);
res.push(elt);
};
- const exploreSteps = (stepArray) => {
+ const exploreSteps = (stepArray, mode) => {
for (let s of stepArray) {
outerLoop: for (let step of s.steps) {
if (o.segments) {
!o.captureTarget ||
(o.captureTarget[0] == i && o.captureTarget[1] == j)
) {
- if (o.one && !o.attackOnly)
+ if (o.one && mode != "attack")
return true;
- if (!o.attackOnly)
+ if (mode != "attack")
addSquare(!o.captureTarget ? [i, j] : [x, y]);
if (o.captureTarget)
return res[0];
if (!explored[i + "." + j]) {
explored[i + "." + j] = true;
if (allowed([x, y], [i, j])) {
- if (o.one && !o.moveOnly)
+ if (o.one && mode != "moves")
return true;
- if (!o.moveOnly)
+ if (mode != "moves")
addSquare(!o.captureTarget ? [i, j] : [x, y]);
if (
o.captureTarget &&
return undefined; //default, but let's explicit it
};
if (o.captureTarget)
- return exploreSteps(o.captureSteps)
+ return exploreSteps(o.captureSteps, "attack");
else {
const stepSpec =
o.stepSpec || this.getStepSpec(this.getColor(x, y), x, y);
let outOne = false;
- if (!o.attackOnly || !stepSpec.attack)
- outOne = exploreSteps(stepSpec.moves);
- if (!outOne && !o.moveOnly && !!stepSpec.attack) {
- o.attackOnly = true; //ok because o is always a temporary object
- outOne = exploreSteps(stepSpec.attack);
- }
+ if (!o.attackOnly)
+ outOne = exploreSteps(stepSpec.both.concat(stepSpec.moves), "moves");
+ if (!outOne && !o.moveOnly)
+ outOne = exploreSteps(stepSpec.both.concat(stepSpec.attack), "attack");
return (o.one ? outOne : res);
}
}
// Search for enemy (or not) pieces attacking [x, y]
findCapturesOn([x, y], o, allowed) {
if (!o.byCol)
- o.byCol = [C.GetOppCol(this.getColor(x, y) || this.turn)];
+ o.byCol = this.getOppCols(this.getColor(x, y) || this.turn);
let res = [];
for (let i=0; i<this.size.x; i++) {
for (let j=0; j<this.size.y; j++) {
if (this.canStepOver(x, y, apparentPiece))
continue;
const stepSpec = this.getStepSpec(colIJ, i, j);
- const attacks = stepSpec.attack || stepSpec.moves;
+ const attacks = stepSpec.attack.concat(stepSpec.both);
for (let a of attacks) {
for (let s of a.steps) {
// Quick check: if step isn't compatible, don't even try
{
captureTarget: [x, y],
captureSteps: [{steps: [s], range: a.range}],
- segments: o.segments,
- attackOnly: true,
- one: false //one and captureTarget are mutually exclusive
+ segments: o.segments
},
allowed
);
getEnpassantCaptures([x, y]) {
const color = this.getColor(x, y);
const shiftX = (color == 'w' ? -1 : 1);
- const oppCol = C.GetOppCol(color);
+ const oppCols = this.getOppCols(color);
if (
this.epSquare &&
this.epSquare.x == x + shiftX &&
Math.abs(this.getY(this.epSquare.y - y)) == 1 &&
// Doublemove (and Progressive?) guards:
this.board[this.epSquare.x][this.epSquare.y] == "" &&
- this.getColor(x, this.epSquare.y) == oppCol
+ oppCols.includes(this.getColor(x, this.epSquare.y))
) {
const [epx, epy] = [this.epSquare.x, this.epSquare.y];
- this.board[epx][epy] = oppCol + 'p';
+ this.board[epx][epy] = this.board[x][this.epSquare.y];
let enpassantMove = this.getBasicMove([x, y], [epx, epy]);
this.board[epx][epy] = "";
const lastIdx = enpassantMove.vanish.length - 1; //think Rifle
const c = this.getColor(x, y);
// Castling ?
- const oppCol = C.GetOppCol(c);
+ const oppCols = this.getOppCols(c);
let moves = [];
// King, then rook:
finalSquares =
// will be executed in filterValid() later.
(
i != finalSquares[castleSide][0] &&
- this.underCheck([x, i], oppCol)
+ this.underCheck([[x, i]], oppCols)
)
||
(
////////////////////
// MOVES VALIDATION
- // Is piece (or square) at given position attacked by "oppCol" ?
- underAttack([x, y], oppCol) {
+ // Is piece (or square) at given position attacked by "oppCol(s)" ?
+ underAttack([x, y], oppCols) {
// An empty square is considered as king,
// since it's used only in getCastleMoves (TODO?)
const king = this.board[x][y] == "" || this.isKing(x, y);
this.findCapturesOn(
[x, y],
{
- byCol: [oppCol],
+ byCol: oppCols,
segments: this.options["cylinder"],
one: true
}
segments: this.options["cylinder"],
one: true
},
- ([i1, j1], [i2, j2]) => this.getColor(i2, j2) == oppCol
+ ([i1, j1], [i2, j2]) => oppCols.includes(this.getColor(i2, j2))
)
)
);
}
// Argument is (very generally) an array of squares (= arrays)
- underCheck(square_s, oppCol) {
+ underCheck(square_s, oppCols) {
if (this.options["taking"] || this.options["dark"])
return false;
- if (!Array.isArray(square_s[0]))
- square_s = [square_s];
- return square_s.some(sq => this.underAttack(sq, oppCol));
+ return square_s.some(sq => this.underAttack(sq, oppCols));
}
// Scan board for king(s)
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)
color = this.turn;
- const oppCol = C.GetOppCol(color);
+ const oppCols = this.getOppCols(color);
let kingPos = this.searchKingPos(color);
- let filtered = {}; //avoid re-checking similar moves (promotions...)
return moves.filter(m => {
- const key = m.start.x + m.start.y + '.' + m.end.x + m.end.y;
- if (!filtered[key]) {
- 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, oppCol);
- if (oldKingPP && newKingPP)
- kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
- this.undoOnBoard(m);
- filtered[key] = res;
- return res;
- }
- return filtered[key];
+ this.playOnBoard(m);
+ const res = this.trackKingWrap(m, kingPos, (kp) => {
+ return !this.underCheck(kp, oppCols);
+ });
+ this.undoOnBoard(m);
+ return res;
});
}
}
postPlay(move) {
- const color = this.turn;
if (this.options["dark"])
this.updateEnlightened();
if (this.options["teleport"]) {
if (
this.subTurnTeleport == 1 &&
move.vanish.length > move.appear.length &&
- move.vanish[1].c == color
+ move.vanish[1].c == this.turn
) {
const v = move.vanish[move.vanish.length - 1];
this.captured = {x: v.x, y: v.y, c: v.c, p: v.p};
this.subTurnTeleport = 1;
this.captured = null;
}
+ this.tryChangeTurn(move);
+ }
+
+ tryChangeTurn(move) {
if (this.isLastMove(move)) {
- this.turn = C.GetOppCol(color);
+ this.turn = C.GetOppTurn(this.turn);
this.movesCount++;
this.subTurn = 1;
}
if (move.next)
return false;
const color = this.turn;
- const oppKingPos = this.searchKingPos(C.GetOppCol(color));
- if (oppKingPos.length == 0 || this.underCheck(oppKingPos, color))
+ const oppKingPos = this.searchKingPos(C.GetOppTurn(color));
+ if (oppKingPos.length == 0 || this.underCheck(oppKingPos, [color]))
return true;
return (
(
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;
}
// Shortcut in case the score was computed before:
if (move.result)
return move.result;
- const color = this.turn;
- const oppCol = C.GetOppCol(color);
+ const oppTurn = C.GetOppTurn(this.turn);
const kingPos = {
- [color]: this.searchKingPos(color),
- [oppCol]: this.searchKingPos(oppCol)
+ w: this.searchKingPos('w'),
+ b: this.searchKingPos('b')
};
- if (kingPos[color].length == 0 && kingPos[oppCol].length == 0)
+ if (kingPos[this.turn].length == 0 && kingPos[oppTurn].length == 0)
return "1/2";
- if (kingPos[color].length == 0)
- return (color == "w" ? "0-1" : "1-0");
- if (kingPos[oppCol].length == 0)
- return (color == "w" ? "1-0" : "0-1");
- if (this.atLeastOneMove(color))
+ if (kingPos[this.turn].length == 0)
+ return (this.turn == "w" ? "0-1" : "1-0");
+ if (kingPos[oppTurn].length == 0)
+ return (this.turn == "w" ? "1-0" : "0-1");
+ if (this.atLeastOneMove(this.turn))
return "*";
// No valid move: stalemate or checkmate?
- if (!this.underCheck(kingPos[color], oppCol))
+ if (!this.underCheck(kingPos[this.turn], this.getOppCols(this.turn)))
return "1/2";
// OK, checkmate
- return (color == "w" ? "0-1" : "1-0");
+ return (this.turn == "w" ? "0-1" : "1-0");
}
playVisual(move, r) {
move.vanish.forEach(v => {
- this.g_pieces[v.x][v.y].remove();
+ if (this.g_pieces[v.x][v.y]) //can be null (e.g. Apocalypse)
+ this.g_pieces[v.x][v.y].remove();
this.g_pieces[v.x][v.y] = null;
});
let chessboard =
this.g_pieces[a.x][a.y] = document.createElement("piece");
C.AddClass_es(this.g_pieces[a.x][a.y],
this.pieces(a.c, a.x, a.y)[a.p]["class"]);
- this.g_pieces[a.x][a.y].classList.add(C.GetColorClass(a.c));
+ this.g_pieces[a.x][a.y].classList.add(V.GetColorClass(a.c));
this.g_pieces[a.x][a.y].style.width = pieceWidth + "px";
this.g_pieces[a.x][a.y].style.height = pieceWidth + "px";
const [ip, jp] = this.getPixelPosition(a.x, a.y, r);
buildMoveStack(move, r) {
this.moveStack.push(move);
this.computeNextMove(move);
- this.play(move);
- const newTurn = this.turn;
- if (this.moveStack.length == 1 && !this.hideMoves)
- this.playVisual(move, r);
- if (move.next) {
- this.gameState = {
- fen: this.getFen(),
- board: JSON.parse(JSON.stringify(this.board)) //easier
- };
- this.buildMoveStack(move.next, r);
- }
- else {
- if (this.moveStack.length == 1) {
- // Usual case (one normal move)
- this.afterPlay(this.moveStack, newTurn, {send: true, res: true});
- this.moveStack = []
+ const then = () => {
+ const newTurn = this.turn;
+ if (this.moveStack.length == 1 && !this.hideMoves)
+ this.playVisual(move, r);
+ if (move.next) {
+ this.gameState = {
+ fen: this.getFen(),
+ board: JSON.parse(JSON.stringify(this.board)) //easier
+ };
+ this.buildMoveStack(move.next, r);
}
else {
- this.afterPlay(this.moveStack, newTurn, {send: true, res: false});
- this.re_initFromFen(this.gameState.fen, this.gameState.board);
- this.playReceivedMove(this.moveStack.slice(1), () => {
- this.afterPlay(this.moveStack, newTurn, {send: false, res: true});
- this.moveStack = []
- });
+ if (this.moveStack.length == 1) {
+ // Usual case (one normal move)
+ this.afterPlay(this.moveStack, newTurn, {send: true, res: true});
+ this.moveStack = [];
+ }
+ else {
+ this.afterPlay(this.moveStack, newTurn, {send: true, res: false});
+ this.re_initFromFen(this.gameState.fen, this.gameState.board);
+ this.playReceivedMove(this.moveStack.slice(1), () => {
+ this.afterPlay(this.moveStack, newTurn, {send: false, res: true});
+ this.moveStack = [];
+ });
+ }
}
- }
+ };
+ // If hiding moves, then they are revealed in play() with callback
+ this.play(move, this.hideMoves ? then : null);
+ if (!this.hideMoves)
+ then();
}
// Implemented in variants using (automatic) moveStack
animateMoving(start, end, drag, segments, cb) {
let initPiece = this.getDomPiece(start.x, start.y);
+ if (!initPiece) { //TODO: shouldn't occur!
+ cb();
+ return;
+ }
// NOTE: cloning often not required, but light enough, and simpler
let movingPiece = initPiece.cloneNode();
initPiece.style.opacity = "0";
C.RemoveClass_es(movingPiece, pieces[startCode]["class"]);
C.AddClass_es(movingPiece, pieces[drag.p]["class"]);
if (apparentColor != drag.c) {
- movingPiece.classList.remove(C.GetColorClass(apparentColor));
- movingPiece.classList.add(C.GetColorClass(drag.c));
+ movingPiece.classList.remove(V.GetColorClass(apparentColor));
+ movingPiece.classList.add(V.GetColorClass(drag.c));
}
}
container.appendChild(movingPiece);
launchAnimation(moves, container, callback) {
if (this.hideMoves) {
- moves.forEach(m => this.play(m));
- callback();
+ for (let i=0; i<moves.length; i++)
+ // If hiding moves, they are revealed into play():
+ this.play(moves[i], i == moves.length - 1 ? callback : () => {});
return;
}
const r = container.querySelector(".chessboard").getBoundingClientRect();
let container = document.getElementById(this.containerId);
if (document.hidden) {
document.onvisibilitychange = () => {
+ // TODO here: page reload ?! (some issues if tab changed...)
document.onvisibilitychange = undefined;
checkDisplayThenAnimate(700);
};