-import { Random } from "/utils/alea.js";
-import { ArrayFun } from "/utils/array.js";
+import {Random} from "/utils/alea.js";
+import {ArrayFun} from "/utils/array.js";
import PiPo from "/utils/PiPo.js";
import Move from "/utils/Move.js";
return false;
}
+ // Some variants reveal moves only after both players played
+ get hideMoves() {
+ return false;
+ }
+
// Some variants use click infos:
doClick(coords) {
if (typeof coords.x != "number")
baseFen.o = Object.assign({init: true}, baseFen.o);
const parts = this.getPartFen(baseFen.o);
return (
- baseFen.fen +
+ baseFen.fen + " w 0" +
(Object.keys(parts).length > 0 ? (" " + JSON.stringify(parts)) : "")
);
}
let fen, flags = "0707";
if (!this.options.randomness)
// Deterministic:
- fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0";
+ fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
else {
// Randomize
fen = (
pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0"
+ pieces["w"].join("").toUpperCase()
);
}
return { fen: fen, o: {flags: flags} };
}
// ordering as in pieces() p,r,n,b,q,k
- initReserves(reserveStr) {
+ initReserves(reserveStr, pieceArray) {
+ if (!pieceArray)
+ pieceArray = ['p', 'r', 'n', 'b', 'q', 'k'];
const counts = reserveStr.split("").map(c => parseInt(c, 36));
- this.reserve = { w: {}, b: {} };
- const pieceName = ['p', 'r', 'n', 'b', 'q', 'k'];
- const L = pieceName.length;
- for (let i of ArrayFun.range(2 * L)) {
- if (i < L)
- this.reserve['w'][pieceName[i]] = counts[i];
- else
- this.reserve['b'][pieceName[i-L]] = counts[i];
- }
+ const L = pieceArray.length;
+ this.reserve = {
+ w: ArrayFun.toObject(pieceArray, counts.slice(0, L)),
+ b: ArrayFun.toObject(pieceArray, counts.slice(L, 2 * L))
+ };
}
initIspawn(ispawnStr) {
(oldV,newV) => oldV + (this.reserve[c][newV] > 0 ? 1 : 0), 0);
}
- static AddClass_es(piece, class_es) {
+ static AddClass_es(elt, class_es) {
if (!Array.isArray(class_es))
class_es = [class_es];
- class_es.forEach(cl => {
- piece.classList.add(cl);
- });
+ class_es.forEach(cl => elt.classList.add(cl));
}
- static RemoveClass_es(piece, class_es) {
+ static RemoveClass_es(elt, class_es) {
if (!Array.isArray(class_es))
class_es = [class_es];
- class_es.forEach(cl => {
- piece.classList.remove(cl);
- });
+ class_es.forEach(cl => elt.classList.remove(cl));
}
// Generally light square bottom-right
class="chessboard_SVG">`;
for (let i=0; i < this.size.x; i++) {
for (let j=0; j < this.size.y; j++) {
+ if (!this.onBoard(i, j))
+ continue;
const ii = (flipped ? this.size.x - 1 - i : i);
const jj = (flipped ? this.size.y - 1 - j : j);
let classes = this.getSquareColorClass(ii, jj);
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, or genFenOnly
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);
}
}
+ displayMessage(elt, msg, classe_s, timeout) {
+ if (elt)
+ // Fixed element, e.g. for Dice Chess
+ elt.innerHTML = msg;
+ else {
+ // Temporary div (Chakart, Apocalypse...)
+ let divMsg = document.createElement("div");
+ C.AddClass_es(divMsg, classe_s);
+ divMsg.innerHTML = msg;
+ let container = document.getElementById(this.containerId);
+ container.appendChild(divMsg);
+ setTimeout(() => container.removeChild(divMsg), timeout);
+ }
+ }
+
////////////////
// DARK METHODS
},
'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?
const oppCol = C.GetOppCol(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]];
let moves = [];
for (let i=0; i<this.size.x; i++) {
for (let j=0; j<this.size.y; j++) {
- if (this.canDrop([c, p], [i, j])) {
+ if (this.onBoard(i, j) && this.canDrop([c, p], [i, j])) {
let mv = new Move({
start: {x: c, y: p},
end: {x: i, y: j},
moves = this.capturePostProcess(moves, oppCol);
if (this.options["atomic"])
- this.atomicPostProcess(moves, color, oppCol);
+ moves = this.atomicPostProcess(moves, color, oppCol);
if (
moves.length > 0 &&
this.getPieceType(moves[0].start.x, moves[0].start.y) == "p"
) {
- this.pawnPostProcess(moves, color, oppCol);
+ moves = this.pawnPostProcess(moves, color, oppCol);
}
if (this.options["cannibal"] && this.options["rifle"])
// In this case a rifle-capture from last rank may promote a pawn
- this.riflePromotePostProcess(moves, color);
+ moves = this.riflePromotePostProcess(moves, color);
return moves;
}
m.next = mNext;
}
});
+ return moves;
}
pawnPostProcess(moves, color, oppCol) {
moreMoves.push(newMove);
}
});
- Array.prototype.push.apply(moves, moreMoves);
+ return moves.concat(moreMoves);
}
riflePromotePostProcess(moves, color) {
}
}
});
- Array.prototype.push.apply(moves, newMoves);
+ return moves.concat(newMoves);
}
// Generic method to find possible moves of "sliding or jumping" pieces
elt.segments = this.getSegments(segments, segStart, end);
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);
}
}
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
// will be executed in filterValid() later.
(
i != finalSquares[castleSide][0] &&
- this.underCheck([x, i], oppCol)
+ this.underCheck([[x, i]], oppCol)
)
||
(
underCheck(square_s, oppCol) {
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));
}
}
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.GetOppCol(this.turn);
this.movesCount++;
this.subTurn = 1;
}
}
// What is the score ? (Interesting if game is over)
- getCurrentScore(move) {
+ getCurrentScore(move_s) {
+ const move = move_s[move_s.length - 1];
+ // Shortcut in case the score was computed before:
+ if (move.result)
+ return move.result;
const color = this.turn;
const oppCol = C.GetOppCol(color);
const kingPos = {
buildMoveStack(move, r) {
this.moveStack.push(move);
this.computeNextMove(move);
- this.play(move);
- const newTurn = this.turn;
- if (this.moveStack.length == 1)
- 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
this.animateFading(arr, () => targetObj.increment());
}
}
+ targetObj.target +=
+ this.tryAnimateCastle(move, () => targetObj.increment());
targetObj.target +=
this.customAnimate(move, segments, () => targetObj.increment());
if (targetObj.target == 0)
callback();
}
+ tryAnimateCastle(move, cb) {
+ if (
+ this.hasCastle &&
+ move.vanish.length == 2 &&
+ move.appear.length == 2 &&
+ this.isKing(0, 0, move.vanish[0].p) &&
+ this.isKing(0, 0, move.appear[0].p)
+ ) {
+ const start = {x: move.vanish[1].x, y: move.vanish[1].y},
+ end = {x: move.appear[1].x, y: move.appear[1].y};
+ const segments = [ [[start.x, start.y], [end.x, end.y]] ];
+ this.animateMoving(start, end, null, segments, cb);
+ return 1;
+ }
+ return 0;
+ }
+
// Potential other animations (e.g. for Suction variant)
customAnimate(move, segments, cb) {
return 0; //nb of targets
}
- playReceivedMove(moves, callback) {
- const launchAnimation = () => {
- const r = container.querySelector(".chessboard").getBoundingClientRect();
- const animateRec = i => {
- this.animate(moves[i], () => {
- this.play(moves[i]);
- this.playVisual(moves[i], r);
- if (i < moves.length - 1)
- setTimeout(() => animateRec(i+1), 300);
- else
- callback();
- });
- };
- animateRec(0);
+ launchAnimation(moves, container, callback) {
+ if (this.hideMoves) {
+ 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();
+ const animateRec = i => {
+ this.animate(moves[i], () => {
+ this.play(moves[i]);
+ this.playVisual(moves[i], r);
+ if (i < moves.length - 1)
+ setTimeout(() => animateRec(i+1), 300);
+ else
+ callback();
+ });
};
+ animateRec(0);
+ }
+
+ playReceivedMove(moves, callback) {
// Delay if user wasn't focused:
const checkDisplayThenAnimate = (delay) => {
if (container.style.display == "none") {
alert("New move! Let's go back to game...");
document.getElementById("gameInfos").style.display = "none";
container.style.display = "block";
- setTimeout(launchAnimation, 700);
+ setTimeout(
+ () => this.launchAnimation(moves, container, callback),
+ 700
+ );
+ }
+ else {
+ setTimeout(
+ () => this.launchAnimation(moves, container, callback),
+ delay || 0
+ );
}
- else
- setTimeout(launchAnimation, delay || 0);
};
let container = document.getElementById(this.containerId);
if (document.hidden) {