-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")
return (f.charCodeAt(0) <= 90 ? "w" + f.toLowerCase() : "b" + f);
}
- // Setup the initial random-or-not (asymmetric-or-not) position
genRandInitFen(seed) {
+ Random.setSeed(seed); //may be unused
+ let baseFen = this.genRandInitBaseFen();
+ baseFen.o = Object.assign({init: true}, baseFen.o);
+ const parts = this.getPartFen(baseFen.o);
+ return (
+ baseFen.fen +
+ (Object.keys(parts).length > 0 ? (" " + JSON.stringify(parts)) : "")
+ );
+ }
+
+ // Setup the initial random-or-not (asymmetric-or-not) position
+ genRandInitBaseFen() {
let fen, flags = "0707";
if (!this.options.randomness)
// Deterministic:
else {
// Randomize
- Random.setSeed(seed);
let pieces = {w: new Array(8), b: new Array(8)};
flags = "";
// Shuffle pieces on first (and last rank if randomness == 2)
flags += flags;
break;
}
-
let positions = ArrayFun.range(8);
-
// Get random squares for bishops
let randIndex = 2 * Random.randInt(4);
const bishop1Pos = positions[randIndex];
// 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];
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";
" w 0"
);
}
- // Add turn + flags + enpassant (+ reserve)
- let parts = [];
- if (this.hasFlags)
- parts.push(`"flags":"${flags}"`);
- if (this.hasEnpassant)
- parts.push('"enpassant":"-"');
- if (this.hasReserveFen)
- parts.push('"reserve":"000000000000"');
- if (this.options["crazyhouse"])
- parts.push('"ispawn":"-"');
- if (parts.length >= 1)
- fen += " {" + parts.join(",") + "}";
- return fen;
+ return { fen: fen, o: {flags: flags} };
}
// "Parse" FEN: just return untransformed string data
// Return current fen (game state)
getFen() {
- let fen = (
- this.getPosition() + " " +
- this.getTurnFen() + " " +
- this.movesCount
+ const parts = this.getPartFen({});
+ return (
+ this.getBaseFen() +
+ (Object.keys(parts).length > 0 ? (" " + JSON.stringify(parts)) : "")
);
- let parts = [];
+ }
+
+ getBaseFen() {
+ return this.getPosition() + " " + this.turn + " " + this.movesCount;
+ }
+
+ getPartFen(o) {
+ let parts = {};
if (this.hasFlags)
- parts.push(`"flags":"${this.getFlagsFen()}"`);
+ parts["flags"] = o.init ? o.flags : this.getFlagsFen();
if (this.hasEnpassant)
- parts.push(`"enpassant":"${this.getEnpassantFen()}"`);
+ parts["enpassant"] = o.init ? "-" : this.getEnpassantFen();
if (this.hasReserveFen)
- parts.push(`"reserve":"${this.getReserveFen()}"`);
+ parts["reserve"] = this.getReserveFen(o);
if (this.options["crazyhouse"])
- parts.push(`"ispawn":"${this.getIspawnFen()}"`);
- if (parts.length >= 1)
- fen += " {" + parts.join(",") + "}";
- return fen;
+ parts["ispawn"] = this.getIspawnFen(o);
+ return parts;
}
static FenEmptySquares(count) {
return position;
}
- getTurnFen() {
- return this.turn;
- }
-
// Flags part of the FEN string
getFlagsFen() {
return ["w", "b"].map(c => {
// Enpassant part of the FEN string
getEnpassantFen() {
if (!this.epSquare)
- return "-"; //no en-passant
+ return "-";
return C.CoordsToSquare(this.epSquare);
}
- getReserveFen() {
+ getReserveFen(o) {
+ if (o.init)
+ return "000000000000";
return (
["w","b"].map(c => Object.values(this.reserve[c]).join("")).join("")
);
}
- getIspawnFen() {
+ getIspawnFen(o) {
+ if (o.init)
+ // NOTE: cannot merge because this.ispawn doesn't exist yet
+ return "-";
const squares = Object.keys(this.ispawn);
if (squares.length == 0)
return "-";
if (o.genFenOnly)
// This object will be used only for initial FEN generation
return;
+
+ // Some variables
this.playerColor = o.color;
this.afterPlay = o.afterPlay; //trigger some actions after playing a move
+ this.containerId = o.element;
+ this.isDiagram = o.diagram;
+ this.marks = o.marks;
- // Fen string fully describes the game state
+ // Initializations
if (!o.fen)
o.fen = this.genRandInitFen(o.seed);
this.re_initFromFen(o.fen);
-
- // Graphical (can use variables defined above)
- this.containerId = o.element;
- this.isDiagram = o.diagram;
- this.marks = o.marks;
this.graphicalInit();
}
(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);
}
}
+ 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
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) {
m.appear.shift();
return;
}
- let finalPieces = ["p"];
+ let finalPieces;
if (
this.options["cannibal"] &&
this.board[x2][y2] != "" &&
if (initPiece == "!") //cannibal king-pawn
m.appear[0].p = C.CannibalKingCode[finalPieces[0]];
for (let i=1; i<finalPieces.length; i++) {
+ let newMove = JSON.parse(JSON.stringify(m));
const piece = finalPieces[i];
- const tr = {
- c: color,
- p: (initPiece != "!" ? piece : C.CannibalKingCode[piece])
- };
- let newMove = this.getBasicMove([x1, y1], [x2, y2], tr);
+ m.appear[0].p = (initPiece != "!" ? piece : C.CannibalKingCode[piece]);
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
);
}
+ // Argument is (very generally) an array of squares (= arrays)
underCheck(square_s, oppCol) {
if (this.options["taking"] || this.options["dark"])
return false;
- if (!Array.isArray(square_s))
+ if (!Array.isArray(square_s[0]))
square_s = [square_s];
return square_s.some(sq => this.underAttack(sq, oppCol));
}
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);
+ 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);
+ 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(square_s, oppCol);
+ res &&= !this.underCheck(kingPos, oppCol);
if (oldKingPP && newKingPP)
kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
this.undoOnBoard(m);
this.computeNextMove(move);
this.play(move);
const newTurn = this.turn;
- if (this.moveStack.length == 1)
+ if (this.moveStack.length == 1 && !this.hideMoves)
this.playVisual(move, r);
if (move.next) {
this.gameState = {
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) {
+ moves.forEach(m => this.play(m));
+ 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) {