X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=base_rules.js;h=dac7bb4f91f6b5c9e208a6c748ff47fe407f5263;hb=8f228ec8d91d528be3c72200f5f674058abce31e;hp=07084bf1df622b0126883aa3537c52847b25868f;hpb=082e639a3ed0a38a91fbe8d0e8bfa7b262b5227d;p=xogo.git diff --git a/base_rules.js b/base_rules.js index 07084bf..dac7bb4 100644 --- a/base_rules.js +++ b/base_rules.js @@ -29,16 +29,18 @@ 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 @@ -98,7 +100,7 @@ export default class ChessRules { 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({ @@ -108,9 +110,10 @@ export default class ChessRules { 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; } @@ -172,8 +175,6 @@ export default class ChessRules { // Setup the initial random-or-not (asymmetric-or-not) position genRandInitFen(seed) { - Random.setSeed(seed); - let fen, flags = "0707"; if (!this.options.randomness) // Deterministic: @@ -181,7 +182,8 @@ export default class ChessRules { 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"]) { @@ -289,19 +291,20 @@ export default class ChessRules { 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; @@ -311,7 +314,7 @@ export default class ChessRules { 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]); @@ -319,7 +322,7 @@ export default class ChessRules { } if (emptyCount > 0) // "Flush remainder" - position += format(emptyCount); + position += C.FenEmptySquares(emptyCount); if (i < this.size.y - 1) position += "/"; //separate rows } @@ -368,8 +371,16 @@ export default class ChessRules { ////////////////// // INITIALIZATION - constructor(o) { + constructor(o, genFenOnly) { 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 (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 @@ -455,7 +466,7 @@ export default class ChessRules { const steps = this.pieces(this.playerColor)["p"].attack[0].steps; for (let step of steps) { const x = this.epSquare.x - step[0], - y = this.computeY(this.epSquare.y - step[1]); + y = this.getY(this.epSquare.y - step[1]); if ( this.onBoard(x, y) && this.getColor(x, y) == this.playerColor && @@ -526,7 +537,6 @@ export default class ChessRules { this.initMouseEvents(); const chessboard = document.getElementById(this.containerId).querySelector(".chessboard"); - new ResizeObserver(this.rescale).observe(chessboard); } re_drawBoardElements() { @@ -549,7 +559,7 @@ export default class ChessRules { 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. @@ -581,8 +591,7 @@ export default class ChessRules { let board = ` - `; + 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); @@ -591,16 +600,18 @@ export default class ChessRules { if (this.enlightened && !this.enlightened[ii][jj]) classes += " in-shadow"; // NOTE: x / y reversed because coordinates system is reversed. - board += ``; + board += ` + `; } } - board += ""; + board += ""; return board; } @@ -648,7 +659,7 @@ export default class ChessRules { } } } - if (this.reserve) + if (this.hasReserve) this.re_drawReserve(['w', 'b'], r); } @@ -764,44 +775,48 @@ export default class ChessRules { } } - // 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); } @@ -932,11 +947,6 @@ export default class ChessRules { }; 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]; @@ -965,6 +975,8 @@ export default class ChessRules { 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 @@ -1159,7 +1171,7 @@ export default class ChessRules { // MOVES GENERATION // For Cylinder: get Y coordinate - computeY(y) { + getY(y) { if (!this.options["cylinder"]) return y; let res = y % this.size.y; @@ -1180,13 +1192,13 @@ export default class ChessRules { const attacks = specs.attack || specs.moves; for (let a of attacks) { outerLoop: for (let step of a.steps) { - let [ii, jj] = [i + step[0], this.computeY(j + step[1])]; + let [ii, jj] = [i + step[0], this.getY(j + step[1])]; let stepCounter = 1; while (this.onBoard(ii, jj) && this.board[ii][jj] == "") { if (a.range <= stepCounter++) continue outerLoop; ii += step[0]; - jj = this.computeY(jj + step[1]); + jj = this.getY(jj + step[1]); } if ( this.onBoard(ii, jj) && @@ -1207,7 +1219,7 @@ export default class ChessRules { 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 = []; @@ -1323,7 +1335,7 @@ export default class ChessRules { ]; for (let step of steps) { let x = m.end.x + step[0]; - let y = this.computeY(m.end.y + step[1]); + let y = this.getY(m.end.y + step[1]); if ( this.onBoard(x, y) && this.board[x][y] != "" && @@ -1455,7 +1467,7 @@ export default class ChessRules { if (a.range <= stepCounter++) continue outerLoop; i += step[0]; - j = this.computeY(j + step[1]); + j = this.getY(j + step[1]); } if ( this.onBoard(i, j) && @@ -1474,23 +1486,49 @@ export default class ChessRules { const color = this.getColor(x, y); const stepSpec = this.pieces(color, x, y)[piece]; let moves = []; - let explored = {}; //for Cylinder mode + // Next 3 for Cylinder mode: + let explored = {}; + let segments = []; + let segStart = []; + + const addMove = (start, end) => { + let newMove = this.getBasicMove(start, end); + if (segments.length > 0) { + newMove.segments = JSON.parse(JSON.stringify(segments)); + newMove.segments.push([[segStart[0], segStart[1]], [end[0], end[1]]]); + } + moves.push(newMove); + }; const findAddMoves = (type, stepArray) => { for (let s of stepArray) { - // TODO: if jump in y (computeY, Cylinder), move.segments outerLoop: for (let step of s.steps) { - let [i, j] = [x + step[0], this.computeY(y + step[1])]; - let stepCounter = 1; - while (this.onBoard(i, j) && this.board[i][j] == "") { - if (type != "attack" && !explored[i + "." + j]) { + segments = []; + segStart = [x, y]; + let [i, j] = [x, y]; + let stepCounter = 0; + while ( + this.onBoard(i, j) && + (this.board[i][j] == "" || (i == x && j == y)) + ) { + if ( + type != "attack" && + !explored[i + "." + j] && + (i != x || j != y) + ) { explored[i + "." + j] = true; - moves.push(this.getBasicMove([x, y], [i, j])); + addMove([x, y], [i, j]); } if (s.range <= stepCounter++) continue outerLoop; + const oldIJ = [i, j]; i += step[0]; - j = this.computeY(j + step[1]); + j = this.getY(j + step[1]); + if (Math.abs(j - oldIJ[1]) > 1) { + // Boundary between segments (cylinder mode) + segments.push([[segStart[0], segStart[1]], oldIJ]); + segStart = [i, j]; + } } if (!this.onBoard(i, j)) continue; @@ -1511,7 +1549,7 @@ export default class ChessRules { ) ) { explored[i + "." + j] = true; - moves.push(this.getBasicMove([x, y], [i, j])); + addMove([x, y], [i, j]); } } } @@ -1551,7 +1589,7 @@ export default class ChessRules { if (!C.CompatibleStep([i, j], [x, y], s, a.range)) continue; // Finally verify that nothing stand in-between - let [ii, jj] = [i + s[0], this.computeY(j + s[1])]; + let [ii, jj] = [i + s[0], this.getY(j + s[1])]; let stepCounter = 1; while ( this.onBoard(ii, jj) && @@ -1559,7 +1597,7 @@ export default class ChessRules { (ii != x || jj != y) //condition to attack empty squares too ) { ii += s[0]; - jj = this.computeY(jj + s[1]); + jj = this.getY(jj + s[1]); } if (ii == x && jj == y) { if (args.zen) @@ -1706,7 +1744,7 @@ export default class ChessRules { if ( !!this.epSquare && this.epSquare.x == x + shiftX && - Math.abs(this.computeY(this.epSquare.y - y)) == 1 && + Math.abs(this.getY(this.epSquare.y - y)) == 1 && this.getColor(x, this.epSquare.y) == oppCol //Doublemove guard... ) { const [epx, epy] = [this.epSquare.x, this.epSquare.y]; @@ -2112,9 +2150,7 @@ export default class ChessRules { 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 = @@ -2161,10 +2197,6 @@ export default class ChessRules { 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"; @@ -2204,7 +2236,7 @@ export default class ChessRules { // TODO: unclear why we need this new delay below: setTimeout(() => { movingPiece.style.transitionDuration = duration + "s"; - // movingPiece is child of container: no need to adjust cordinates + // movingPiece is child of container: no need to adjust coordinates movingPiece.style.transform = `translate(${arr[0]}px, ${arr[1]}px)`; setTimeout(cb, duration * 1000); }, 50);