{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["teleport"] && this.subTurnTeleport == 2 &&
this.board[coords.x][coords.y] == ""
) {
- return new Move({
+ let res = new Move({
start: {x: this.captured.x, y: this.captured.y},
appear: [
new PiPo({
p: this.captured.p
})
],
- vanish: [],
- drag: {c: this.captured.c, p: this.captured.p}
+ vanish: []
});
+ res.drag = {c: this.captured.c, p: this.captured.p};
+ return res;
}
return null;
}
// 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"]) {
return fen;
}
+ static FenEmptySquares(count) {
+ // if more than 9 consecutive free spaces, break the integer,
+ // otherwise FEN parsing will fail.
+ if (count <= 9)
+ return count;
+ // Most boards of size < 18:
+ if (count <= 18)
+ return "9" + (count - 9);
+ // Except Gomoku:
+ return "99" + (count - 18);
+ }
+
// Position part of the FEN string
getPosition() {
- const format = (count) => {
- // if more than 9 consecutive free spaces, break the integer,
- // otherwise FEN parsing will fail.
- if (count <= 9)
- return count;
- // Most boards of size < 18:
- if (count <= 18)
- return "9" + (count - 9);
- // Except Gomoku:
- return "99" + (count - 18);
- };
let position = "";
for (let i = 0; i < this.size.y; i++) {
let emptyCount = 0;
else {
if (emptyCount > 0) {
// Add empty squares in-between
- position += format(emptyCount);
+ position += C.FenEmptySquares(emptyCount);
emptyCount = 0;
}
position += this.board2fen(this.board[i][j]);
}
if (emptyCount > 0)
// "Flush remainder"
- position += format(emptyCount);
+ position += C.FenEmptySquares(emptyCount);
if (i < this.size.y - 1)
position += "/"; //separate rows
}
constructor(o) {
this.options = o.options;
+ // Fill missing options (always the case if random challenge)
+ (V.Options.select || []).concat(V.Options.input || []).forEach(opt => {
+ 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
this.initMouseEvents();
const chessboard =
document.getElementById(this.containerId).querySelector(".chessboard");
- new ResizeObserver(this.rescale).observe(chessboard);
}
re_drawBoardElements() {
cbHeight = Math.min(window.innerHeight, 767);
cbWidth = cbHeight * this.size.ratio;
}
- if (this.reserve) {
+ if (this.hasReserve) {
const sqSize = cbWidth / this.size.y;
// NOTE: allocate space for reserves (up/down) even if they are empty
// Cannot use getReserveSquareSize() here, but sqSize is an upper bound.
let board = `
<svg
viewBox="0 0 80 80"
- class="chessboard_SVG">
- <g>`;
+ class="chessboard_SVG">`;
for (let i=0; i < this.size.x; i++) {
for (let j=0; j < this.size.y; j++) {
const ii = (flipped ? this.size.x - 1 - i : i);
if (this.enlightened && !this.enlightened[ii][jj])
classes += " in-shadow";
// NOTE: x / y reversed because coordinates system is reversed.
- board += `<rect
- class="${classes}"
- id="${this.coordsToId({x: ii, y: jj})}"
- width="10"
- height="10"
- x="${10*j}"
- y="${10*i}" />`;
+ board += `
+ <rect
+ class="${classes}"
+ id="${this.coordsToId({x: ii, y: jj})}"
+ width="10"
+ height="10"
+ x="${10*j}"
+ y="${10*i}"
+ />`;
}
}
- board += "</g></svg>";
+ board += "</svg>";
return board;
}
}
}
}
- if (this.reserve)
+ if (this.hasReserve)
this.re_drawReserve(['w', 'b'], r);
}
}
}
- // After resize event: no need to destroy/recreate pieces
- rescale() {
- const container = document.getElementById(this.containerId);
- if (!container)
- return; //useful at initial loading
- let chessboard = container.querySelector(".chessboard");
+ // Resize board: no need to destroy/recreate pieces
+ rescale(mode) {
+ let chessboard =
+ document.getElementById(this.containerId).querySelector(".chessboard");
const r = chessboard.getBoundingClientRect();
- const newRatio = r.width / r.height;
- let newWidth = r.width,
- newHeight = r.height;
- if (newRatio > this.size.ratio) {
- newWidth = r.height * this.size.ratio;
- chessboard.style.width = newWidth + "px";
- }
- else if (newRatio < this.size.ratio) {
- newHeight = r.width / this.size.ratio;
- chessboard.style.height = newHeight + "px";
- }
+ const multFact = (mode == "up" ? 1.05 : 0.95);
+ let [newWidth, newHeight] = [multFact * r.width, multFact * r.height];
+ // Stay in window:
+ if (newWidth > window.innerWidth) {
+ newWidth = window.innerWidth;
+ newHeight = newWidth / this.size.ratio;
+ }
+ if (newHeight > window.innerHeight) {
+ newHeight = window.innerHeight;
+ newWidth = newHeight * this.size.ratio;
+ }
+ chessboard.style.width = newWidth + "px";
+ chessboard.style.height = newHeight + "px";
const newX = (window.innerWidth - newWidth) / 2;
chessboard.style.left = newX + "px";
const newY = (window.innerHeight - newHeight) / 2;
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++) {
- for (let j=0; j < this.size.y; j++) {
- if (this.g_pieces[i][j]) {
- // NOTE: could also use CSS transform "scale"
- this.g_pieces[i][j].style.width = pieceWidth + "px";
- this.g_pieces[i][j].style.height = pieceWidth + "px";
- const [ip, jp] = this.getPixelPosition(i, j, newR);
- // Translate coordinates to use chessboard as reference:
- this.g_pieces[i][j].style.transform =
- `translate(${ip - newX}px,${jp - newY}px)`;
+ // NOTE: next "if" for variants which use squares filling
+ // instead of "physical", moving pieces
+ if (this.g_pieces) {
+ for (let i=0; i < this.size.x; i++) {
+ for (let j=0; j < this.size.y; j++) {
+ if (this.g_pieces[i][j]) {
+ // NOTE: could also use CSS transform "scale"
+ this.g_pieces[i][j].style.width = pieceWidth + "px";
+ this.g_pieces[i][j].style.height = pieceWidth + "px";
+ const [ip, jp] = this.getPixelPosition(i, j, newR);
+ // Translate coordinates to use chessboard as reference:
+ this.g_pieces[i][j].style.transform =
+ `translate(${ip - newX}px,${jp - newY}px)`;
+ }
}
}
}
- if (this.reserve)
+ if (this.hasReserve)
this.rescaleReserve(newR);
}
};
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];
document.addEventListener("mousedown", mousedown);
document.addEventListener("mousemove", mousemove);
document.addEventListener("mouseup", mouseup);
+ document.addEventListener("wheel",
+ (e) => this.rescale(e.deltaY < 0 ? "up" : "down"));
}
if ('ontouchstart' in window) {
// https://stackoverflow.com/a/42509310/12660887
}
static GetColorClass(c) {
- return (c == 'w' ? "white" : "black");
+ if (c == 'w')
+ return "white";
+ if (c == 'b')
+ return "black";
+ return ""; //unidentified color
}
// Assume square i,j isn't empty
getDropMovesFrom([c, p]) {
// NOTE: by design, this.reserve[c][p] >= 1 on user click
- // (but not necessarily otherwise)
+ // (but not necessarily otherwise: atLeastOneMove() etc)
if (this.reserve[c][p] == 0)
return [];
let moves = [];
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 =
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");
+ 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);
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
let movingPiece = initPiece.cloneNode();
initPiece.style.opacity = "0";