// NOTE: ChessRules is aliased as window.C, and variants as window.V
export default class ChessRules {
+ static get Aliases() {
+ return {'C': ChessRules};
+ }
+
/////////////////////////
// VARIANT SPECIFICATIONS
{ label: "Asymmetric random", value: 2 }
]
}],
- check: [{
- label: "Capture king?",
- defaut: false,
- variable: "taking"
- }],
+ check: [
+ {
+ label: "Capture king",
+ defaut: false,
+ variable: "taking"
+ },
+ {
+ label: "Falling pawn",
+ defaut: false,
+ variable: "pawnfall"
+ }
+ ],
// Game modifiers (using "elementary variants"). Default: false
styles: [
"atomic",
// Fen string fully describes the game state
constructor(o) {
- window.C = ChessRules; //easier alias
-
this.options = o.options;
this.playerColor = o.color;
this.afterPlay = o.afterPlay;
// Apply diff this.enlightened --> newEnlightened on board
graphUpdateEnlightened(newEnlightened) {
- let container = document.getElementById(this.containerId);
- const r = container.getBoundingClientRect();
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
+ const r = chessboard.getBoundingClientRect();
const pieceWidth = this.getPieceWidth(r.width);
for (let x=0; x<this.size.x; x++) {
for (let y=0; y<this.size.y; y++) {
let elt = document.getElementById(this.coordsToId([x, y]));
elt.classList.remove("in-shadow");
if (this.board[x][y] != "") {
- const color = this.getColor(i, j);
- const piece = this.getPiece(i, j);
+ const color = this.getColor(x, y);
+ const piece = this.getPiece(x, y);
this.g_pieces[x][y] = document.createElement("piece");
let newClasses = [
this.pieces()[piece]["class"],
const [ip, jp] = this.getPixelPosition(x, y, r);
this.g_pieces[x][y].style.transform =
`translate(${ip}px,${jp}px)`;
- container.appendChild(this.g_pieces[x][y]);
+ chessboard.appendChild(this.g_pieces[x][y]);
}
}
}
window.onresize = () => this.re_drawBoardElements();
this.re_drawBoardElements();
this.initMouseEvents();
- const container = document.getElementById(this.containerId);
- new ResizeObserver(this.rescale).observe(container);
+ const chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
+ new ResizeObserver(this.rescale).observe(chessboard);
}
re_drawBoardElements() {
const board = this.getSvgChessboard();
const oppCol = C.GetOppCol(this.playerColor);
- let container = document.getElementById(this.containerId);
- container.innerHTML = "";
- container.insertAdjacentHTML('beforeend', board);
- let cb = container.querySelector("#" + this.containerId + "_SVG");
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
+ chessboard.innerHTML = "";
+ chessboard.insertAdjacentHTML('beforeend', board);
const aspectRatio = this.size.y / this.size.x;
// Compare window ratio width / height to aspectRatio:
const windowRatio = window.innerWidth / window.innerHeight;
cbWidth = cbHeight * aspectRatio;
}
}
- container.style.width = cbWidth + "px";
- container.style.height = cbHeight + "px";
+ chessboard.style.width = cbWidth + "px";
+ chessboard.style.height = cbHeight + "px";
// Center chessboard:
const spaceLeft = (window.innerWidth - cbWidth) / 2,
spaceTop = (window.innerHeight - cbHeight) / 2;
- container.style.left = spaceLeft + "px";
- container.style.top = spaceTop + "px";
+ chessboard.style.left = spaceLeft + "px";
+ chessboard.style.top = spaceTop + "px";
// Give sizes instead of recomputing them,
// because chessboard might not be drawn yet.
this.setupPieces({
<svg
viewBox="0 0 80 80"
version="1.1"
- id="${this.containerId}_SVG">
+ class="chessboard_SVG">
<g>`;
for (let i=0; i < sizeX; i++) {
for (let j=0; j < sizeY; j++) {
}
}
else this.g_pieces = ArrayFun.init(this.size.x, this.size.y, null);
- let container = document.getElementById(this.containerId);
- if (!r) r = container.getBoundingClientRect();
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
+ if (!r) r = chessboard.getBoundingClientRect();
const pieceWidth = this.getPieceWidth(r.width);
for (let i=0; i < this.size.x; i++) {
for (let j=0; j < this.size.y; j++) {
this.g_pieces[i][j].style.height = pieceWidth + "px";
const [ip, jp] = this.getPixelPosition(i, j, r);
this.g_pieces[i][j].style.transform = `translate(${ip}px,${jp}px)`;
- container.appendChild(this.g_pieces[i][j]);
+ chessboard.appendChild(this.g_pieces[i][j]);
}
}
}
}
}
else this.r_pieces = { 'w': {}, 'b': {} };
- if (!r) {
- const container = document.getElementById(this.containerId);
- r = container.getBoundingClientRect();
- }
- const epsilon = 1e-4; //fix display bug on Firefox at least
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
+ if (!r) r = chessboard.getBoundingClientRect();
for (let c of colors) {
if (!this.reserve[c]) continue;
const nbR = this.getNbReservePieces(c);
rcontainer.classList.add("reserves");
rcontainer.style.left = i0 + "px";
rcontainer.style.top = j0 + "px";
- rcontainer.style.width = (nbR * sqResSize) + "px";
+ // NOTE: +1 fix display bug on Firefox at least
+ rcontainer.style.width = (nbR * sqResSize + 1) + "px";
rcontainer.style.height = sqResSize + "px";
- document.getElementById("boardContainer").appendChild(rcontainer);
+ chessboard.appendChild(rcontainer);
for (let p of Object.keys(this.reserve[c])) {
if (this.reserve[c][p] == 0) continue;
let r_cell = document.createElement("div");
r_cell.id = this.coordsToId([c, p]);
r_cell.classList.add("reserve-cell");
- r_cell.style.width = (sqResSize - epsilon) + "px";
- r_cell.style.height = (sqResSize - epsilon) + "px";
+ r_cell.style.width = sqResSize + "px";
+ r_cell.style.height = sqResSize + "px";
rcontainer.appendChild(r_cell);
let piece = document.createElement("piece");
const pieceSpec = this.pieces(c)[p];
}
updateReserve(color, piece, count) {
- if (this.options["cannibal"] && C.CannibalKing[piece])
+ if (this.options["cannibal"] && C.CannibalKings[piece])
piece = "k"; //capturing cannibal king: back to king form
const oldCount = this.reserve[color][piece];
this.reserve[color][piece] = count;
// After resize event: no need to destroy/recreate pieces
rescale() {
- let container = document.getElementById(this.containerId);
+ const container = document.getElementById(this.containerId);
if (!container) return; //useful at initial loading
- const r = container.getBoundingClientRect();
+ let chessboard = container.querySelector(".chessboard");
+ const r = chessboard.getBoundingClientRect();
const newRatio = r.width / r.height;
const aspectRatio = this.size.y / this.size.x;
let newWidth = r.width,
newHeight = r.height;
if (newRatio > aspectRatio) {
newWidth = r.height * aspectRatio;
- container.style.width = newWidth + "px";
+ chessboard.style.width = newWidth + "px";
}
else if (newRatio < aspectRatio) {
newHeight = r.width / aspectRatio;
- container.style.height = newHeight + "px";
+ chessboard.style.height = newHeight + "px";
}
const newX = (window.innerWidth - newWidth) / 2;
- container.style.left = newX + "px";
+ chessboard.style.left = newX + "px";
const newY = (window.innerHeight - newHeight) / 2;
- container.style.top = newY + "px";
+ chessboard.style.top = newY + "px";
const newR = { x: newX, y: newY, width: newWidth, height: newHeight };
const pieceWidth = this.getPieceWidth(newWidth);
for (let i=0; i < this.size.x; i++) {
}
rescaleReserve(r) {
- const epsilon = 1e-4;
for (let c of ['w','b']) {
if (!this.reserve[c]) continue;
const nbR = this.getNbReservePieces(c);
let rcontainer = document.getElementById("reserves_" + c);
rcontainer.style.left = i0 + "px";
rcontainer.style.top = j0 + "px";
- rcontainer.style.width = (nbR * sqResSize) + "px";
+ rcontainer.style.width = (nbR * sqResSize + 1) + "px";
rcontainer.style.height = sqResSize + "px";
// And then reserve cells:
const rpieceWidth = this.getReserveSquareSize(r.width, nbR);
Object.keys(this.reserve[c]).forEach(p => {
if (this.reserve[c][p] == 0) return;
let r_cell = document.getElementById(this.coordsToId([c, p]));
- r_cell.style.width = (sqResSize - epsilon) + "px";
- r_cell.style.height = (sqResSize - epsilon) + "px";
+ r_cell.style.width = sqResSize + "px";
+ r_cell.style.height = sqResSize + "px";
});
}
}
- // Return the absolute pixel coordinates given current position.
+ // Return the absolute pixel coordinates (on board) given current position.
// Our coordinate system differs from CSS one (x <--> y).
// We return here the CSS coordinates (more useful).
getPixelPosition(i, j, r) {
}
initMouseEvents() {
- let container = document.getElementById(this.containerId);
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
const getOffset = e => {
- if (e.clientX) return {x: e.clientX, y: e.clientY}; //Mouse
+ if (e.clientX)
+ // Mouse
+ return {x: e.clientX, y: e.clientY};
let touchLocation = null;
if (e.targetTouches && e.targetTouches.length >= 1)
// Touch screen, dragstart
// Touch screen, dragend
touchLocation = e.changedTouches[0];
if (touchLocation)
- return {x: touchLocation.pageX, y: touchLocation.pageY};
- return [0, 0]; //Big trouble here =)
+ return {x: touchLocation.clientX, y: touchLocation.clientY};
+ return [0, 0]; //shouldn't reach here =)
}
const centerOnCursor = (piece, e) => {
const centerShift = sqSize / 2;
const offset = getOffset(e);
- piece.style.left = (offset.x - centerShift) + "px";
- piece.style.top = (offset.y - centerShift) + "px";
+ piece.style.left = (offset.x - r.x - centerShift) + "px";
+ piece.style.top = (offset.y - r.y - centerShift) + "px";
}
let start = null,
startPiece, curPiece = null,
sqSize;
const mousedown = (e) => {
- r = container.getBoundingClientRect();
+ // Disable zoom on smartphones:
+ if (e.touches && e.touches.length > 1) e.preventDefault();
+ r = chessboard.getBoundingClientRect();
sqSize = this.getSquareWidth(r.width);
const square = this.idToCoords(e.target.id);
if (square) {
curPiece.style.width = sqSize + "px";
curPiece.style.height = sqSize + "px";
centerOnCursor(curPiece, e);
- document.getElementById("boardContainer").appendChild(curPiece);
+ chessboard.appendChild(curPiece);
startPiece.style.opacity = "0.4";
- container.style.cursor = "none";
+ chessboard.style.cursor = "none";
}
}
}
e.preventDefault();
centerOnCursor(curPiece, e);
}
+ else if (e.changedTouches && e.changedTouches.length >= 1)
+ // Attempt to prevent horizontal swipe...
+ e.preventDefault();
};
const mouseup = (e) => {
- const newR = container.getBoundingClientRect();
+ const newR = chessboard.getBoundingClientRect();
if (newR.width != r.width || newR.height != r.height) {
this.rescale();
return;
const [x, y] = [start.x, start.y];
start = null;
e.preventDefault();
- container.style.cursor = "pointer";
+ chessboard.style.cursor = "pointer";
startPiece.style.opacity = "1";
const offset = getOffset(e);
const landingElt = document.elementFromPoint(offset.x, offset.y);
document.addEventListener("mouseup", mouseup);
}
if ('ontouchstart' in window) {
- document.addEventListener("touchstart", mousedown);
- document.addEventListener("touchmove", mousemove);
- document.addEventListener("touchend", mouseup);
+ // https://stackoverflow.com/a/42509310/12660887
+ document.addEventListener("touchstart", mousedown, {passive: false});
+ document.addEventListener("touchmove", mousemove, {passive: false});
+ document.addEventListener("touchend", mouseup, {passive: false});
}
+ // TODO: onpointerdown/move/up ? See reveal.js /controllers/touch.js
}
showChoices(moves, r) {
let container = document.getElementById(this.containerId);
+ let chessboard = container.querySelector(".chessboard");
let choices = document.createElement("div");
choices.id = "choices";
choices.style.width = r.width + "px";
choices.style.height = r.height + "px";
choices.style.left = r.x + "px";
choices.style.top = r.y + "px";
- container.style.opacity = "0.5";
- let boardContainer = document.getElementById("boardContainer");
- boardContainer.appendChild(choices);
+ chessboard.style.opacity = "0.5";
+ container.appendChild(choices);
const squareWidth = this.getSquareWidth(r.width);
const firstUpLeft = (r.width - (moves.length * squareWidth)) / 2;
const firstUpTop = (r.height - squareWidth) / 2;
const color = moves[0].appear[0].c;
const callback = (m) => {
- container.style.opacity = "1";
- boardContainer.removeChild(choices);
+ chessboard.style.opacity = "1";
+ container.removeChild(choices);
this.playPlusVisual(m, r);
}
for (let i=0; i < moves.length; i++) {
return; //king isn't captured this way
}
const steps = pieces[p].attack || pieces[p].steps;
+ if (!steps) return; //cannibal king for example (TODO...)
const range = pieces[p].range;
steps.forEach(s => {
// From x,y: revert step
return !!enpassantMove ? [enpassantMove] : [];
}
- // Consider all potential promotions:
+ // Consider all potential promotions.
+ // NOTE: "promotions" arg = special override for Hiddenqueen variant
addPawnMoves([x1, y1], [x2, y2], moves, promotions) {
let finalPieces = ["p"];
const color = this.getColor(x1, y1);
+ const oppCol = C.GetOppCol(color);
const lastRank = (color == "w" ? 0 : this.size.x - 1);
- if (x2 == lastRank && (!this.options["rifle"] || this.board[x2][y2] == ""))
- {
- // promotions arg: special override for Hiddenqueen variant
- if (promotions) finalPieces = promotions;
+ const promotionOk =
+ x2 == lastRank && (!this.options["rifle"] || this.board[x2][y2] == "");
+ if (promotionOk && !this.options["pawnfall"]) {
+ if (
+ this.options["cannibal"] &&
+ this.board[x2][y2] != "" &&
+ this.getColor(x2, y2) == oppCol
+ ) {
+ finalPieces = [this.getPieceType(x2, y2)];
+ }
+ else if (promotions) finalPieces = promotions;
else if (this.pawnSpecs.promotions)
finalPieces = this.pawnSpecs.promotions;
}
for (let piece of finalPieces) {
- const tr = (piece != "p" ? { c: color, p: piece } : null);
- moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
+ const tr = !this.options["pawnfall"] && piece != "p"
+ ? { c: color, p: piece }
+ : null;
+ let newMove = this.getBasicMove([x1, y1], [x2, y2], tr);
+ if (promotionOk && this.options["pawnfall"]) {
+ newMove.appear.shift();
+ newMove.pawnfall = true; //required in prePlay()
+ }
+ moves.push(newMove);
}
}
// Is (king at) given position under check by "color" ?
underCheck([x, y], color) {
- if (this.taking || this.options["dark"]) return false;
+ if (this.options["taking"] || this.options["dark"]) return false;
color = color || C.GetOppCol(this.getColor(x, y));
const pieces = this.pieces(color);
return Object.keys(pieces).some(p => {
return res;
});
}
- if (this.taking || this.options["dark"]) return moves;
+ if (this.options["taking"] || this.options["dark"]) return moves;
const kingPos = this.searchKingPos(color);
let filtered = {}; //avoid re-checking similar moves (promotions...)
return moves.filter(m => {
}
}
const minSize = Math.min(move.appear.length, move.vanish.length);
- if (this.hasReserve) {
+ if (this.hasReserve && !move.pawnfall) {
const color = this.turn;
for (let i=minSize; i<move.appear.length; i++) {
// Something appears = dropped on board (some exceptions, Chakart...)
this.g_pieces[v.x][v.y] = null;
}
});
- let container = document.getElementById(this.containerId);
- if (!r) r = container.getBoundingClientRect();
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
+ if (!r) r = chessboard.getBoundingClientRect();
const pieceWidth = this.getPieceWidth(r.width);
move.appear.forEach(a => {
if (this.enlightened && !this.enlightened[a.x][a.y]) return;
this.g_pieces[a.x][a.y].style.height = pieceWidth + "px";
const [ip, jp] = this.getPixelPosition(a.x, a.y, r);
this.g_pieces[a.x][a.y].style.transform = `translate(${ip}px,${jp}px)`;
- container.appendChild(this.g_pieces[a.x][a.y]);
+ chessboard.appendChild(this.g_pieces[a.x][a.y]);
});
}
nbR++;
}
const rsqSize = this.getReserveSquareSize(r.width, nbR);
- return [ridx * rsqSize, rsqSize]; //slightly inaccurate... TODO?
+ return [-ridx * rsqSize, rsqSize]; //slightly inaccurate... TODO?
}
animate(move, callback) {
const dropMove = (typeof i1 == "string");
const startArray = (dropMove ? this.r_pieces : this.g_pieces);
let startPiece = startArray[i1][j1];
- let container = document.getElementById(this.containerId);
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
const clonePiece = (
!dropMove &&
this.options["rifle"] ||
startPiece.classList.add(pieces[this.captured.p]["class"]);
// Color: OK
}
- container.appendChild(startPiece);
+ chessboard.appendChild(startPiece);
}
const [i2, j2] = [move.end.x, move.end.y];
let startCoords;
];
}
else startCoords = [i1, j1];
- const r = container.getBoundingClientRect();
+ const r = chessboard.getBoundingClientRect();
const arrival = this.getPixelPosition(i2, j2, r); //TODO: arrival on drop?
let rs = [0, 0];
if (dropMove) rs = this.getReserveShift(i1, j1, r);
const maxDist = Math.sqrt((this.size.x - 1)** 2 + (this.size.y - 1) ** 2);
const multFact = (distance - 1) / (maxDist - 1); //1 == minDist
const duration = 0.2 + multFact * 0.3;
+ const initTransform = startPiece.style.transform;
startPiece.style.transform =
`translate(${arrival[0] + rs[0]}px, ${arrival[1] + rs[1]}px)`;
startPiece.style.transitionDuration = duration + "s";
if (this.options["rifle"]) startArray[i1][j1].style.opacity = "1";
startPiece.remove();
}
+ else {
+ startPiece.style.transform = initTransform;
+ startPiece.style.transitionDuration = "0s";
+ }
callback();
},
duration * 1000
playReceivedMove(moves, callback) {
const launchAnimation = () => {
- const r =
- document.getElementById(this.containerId).getBoundingClientRect();
+ const r = container.querySelector(".chessboard").getBoundingClientRect();
const animateRec = i => {
this.animate(moves[i], () => {
this.playVisual(moves[i], r);
};
animateRec(0);
};
- const checkDisplayThenAnimate = () => {
- if (boardContainer.style.display == "none") {
+ // 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";
- boardContainer.style.display = "block";
+ container.style.display = "block";
setTimeout(launchAnimation, 700);
}
- else launchAnimation(); //focused user!
+ else setTimeout(launchAnimation, delay || 0);
};
- let boardContainer = document.getElementById("boardContainer");
+ let container = document.getElementById(this.containerId);
if (document.hidden) {
document.onvisibilitychange = () => {
document.onvisibilitychange = undefined;
- checkDisplayThenAnimate();
+ checkDisplayThenAnimate(700);
};
}
else checkDisplayThenAnimate();