import PiPo from "/utils/PiPo.js";
import Move from "/utils/Move.js";
+// Helper class for move animation
+class TargetObj {
+
+ constructor(callOnComplete) {
+ this.value = 0;
+ this.target = 0;
+ this.callOnComplete = callOnComplete;
+ }
+ increment() {
+ if (++this.value == this.target)
+ this.callOnComplete();
+ }
+
+};
+
// NOTE: x coords: top to bottom (white perspective); y: left to right
// NOTE: ChessRules is aliased as window.C, and variants as window.V
export default class ChessRules {
{label: "Asymmetric random", value: 2}
]
}],
- check: [
+ input: [
{
label: "Capture king",
- defaut: false,
- variable: "taking"
+ variable: "taking",
+ type: "checkbox",
+ defaut: false
},
{
label: "Falling pawn",
- defaut: false,
- variable: "pawnfall"
+ variable: "pawnfall",
+ type: "checkbox",
+ defaut: false
}
],
// Game modifiers (using "elementary variants"). Default: false
(!!this.options["recycle"] && !this.options["teleport"])
);
}
+ // Some variants do not store reserve state (Align4, Chakart...)
+ get hasReserveFen() {
+ return this.hasReserve;
+ }
get noAnimate() {
return !!this.options["dark"];
// Setup the initial random-or-not (asymmetric-or-not) position
genRandInitFen(seed) {
- Random.setSeed(seed);
-
let fen, flags = "0707";
if (!this.options.randomness)
// Deterministic:
else {
// Randomize
- let pieces = { w: new Array(8), b: new Array(8) };
+ Random.setSeed(seed);
+ 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"]) {
parts.push(`"flags":"${flags}"`);
if (this.hasEnpassant)
parts.push('"enpassant":"-"');
- if (this.hasReserve)
+ if (this.hasReserveFen)
parts.push('"reserve":"000000000000"');
if (this.options["crazyhouse"])
parts.push('"ispawn":"-"');
parts.push(`"flags":"${this.getFlagsFen()}"`);
if (this.hasEnpassant)
parts.push(`"enpassant":"${this.getEnpassantFen()}"`);
- if (this.hasReserve)
+ if (this.hasReserveFen)
parts.push(`"reserve":"${this.getReserveFen()}"`);
if (this.options["crazyhouse"])
parts.push(`"ispawn":"${this.getIspawnFen()}"`);
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;
this.playerColor = o.color;
this.afterPlay = o.afterPlay; //trigger some actions after playing a move
// Fen string fully describes the game state
if (!o.fen)
o.fen = this.genRandInitFen(o.seed);
- const fenParsed = this.parseFen(o.fen);
- this.board = this.getBoard(fenParsed.position);
- this.turn = fenParsed.turn;
- this.movesCount = parseInt(fenParsed.movesCount, 10);
- this.setOtherVariables(fenParsed);
+ this.re_initFromFen(o.fen);
// Graphical (can use variables defined above)
this.containerId = o.element;
this.graphicalInit();
}
+ re_initFromFen(fen, oldBoard) {
+ const fenParsed = this.parseFen(fen);
+ this.board = oldBoard || this.getBoard(fenParsed.position);
+ this.turn = fenParsed.turn;
+ this.movesCount = parseInt(fenParsed.movesCount, 10);
+ this.setOtherVariables(fenParsed);
+ }
+
// Turn position fen into double array ["wb","wp","bk",...]
getBoard(position) {
const rows = position.split("/");
this.initReserves(fenParsed.reserve);
if (this.options["crazyhouse"])
this.initIspawn(fenParsed.ispawn);
- this.subTurn = 1; //may be unused
if (this.options["teleport"]) {
this.subTurnTeleport = 1;
this.captured = null;
this.enlightened = ArrayFun.init(this.size.x, this.size.y, false);
this.updateEnlightened();
}
+ this.subTurn = 1; //may be unused
+ if (!this.moveStack) //avoid resetting (unwanted)
+ this.moveStack = [];
}
updateEnlightened() {
return `${this.containerId}|rnum-${color}${piece}`;
}
+ static AddClass_es(piece, class_es) {
+ if (!Array.isArray(class_es))
+ class_es = [class_es];
+ class_es.forEach(cl => {
+ piece.classList.add(cl);
+ });
+ }
+
+ static RemoveClass_es(piece, class_es) {
+ if (!Array.isArray(class_es))
+ class_es = [class_es];
+ class_es.forEach(cl => {
+ piece.classList.remove(cl);
+ });
+ }
+
graphicalInit() {
// NOTE: not window.onresize = this.re_drawBoardElts because scope (this)
window.onresize = () => this.re_drawBoardElements();
// Compare window ratio width / height to aspectRatio:
const windowRatio = window.innerWidth / window.innerHeight;
let cbWidth, cbHeight;
- if (windowRatio <= this.size.ratio) {
+ const vRatio = this.size.ratio || 1;
+ if (windowRatio <= vRatio) {
// Limiting dimension is width:
cbWidth = Math.min(window.innerWidth, 767);
- cbHeight = cbWidth / this.size.ratio;
+ cbHeight = cbWidth / vRatio;
}
else {
// Limiting dimension is height:
cbHeight = Math.min(window.innerHeight, 767);
- cbWidth = cbHeight * this.size.ratio;
+ cbWidth = cbHeight * vRatio;
}
if (this.hasReserve) {
const sqSize = cbWidth / this.size.y;
// Cannot use getReserveSquareSize() here, but sqSize is an upper bound.
if ((window.innerHeight - cbHeight) / 2 < sqSize + 5) {
cbHeight = window.innerHeight - 2 * (sqSize + 5);
- cbWidth = cbHeight * this.size.ratio;
+ cbWidth = cbHeight * vRatio;
}
}
chessboard.style.width = cbWidth + "px";
const flipped = (this.playerColor == 'b');
let board = `
<svg
- viewBox="0 0 80 80"
+ viewBox="0 0 ${10*this.size.y} ${10*this.size.x}"
class="chessboard_SVG">`;
for (let i=0; i < this.size.x; i++) {
for (let j=0; j < this.size.y; j++) {
const color = this.getColor(i, j);
const piece = this.getPiece(i, j);
this.g_pieces[i][j] = document.createElement("piece");
- this.g_pieces[i][j].classList.add(this.pieces()[piece]["class"]);
+ C.AddClass_es(this.g_pieces[i][j],
+ this.pieces(color, i, j)[piece]["class"]);
this.g_pieces[i][j].classList.add(C.GetColorClass(color));
this.g_pieces[i][j].style.width = pieceWidth + "px";
this.g_pieces[i][j].style.height = pieceWidth + "px";
this.re_drawReserve(['w', 'b'], r);
}
- // NOTE: assume !!this.reserve
+ // NOTE: assume this.reserve != null
re_drawReserve(colors, r) {
if (this.r_pieces) {
// Remove (old) reserve pieces
for (let c of colors) {
- if (!this.reserve[c])
- continue;
- Object.keys(this.reserve[c]).forEach(p => {
- if (this.r_pieces[c][p]) {
- this.r_pieces[c][p].remove();
- delete this.r_pieces[c][p];
- const numId = this.getReserveNumId(c, p);
- document.getElementById(numId).remove();
- }
+ Object.keys(this.r_pieces[c]).forEach(p => {
+ this.r_pieces[c][p].remove();
+ delete this.r_pieces[c][p];
+ const numId = this.getReserveNumId(c, p);
+ document.getElementById(numId).remove();
});
- let reservesDiv = document.getElementById("reserves_" + c);
- if (reservesDiv)
- reservesDiv.remove();
}
}
else
if (!r)
r = container.querySelector(".chessboard").getBoundingClientRect();
for (let c of colors) {
+ let reservesDiv = document.getElementById("reserves_" + c);
+ if (reservesDiv)
+ reservesDiv.remove();
if (!this.reserve[c])
continue;
const nbR = this.getNbReservePieces(c);
r_cell.style.height = sqResSize + "px";
rcontainer.appendChild(r_cell);
let piece = document.createElement("piece");
- const pieceSpec = this.pieces()[p];
- piece.classList.add(pieceSpec["class"]);
+ C.AddClass_es(piece, this.pieces(c, c, p)[p]["class"]);
piece.classList.add(C.GetColorClass(c));
piece.style.width = "100%";
piece.style.height = "100%";
const multFact = (mode == "up" ? 1.05 : 0.95);
let [newWidth, newHeight] = [multFact * r.width, multFact * r.height];
// Stay in window:
+ const vRatio = this.size.ratio || 1;
if (newWidth > window.innerWidth) {
newWidth = window.innerWidth;
- newHeight = newWidth / this.size.ratio;
+ newHeight = newWidth / vRatio;
}
if (newHeight > window.innerHeight) {
newHeight = window.innerHeight;
- newWidth = newHeight * this.size.ratio;
+ newWidth = newHeight * vRatio;
}
chessboard.style.width = newWidth + "px";
chessboard.style.height = newHeight + "px";
if (cd) {
const move = this.doClick(cd);
if (move)
- this.playPlusVisual(move);
+ this.buildMoveStack(move, r);
else {
const [x, y] = Object.values(cd);
if (typeof x != "number")
};
const mouseup = (e) => {
- const newR = chessboard.getBoundingClientRect();
- if (newR.width != r.width || newR.height != r.height) {
- this.rescale();
- return;
- }
if (!start)
return;
const [x, y] = [start.x, start.y];
if (moves.length >= 2)
this.showChoices(moves, r);
else if (moves.length == 1)
- this.playPlusVisual(moves[0], r);
+ this.buildMoveStack(moves[0], r);
}
curPiece.remove();
};
- const wheelResize = (e) => {
- this.rescale(e.deltaY < 0 ? "up" : "down");
- };
-
if ('onmousedown' in window) {
document.addEventListener("mousedown", mousedown);
document.addEventListener("mousemove", mousemove);
document.addEventListener("mouseup", mouseup);
- document.addEventListener("wheel", wheelResize);
+ document.addEventListener("wheel",
+ (e) => this.rescale(e.deltaY < 0 ? "up" : "down"));
}
if ('ontouchstart' in window) {
// https://stackoverflow.com/a/42509310/12660887
let chessboard = container.querySelector(".chessboard");
let choices = document.createElement("div");
choices.id = "choices";
+ if (!r)
+ r = chessboard.getBoundingClientRect();
choices.style.width = r.width + "px";
choices.style.height = r.height + "px";
choices.style.left = r.x + "px";
const callback = (m) => {
chessboard.style.opacity = "1";
container.removeChild(choices);
- this.playPlusVisual(m, r);
+ this.buildMoveStack(m, r);
}
for (let i=0; i < moves.length; i++) {
let choice = document.createElement("div");
choice.style.backgroundColor = "lightyellow";
choice.onclick = () => callback(moves[i]);
const piece = document.createElement("piece");
- const pieceSpec = this.pieces()[moves[i].appear[0].p];
- piece.classList.add(pieceSpec["class"]);
+ 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.style.width = "100%";
piece.style.height = "100%";
return {
x: 8,
y: 8,
- ratio: 1 //for rectangular board = y / x
+ ratio: 1 //for rectangular board = y / x (optional, 1 = default)
};
}
}
static GetColorClass(c) {
- return (c == 'w' ? "white" : "black");
+ if (c == 'w')
+ return "white";
+ if (c == 'b')
+ return "black";
+ return "other-color"; //unidentified color
}
// Assume square i,j isn't empty
}
// Piece type on square (i,j)
- getPieceType(i, j) {
- const p = this.getPiece(i, j);
+ getPieceType(i, j, p) {
+ if (!p)
+ p = this.getPiece(i, j);
return C.CannibalKings[p] || p; //a cannibal king move as...
}
return (color == "w" ? "b" : "w");
}
- // Can thing on square1 capture (no return) thing on square2?
+ // Can thing on square1 capture (enemy) thing on square2?
canTake([x1, y1], [x2, y2]) {
return (this.getColor(x1, y1) !== this.getColor(x2, y2));
}
if (this.board[i][j] != "" && this.getColor(i, j) == color) {
const allSpecs = this.pieces(color, i, j)
let specs = allSpecs[this.getPieceType(i, j)];
+ if (specs.moveas)
+ specs = allSpecs[specs.moveas];
const attacks = specs.attack || specs.moves;
for (let a of attacks) {
outerLoop: for (let step of a.steps) {
moves = this.capturePostProcess(moves, oppCol);
if (this.options["atomic"])
- this.atomicPostProcess(moves, oppCol);
+ this.atomicPostProcess(moves, color, oppCol);
if (
moves.length > 0 &&
});
}
- atomicPostProcess(moves, oppCol) {
+ atomicPostProcess(moves, color, oppCol) {
moves.forEach(m => {
if (
this.board[m.end.x][m.end.y] != "" &&
[1, 0],
[1, 1]
];
+ let mNext = new Move({
+ start: m.end,
+ end: m.end,
+ appear: [],
+ vanish: []
+ });
for (let step of steps) {
let x = m.end.x + step[0];
let y = this.getY(m.end.y + step[1]);
if (
this.onBoard(x, y) &&
this.board[x][y] != "" &&
+ (x != m.start.x || y != m.start.y) &&
this.getPieceType(x, y) != "p"
) {
- m.vanish.push(
+ mNext.vanish.push(
new PiPo({
p: this.getPiece(x, y),
c: this.getColor(x, y),
);
}
}
- if (!this.options["rifle"])
- m.appear.pop(); //nothing appears
+ if (!this.options["rifle"]) {
+ // The moving piece also vanish
+ mNext.vanish.unshift(
+ new PiPo({
+ x: m.end.x,
+ y: m.end.y,
+ c: color,
+ p: this.getPiece(m.start.x, m.start.y)
+ })
+ );
+ }
+ m.next = mNext;
}
});
}
const color = this.getColor(x, y);
const oppCol = C.GetOppCol(color);
const piece = this.getPieceType(x, y); //ok not cannibal king
- const stepSpec = this.pieces(color, x, y)[piece];
+ const allSpecs = this.pieces(color, x, y);
+ let stepSpec = allSpecs[piece];
+ if (stepSpec.moveas)
+ stepSpec = allSpecs[stepSpec.moveas];
const attacks = stepSpec.attack || stepSpec.moves;
for (let a of attacks) {
outerLoop: for (let step of a.steps) {
return false;
}
+ canStepOver(i, j, p) {
+ // In some variants, objects on boards don't stop movement (Chakart)
+ return this.board[i][j] == "";
+ }
+
// Generic method to find possible moves of "sliding or jumping" pieces
getPotentialMovesOf(piece, [x, y]) {
const color = this.getColor(x, y);
- const stepSpec = this.pieces(color, x, y)[piece];
+ const apparentPiece = this.getPiece(x, y); //how it looks
+ const allSpecs = this.pieces(color, x, y);
+ let stepSpec = allSpecs[piece];
+ if (stepSpec.moveas)
+ stepSpec = allSpecs[stepSpec.moveas];
let moves = [];
// Next 3 for Cylinder mode:
let explored = {};
let stepCounter = 0;
while (
this.onBoard(i, j) &&
- (this.board[i][j] == "" || (i == x && j == y))
+ ((i == x && j == y) || this.canStepOver(i, j, apparentPiece))
) {
if (
type != "attack" &&
) {
if (args.zen && this.isKing(this.getPiece(i, j)))
continue; //king not captured in this way
- const stepSpec =
- this.pieces(args.oppCol, i, j)[this.getPieceType(i, j)];
+ const apparentPiece = this.getPiece(i, j);
+ // Quick check: does this potential attacker target x,y ?
+ if (this.canStepOver(x, y, apparentPiece))
+ continue;
+ const allSpecs = this.pieces(args.oppCol, i, j);
+ let stepSpec = allSpecs[this.getPieceType(i, j)];
+ if (stepSpec.moveas)
+ stepSpec = allSpecs[stepSpec.moveas];
const attacks = stepSpec.attack || stepSpec.moves;
for (let a of attacks) {
for (let s of a.steps) {
s.y == e.y &&
Math.abs(s.x - e.x) == 2 &&
// Next conditions for variants like Atomic or Rifle, Recycle...
- (move.appear.length > 0 && move.appear[0].p == "p") &&
- (move.vanish.length > 0 && move.vanish[0].p == "p")
+ (
+ move.appear.length > 0 &&
+ this.getPieceType(0, 0, move.appear[0].p) == "p"
+ )
+ &&
+ (
+ move.vanish.length > 0 &&
+ this.getPieceType(0, 0, move.vanish[0].p) == "p"
+ )
) {
return {
x: (s.x + e.x) / 2,
return [-1, -1]; //king not found
}
- filterValid(moves) {
+ // Some variants (e.g. Refusal) may need to check opponent moves too
+ filterValid(moves, color) {
if (moves.length == 0)
return [];
- const color = this.turn;
+ if (!color)
+ color = this.turn;
const oppCol = C.GetOppCol(color);
if (this.options["balance"] && [1, 3].includes(this.movesCount)) {
// Forbid moves either giving check or exploding opponent's king:
let square = kingPos,
res = true; //a priori valid
if (m.vanish.some(v => {
- return C.CannibalKings[v.p] && v.c == color;
+ return this.isKing(v.p) && v.c == color;
})) {
// Search king in appear array:
const newKingIdx =
m.appear.findIndex(a => {
- return C.CannibalKings[a.p] && a.c == color;
+ return this.isKing(a.p) && a.c == color;
});
if (newKingIdx >= 0)
square = [m.appear[newKingIdx].x, m.appear[newKingIdx].y];
this.subTurnTeleport = 1;
this.captured = null;
}
- if (this.options["balance"]) {
- if (![1, 3].includes(this.movesCount))
- this.turn = oppCol;
- }
- else {
- if (
+ if (
+ !move.next &&
+ (
(
this.options["doublemove"] &&
this.movesCount >= 1 &&
this.subTurn == 1
- ) ||
+ )
+ ||
(this.options["progressive"] && this.subTurn <= this.movesCount)
+ )
+ ) {
+ const oppKingPos = this.searchKingPos(oppCol);
+ if (
+ oppKingPos[0] >= 0 &&
+ (
+ this.options["taking"] ||
+ !this.underCheck(oppKingPos, color)
+ )
) {
- const oppKingPos = this.searchKingPos(oppCol);
- if (
- oppKingPos[0] >= 0 &&
- (
- this.options["taking"] ||
- !this.underCheck(oppKingPos, color)
- )
- ) {
- this.subTurn++;
- return;
- }
+ this.subTurn++;
+ return;
}
+ }
+ if (this.isLastMove(move)) {
this.turn = oppCol;
+ this.movesCount++;
+ this.subTurn = 1;
}
- this.movesCount++;
- this.subTurn = 1;
+ }
+
+ isLastMove(move) {
+ return (
+ (this.options["balance"] && ![1, 3].includes(this.movesCount)) ||
+ !move.next
+ );
}
// "Stop at the first move found"
playVisual(move, r) {
move.vanish.forEach(v => {
- // TODO: next "if" shouldn't be required
- if (this.g_pieces[v.x][v.y])
- this.g_pieces[v.x][v.y].remove();
+ this.g_pieces[v.x][v.y].remove();
this.g_pieces[v.x][v.y] = null;
});
let chessboard =
const pieceWidth = this.getPieceWidth(r.width);
move.appear.forEach(a => {
this.g_pieces[a.x][a.y] = document.createElement("piece");
- this.g_pieces[a.x][a.y].classList.add(this.pieces()[a.p]["class"]);
- this.g_pieces[a.x][a.y].classList.add(a.c == "w" ? "white" : "black");
+ 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].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);
this.graphUpdateEnlightened();
}
- playPlusVisual(move, r) {
+ // TODO: send stack receive stack, or allow incremental? (good/bad points)
+ buildMoveStack(move, r) {
+ this.moveStack.push(move);
+ this.computeNextMove(move);
this.play(move);
- this.playVisual(move, r);
- this.afterPlay(move); //user method
+ 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 = []
+ }
+ 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 = []
+ });
+ }
+ }
}
- getMaxDistance(rwidth) {
+ // Implemented in variants using (automatic) moveStack
+ computeNextMove(move) {}
+
+ getMaxDistance(r) {
// Works for all rectangular boards:
- return Math.sqrt(rwidth ** 2 + (rwidth / this.size.ratio) ** 2);
+ return Math.sqrt(r.width ** 2 + r.height ** 2);
}
getDomPiece(x, y) {
return (typeof x == "string" ? this.r_pieces : this.g_pieces)[x][y];
}
- animate(move, callback) {
- if (this.noAnimate || move.noAnimate) {
- callback();
- return;
- }
- let initPiece = this.getDomPiece(move.start.x, move.start.y);
- if (!initPiece) { //TODO this shouldn't be required
- callback();
- return;
- }
- // NOTE: cloning generally not required, but light enough, and simpler
+ animateMoving(start, end, drag, segments, cb) {
+ let initPiece = this.getDomPiece(start.x, start.y);
+ // NOTE: cloning often not required, but light enough, and simpler
let movingPiece = initPiece.cloneNode();
initPiece.style.opacity = "0";
let container =
document.getElementById(this.containerId)
const r = container.querySelector(".chessboard").getBoundingClientRect();
- if (typeof move.start.x == "string") {
+ if (typeof start.x == "string") {
// Need to bound width/height (was 100% for reserve pieces)
const pieceWidth = this.getPieceWidth(r.width);
movingPiece.style.width = pieceWidth + "px";
movingPiece.style.height = pieceWidth + "px";
}
- const maxDist = this.getMaxDistance(r.width);
- const pieces = this.pieces();
- if (move.drag) {
- const startCode = this.getPiece(move.start.x, move.start.y);
- movingPiece.classList.remove(pieces[startCode]["class"]);
- movingPiece.classList.add(pieces[move.drag.p]["class"]);
- const apparentColor = this.getColor(move.start.x, move.start.y);
- if (apparentColor != move.drag.c) {
+ const maxDist = this.getMaxDistance(r);
+ const apparentColor = this.getColor(start.x, start.y);
+ const pieces = this.pieces(apparentColor, start.x, start.y);
+ if (drag) {
+ const startCode = this.getPiece(start.x, start.y);
+ 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(move.drag.c));
+ movingPiece.classList.add(C.GetColorClass(drag.c));
}
}
container.appendChild(movingPiece);
const animateSegment = (index, cb) => {
// NOTE: move.drag could be generalized per-segment (usage?)
- const [i1, j1] = move.segments[index][0];
- const [i2, j2] = move.segments[index][1];
+ const [i1, j1] = segments[index][0];
+ const [i2, j2] = segments[index][1];
const dep = this.getPixelPosition(i1, j1, r);
const arr = this.getPixelPosition(i2, j2, r);
movingPiece.style.transitionDuration = "0s";
setTimeout(cb, duration * 1000);
}, 50);
};
- if (!move.segments) {
- move.segments = [
- [[move.start.x, move.start.y], [move.end.x, move.end.y]]
- ];
- }
let index = 0;
const animateSegmentCallback = () => {
- if (index < move.segments.length)
+ if (index < segments.length)
animateSegment(index++, animateSegmentCallback);
else {
movingPiece.remove();
initPiece.style.opacity = "1";
- callback();
+ cb();
}
};
animateSegmentCallback();
}
+ // Input array of objects with at least fields x,y (e.g. PiPo)
+ animateFading(arr, cb) {
+ const animLength = 350; //TODO: 350ms? More? Less?
+ arr.forEach(v => {
+ let fadingPiece = this.getDomPiece(v.x, v.y);
+ fadingPiece.style.transitionDuration = (animLength / 1000) + "s";
+ fadingPiece.style.opacity = "0";
+ });
+ setTimeout(cb, animLength);
+ }
+
+ animate(move, callback) {
+ if (this.noAnimate || move.noAnimate) {
+ callback();
+ return;
+ }
+ let segments = move.segments;
+ if (!segments)
+ segments = [ [[move.start.x, move.start.y], [move.end.x, move.end.y]] ];
+ let targetObj = new TargetObj(callback);
+ if (move.start.x != move.end.x || move.start.y != move.end.y) {
+ targetObj.target++;
+ this.animateMoving(move.start, move.end, move.drag, segments,
+ () => targetObj.increment());
+ }
+ if (move.vanish.length > move.appear.length) {
+ const arr = move.vanish.slice(move.appear.length)
+ // Ignore disappearing pieces hidden by some appearing ones:
+ .filter(v => move.appear.every(a => a.x != v.x || a.y != v.y));
+ if (arr.length > 0) {
+ targetObj.target++;
+ this.animateFading(arr, () => targetObj.increment());
+ }
+ }
+ targetObj.target +=
+ this.customAnimate(move, segments, () => targetObj.increment());
+ if (targetObj.target == 0)
+ callback();
+ }
+
+ // 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();