X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=base_rules.js;h=d9adc0d93f3c131e7d8015f60fac63d8bbe51a8c;hb=901d9d44ae85571ea671f3f2dbe26f81f0d90d66;hp=5eb4d773c008f390cb5e7f1b6aa600ef20827ae8;hpb=e7b64798015851c120b9e8b4872297cfbaa0cb15;p=xogo.git diff --git a/base_rules.js b/base_rules.js index 5eb4d77..d9adc0d 100644 --- a/base_rules.js +++ b/base_rules.js @@ -197,8 +197,19 @@ export default class ChessRules { 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: @@ -206,7 +217,6 @@ export default class ChessRules { 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) @@ -216,9 +226,7 @@ export default class ChessRules { flags += flags; break; } - let positions = ArrayFun.range(8); - // Get random squares for bishops let randIndex = 2 * Random.randInt(4); const bishop1Pos = positions[randIndex]; @@ -228,7 +236,6 @@ export default class ChessRules { // 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]; @@ -236,18 +243,15 @@ export default class ChessRules { 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"; @@ -266,19 +270,7 @@ export default class ChessRules { " 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 @@ -296,23 +288,28 @@ export default class ChessRules { // 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) { @@ -353,10 +350,6 @@ export default class ChessRules { return position; } - getTurnFen() { - return this.turn; - } - // Flags part of the FEN string getFlagsFen() { return ["w", "b"].map(c => { @@ -367,17 +360,22 @@ export default class ChessRules { // 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 "-"; @@ -405,16 +403,18 @@ export default class ChessRules { 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.graphicalInit(); } @@ -453,7 +453,7 @@ export default class ChessRules { this.setFlags(fenParsed.flags); if (this.hasEnpassant) this.epSquare = this.getEpSquare(fenParsed.enpassant); - if (this.hasReserve) + if (this.hasReserve && !this.isDiagram) this.initReserves(fenParsed.reserve); if (this.options["crazyhouse"]) this.initIspawn(fenParsed.ispawn); @@ -557,12 +557,14 @@ export default class ChessRules { // VISUAL METHODS graphicalInit() { - // NOTE: not window.onresize = this.re_drawBoardElts because scope (this) - window.onresize = () => this.re_drawBoardElements(); - this.re_drawBoardElements(); - this.initMouseEvents(); - const chessboard = - document.getElementById(this.containerId).querySelector(".chessboard"); + const g_init = () => { + this.re_drawBoardElements(); + if (!this.isDiagram && !this.mouseListeners && !this.touchListeners) + this.initMouseEvents(); + }; + let container = document.getElementById(this.containerId); + this.windowResizeObs = new ResizeObserver(g_init); + this.windowResizeObs.observe(container); } re_drawBoardElements() { @@ -587,7 +589,7 @@ export default class ChessRules { cbHeight = Math.min(rc.height, 767); cbWidth = cbHeight * vRatio; } - if (this.hasReserve) { + if (this.hasReserve && !this.isDiagram) { 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. @@ -644,46 +646,62 @@ export default class ChessRules { } setupPieces(r) { - if (this.g_pieces) { - // Refreshing: delete old pieces first - for (let i=0; i { + this[arrName][i][j] = document.createElement("piece"); + C.AddClass_es(this[arrName][i][j], classes); + this[arrName][i][j].style.width = pieceWidth + "px"; + this[arrName][i][j].style.height = pieceWidth + "px"; + let [ip, jp] = this.getPixelPosition(i, j, r); + // Translate coordinates to use chessboard as reference: + this[arrName][i][j].style.transform = + `translate(${ip - r.x}px,${jp - r.y}px)`; + chessboard.appendChild(this[arrName][i][j]); + }; + const conditionalReset = (arrName) => { + if (this[arrName]) { + // Refreshing: delete old pieces first. This isn't necessary, + // but simpler (this method isn't called many times) + for (let i=0; i addPiece(i, j, arrName, "mark")); + }; + if (this.marks) + conditionalReset("d_pieces"); + conditionalReset("g_pieces"); for (let i=0; i < this.size.x; i++) { for (let j=0; j < this.size.y; j++) { if (this.board[i][j] != "") { const color = this.getColor(i, j); const piece = this.getPiece(i, j); - this.g_pieces[i][j] = document.createElement("piece"); - C.AddClass_es(this.g_pieces[i][j], - this.pieces(color, i, j)[piece]["class"]); + addPiece(i, j, "g_pieces", 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"; - let [ip, jp] = this.getPixelPosition(i, j, r); - // Translate coordinates to use chessboard as reference: - this.g_pieces[i][j].style.transform = - `translate(${ip - r.x}px,${jp - r.y}px)`; if (this.enlightened && !this.enlightened[i][j]) this.g_pieces[i][j].classList.add("hidden"); - chessboard.appendChild(this.g_pieces[i][j]); + } + if (this.marks && this.d_pieces[i][j]) { + let classes = ["mark"]; + if (this.board[i][j] != "") + classes.push("transparent"); + addPiece(i, j, "d_pieces", classes); } } } - if (this.hasReserve) + if (this.hasReserve && !this.isDiagram) this.re_drawReserve(['w', 'b'], r); } @@ -996,6 +1014,10 @@ export default class ChessRules { } removeListeners() { + let container = document.getElementById(this.containerId); + this.windowResizeObs.unobserve(container); + if (this.isDiagram) + return; //no listeners in this case if ('onmousedown' in window) { this.mouseListeners.forEach(ml => { document.removeEventListener(ml.type, ml.listener); @@ -1487,7 +1509,7 @@ export default class ChessRules { if (piece == "p" && this.hasEnpassant && this.epSquare) Array.prototype.push.apply(moves, this.getEnpassantCaptures([x, y])); if ( - piece == "k" && this.hasCastle && + this.isKing(0, 0, piece) && this.hasCastle && this.castleFlags[color || this.turn].some(v => v < this.size.y) ) { Array.prototype.push.apply(moves, this.getCastleMoves([x, y])); @@ -1607,7 +1629,7 @@ export default class ChessRules { m.appear.shift(); return; } - let finalPieces = ["p"]; + let finalPieces; if ( this.options["cannibal"] && this.board[x2][y2] != "" && @@ -1621,12 +1643,9 @@ export default class ChessRules { if (initPiece == "!") //cannibal king-pawn m.appear[0].p = C.CannibalKingCode[finalPieces[0]]; for (let i=1; i this.underAttack(sq, oppCol)); } - // Stop at first king found (TODO: multi-kings) + // Scan board for king(s) searchKingPos(color) { + let res = []; for (let i=0; i < this.size.x; i++) { for (let j=0; j < this.size.y; j++) { if (this.getColor(i, j) == color && this.isKing(i, j)) - return [i, j]; + res.push([i, j]); } } - return [-1, -1]; //king not found + return res; } // 'color' arg because some variants (e.g. Refusal) check opponent moves @@ -2160,24 +2183,32 @@ export default class ChessRules { if (!color) color = this.turn; const oppCol = C.GetOppCol(color); - const kingPos = this.searchKingPos(color); + let kingPos = this.searchKingPos(color); let filtered = {}; //avoid re-checking similar moves (promotions...) return moves.filter(m => { const key = m.start.x + m.start.y + '.' + m.end.x + m.end.y; if (!filtered[key]) { this.playOnBoard(m); - let square = kingPos, + let newKingPP = null, + sqIdx = 0, res = true; //a priori valid - if (m.vanish.some(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: - const newKingIdx = - m.appear.findIndex(a => this.isKing(0, 0, a.p) && a.c == color); - if (newKingIdx >= 0) - square = [m.appear[newKingIdx].x, m.appear[newKingIdx].y]; + 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); + kingPos[sqIdx] = [newKingPP.x, newKingPP.y]; + } else - res = false; + res = false; //king vanished } - res &&= !this.underCheck(square, oppCol); + res &&= !this.underCheck(kingPos, oppCol); + if (oldKingPP && newKingPP) + kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y]; this.undoOnBoard(m); filtered[key] = res; return res; @@ -2324,7 +2355,7 @@ export default class ChessRules { return false; const color = this.turn; const oppKingPos = this.searchKingPos(C.GetOppCol(color)); - if (oppKingPos[0] < 0 || this.underCheck(oppKingPos, color)) + if (oppKingPos.length == 0 || this.underCheck(oppKingPos, color)) return true; return ( ( @@ -2373,17 +2404,20 @@ export default class ChessRules { getCurrentScore(move) { const color = this.turn; const oppCol = C.GetOppCol(color); - const kingPos = [this.searchKingPos(color), this.searchKingPos(oppCol)]; - if (kingPos[0][0] < 0 && kingPos[1][0] < 0) + const kingPos = { + [color]: this.searchKingPos(color), + [oppCol]: this.searchKingPos(oppCol) + }; + if (kingPos[color].length == 0 && kingPos[oppCol].length == 0) return "1/2"; - if (kingPos[0][0] < 0) + if (kingPos[color].length == 0) return (color == "w" ? "0-1" : "1-0"); - if (kingPos[1][0] < 0) + if (kingPos[oppCol].length == 0) return (color == "w" ? "1-0" : "0-1"); if (this.atLeastOneMove(color)) return "*"; // No valid move: stalemate or checkmate? - if (!this.underCheck(kingPos[0], oppCol)) + if (!this.underCheck(kingPos[color], oppCol)) return "1/2"; // OK, checkmate return (color == "w" ? "0-1" : "1-0"); @@ -2546,12 +2580,31 @@ export default class ChessRules { 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