X-Git-Url: https://git.auder.net/doc/index.css?a=blobdiff_plain;f=base_rules.js;h=d609a5609fcf16ab86d8b23a4072fde25225eb9f;hb=0cef7bb4f72046ce60db408bfb76103428e2c15a;hp=4296ec5866485f44232666f3584cc02b41df9c07;hpb=9ac672172c605f9f5ea4355b8f743bcf189601fe;p=xogo.git diff --git a/base_rules.js b/base_rules.js index 4296ec5..d609a56 100644 --- a/base_rules.js +++ b/base_rules.js @@ -1,5 +1,6 @@ -import { Random } from "/utils/alea.js"; -import { ArrayFun } from "/utils/array.js"; +import {Random} from "/utils/alea.js"; +import {ArrayFun} from "/utils/array.js"; +import {FenUtil} from "/utils/setupPieces.js"; import PiPo from "/utils/PiPo.js"; import Move from "/utils/Move.js"; @@ -116,6 +117,11 @@ export default class ChessRules { return false; } + // Some variants reveal moves only after both players played + get hideMoves() { + return false; + } + // Some variants use click infos: doClick(coords) { if (typeof coords.x != "number") @@ -203,74 +209,26 @@ export default class ChessRules { baseFen.o = Object.assign({init: true}, baseFen.o); const parts = this.getPartFen(baseFen.o); return ( - baseFen.fen + + baseFen.fen + " w 0" + (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: - fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0"; - - else { - // Randomize - 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"]) { - if (c == 'b' && this.options.randomness == 1) { - pieces['b'] = pieces['w']; - flags += flags; - break; - } - let positions = ArrayFun.range(8); - // Get random squares for bishops - let randIndex = 2 * Random.randInt(4); - const bishop1Pos = positions[randIndex]; - // The second bishop must be on a square of different color - let randIndex_tmp = 2 * Random.randInt(4) + 1; - const bishop2Pos = positions[randIndex_tmp]; - // 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]; - positions.splice(randIndex, 1); - 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"; - pieces[c][bishop1Pos] = "b"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][bishop2Pos] = "b"; - pieces[c][knight2Pos] = "n"; - pieces[c][rook2Pos] = "r"; - flags += rook1Pos.toString() + rook2Pos.toString(); + const s = FenUtil.setupPieces( + ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'], + { + between: {p1: 'k', p2: 'r'}, + diffCol: ['b'], + flags: ['r'] } - fen = ( - pieces["b"].join("") + - "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - " w 0" - ); - } - return { fen: fen, o: {flags: flags} }; + ); + return { + fen: s.b.join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + + s.w.join("").toUpperCase(), + o: {flags: s.flags} + }; } // "Parse" FEN: just return untransformed string data @@ -400,9 +358,6 @@ export default class ChessRules { 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; // Some variables this.playerColor = o.color; @@ -472,17 +427,15 @@ export default class ChessRules { } // ordering as in pieces() p,r,n,b,q,k - initReserves(reserveStr) { + initReserves(reserveStr, pieceArray) { + if (!pieceArray) + pieceArray = ['p', 'r', 'n', 'b', 'q', 'k']; const counts = reserveStr.split("").map(c => parseInt(c, 36)); - this.reserve = { w: {}, b: {} }; - const pieceName = ['p', 'r', 'n', 'b', 'q', 'k']; - const L = pieceName.length; - for (let i of ArrayFun.range(2 * L)) { - if (i < L) - this.reserve['w'][pieceName[i]] = counts[i]; - else - this.reserve['b'][pieceName[i-L]] = counts[i]; - } + const L = pieceArray.length; + this.reserve = { + w: ArrayFun.toObject(pieceArray, counts.slice(0, L)), + b: ArrayFun.toObject(pieceArray, counts.slice(L, 2 * L)) + }; } initIspawn(ispawnStr) { @@ -523,20 +476,16 @@ export default class ChessRules { (oldV,newV) => oldV + (this.reserve[c][newV] > 0 ? 1 : 0), 0); } - static AddClass_es(piece, class_es) { + static AddClass_es(elt, class_es) { if (!Array.isArray(class_es)) class_es = [class_es]; - class_es.forEach(cl => { - piece.classList.add(cl); - }); + class_es.forEach(cl => elt.classList.add(cl)); } - static RemoveClass_es(piece, class_es) { + static RemoveClass_es(elt, class_es) { if (!Array.isArray(class_es)) class_es = [class_es]; - class_es.forEach(cl => { - piece.classList.remove(cl); - }); + class_es.forEach(cl => elt.classList.remove(cl)); } // Generally light square bottom-right @@ -624,6 +573,8 @@ export default class ChessRules { class="chessboard_SVG">`; for (let i=0; i < this.size.x; i++) { for (let j=0; j < this.size.y; j++) { + if (!this.onBoard(i, j)) + continue; const ii = (flipped ? this.size.x - 1 - i : i); const jj = (flipped ? this.size.y - 1 - j : j); let classes = this.getSquareColorClass(ii, jj); @@ -779,8 +730,8 @@ export default class ChessRules { piece = "k"; //capturing cannibal king: back to king form const oldCount = this.reserve[color][piece]; this.reserve[color][piece] = count; - // Redrawing is much easier if count==0 - if ([oldCount, count].includes(0)) + // Redrawing is much easier if count==0 (or undefined) + if ([oldCount, count].some(item => !item)) this.re_drawReserve([color]); else { const numId = this.getReserveNumId(color, piece); @@ -1013,11 +964,10 @@ export default class ChessRules { // TODO: onpointerdown/move/up ? See reveal.js /controllers/touch.js } + // NOTE: not called if isDiagram 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); @@ -1074,6 +1024,21 @@ export default class ChessRules { } } + displayMessage(elt, msg, classe_s, timeout) { + if (elt) + // Fixed element, e.g. for Dice Chess + elt.innerHTML = msg; + else { + // Temporary div (Chakart, Apocalypse...) + let divMsg = document.createElement("div"); + C.AddClass_es(divMsg, classe_s); + divMsg.innerHTML = msg; + let container = document.getElementById(this.containerId); + container.appendChild(divMsg); + setTimeout(() => container.removeChild(divMsg), timeout); + } + } + //////////////// // DARK METHODS @@ -1227,13 +1192,13 @@ export default class ChessRules { }, 'r': { "class": "rook", - moves: [ + both: [ {steps: [[0, 1], [0, -1], [1, 0], [-1, 0]]} ] }, 'n': { "class": "knight", - moves: [ + both: [ { steps: [ [1, 2], [1, -2], [-1, 2], [-1, -2], @@ -1245,13 +1210,13 @@ export default class ChessRules { }, 'b': { "class": "bishop", - moves: [ + both: [ {steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]]} ] }, 'q': { "class": "queen", - moves: [ + both: [ { steps: [ [0, 1], [0, -1], [1, 0], [-1, 0], @@ -1262,7 +1227,7 @@ export default class ChessRules { }, 'k': { "class": "king", - moves: [ + both: [ { steps: [ [0, 1], [0, -1], [1, 0], [-1, 0], @@ -1326,7 +1291,20 @@ export default class ChessRules { } getStepSpec(color, x, y, piece) { - return this.pieces(color, x, y)[piece || this.getPieceType(x, y)]; + let pieceType = piece; + let allSpecs = this.pieces(color, x, y); + if (!piece) + pieceType = this.getPieceType(x, y); + else if (allSpecs[piece].moveas) + pieceType = allSpecs[piece].moveas; + let res = allSpecs[pieceType]; + if (!res["both"]) + res.both = []; + if (!res["moves"]) + res.moves = []; + if (!res["attack"]) + res.attack = []; + return res; } // Can thing on square1 capture thing on square2? @@ -1360,7 +1338,7 @@ export default class ChessRules { const oppCol = C.GetOppCol(color); const piece = this.getPieceType(x, y); const stepSpec = this.getStepSpec(color, x, y, piece); - const attacks = stepSpec.attack || stepSpec.moves; + const attacks = stepSpec.both.concat(stepSpec.attack); for (let a of attacks) { outerLoop: for (let step of a.steps) { let [i, j] = [x + step[0], y + step[1]]; @@ -1474,7 +1452,7 @@ export default class ChessRules { let moves = []; for (let i=0; i 0 && this.getPieceType(moves[0].start.x, moves[0].start.y) == "p" ) { - this.pawnPostProcess(moves, color, oppCol); + moves = this.pawnPostProcess(moves, color, oppCol); } if (this.options["cannibal"] && this.options["rifle"]) // In this case a rifle-capture from last rank may promote a pawn - this.riflePromotePostProcess(moves, color); + moves = this.riflePromotePostProcess(moves, color); return moves; } @@ -1610,6 +1588,7 @@ export default class ChessRules { m.next = mNext; } }); + return moves; } pawnPostProcess(moves, color, oppCol) { @@ -1649,7 +1628,7 @@ export default class ChessRules { moreMoves.push(newMove); } }); - Array.prototype.push.apply(moves, moreMoves); + return moves.concat(moreMoves); } riflePromotePostProcess(moves, color) { @@ -1671,7 +1650,7 @@ export default class ChessRules { } } }); - Array.prototype.push.apply(moves, newMoves); + return moves.concat(newMoves); } // Generic method to find possible moves of "sliding or jumping" pieces @@ -1758,7 +1737,7 @@ export default class ChessRules { elt.segments = this.getSegments(segments, segStart, end); res.push(elt); }; - const exploreSteps = (stepArray) => { + const exploreSteps = (stepArray, mode) => { for (let s of stepArray) { outerLoop: for (let step of s.steps) { if (o.segments) { @@ -1777,9 +1756,9 @@ export default class ChessRules { !o.captureTarget || (o.captureTarget[0] == i && o.captureTarget[1] == j) ) { - if (o.one && !o.attackOnly) + if (o.one && mode != "attack") return true; - if (!o.attackOnly) + if (mode != "attack") addSquare(!o.captureTarget ? [i, j] : [x, y]); if (o.captureTarget) return res[0]; @@ -1802,9 +1781,9 @@ export default class ChessRules { if (!explored[i + "." + j]) { explored[i + "." + j] = true; if (allowed([x, y], [i, j])) { - if (o.one && !o.moveOnly) + if (o.one && mode != "moves") return true; - if (!o.moveOnly) + if (mode != "moves") addSquare(!o.captureTarget ? [i, j] : [x, y]); if ( o.captureTarget && @@ -1819,17 +1798,15 @@ export default class ChessRules { return undefined; //default, but let's explicit it }; if (o.captureTarget) - return exploreSteps(o.captureSteps) + return exploreSteps(o.captureSteps, "attack"); else { const stepSpec = o.stepSpec || this.getStepSpec(this.getColor(x, y), x, y); let outOne = false; - if (!o.attackOnly || !stepSpec.attack) - outOne = exploreSteps(stepSpec.moves); - if (!outOne && !o.moveOnly && !!stepSpec.attack) { - o.attackOnly = true; //ok because o is always a temporary object - outOne = exploreSteps(stepSpec.attack); - } + if (!o.attackOnly) + outOne = exploreSteps(stepSpec.both.concat(stepSpec.moves), "moves"); + if (!outOne && !o.moveOnly) + outOne = exploreSteps(stepSpec.both.concat(stepSpec.attack), "attack"); return (o.one ? outOne : res); } } @@ -1852,7 +1829,7 @@ export default class ChessRules { if (this.canStepOver(x, y, apparentPiece)) continue; const stepSpec = this.getStepSpec(colIJ, i, j); - const attacks = stepSpec.attack || stepSpec.moves; + const attacks = stepSpec.attack.concat(stepSpec.both); for (let a of attacks) { for (let s of a.steps) { // Quick check: if step isn't compatible, don't even try @@ -2054,7 +2031,7 @@ export default class ChessRules { // will be executed in filterValid() later. ( i != finalSquares[castleSide][0] && - this.underCheck([x, i], oppCol) + this.underCheck([[x, i]], oppCol) ) || ( @@ -2161,8 +2138,6 @@ export default class ChessRules { underCheck(square_s, oppCol) { if (this.options["taking"] || this.options["dark"]) return false; - if (!Array.isArray(square_s[0])) - square_s = [square_s]; return square_s.some(sq => this.underAttack(sq, oppCol)); } @@ -2324,14 +2299,13 @@ export default class ChessRules { } postPlay(move) { - const color = this.turn; if (this.options["dark"]) this.updateEnlightened(); if (this.options["teleport"]) { if ( this.subTurnTeleport == 1 && move.vanish.length > move.appear.length && - move.vanish[1].c == color + move.vanish[1].c == this.turn ) { const v = move.vanish[move.vanish.length - 1]; this.captured = {x: v.x, y: v.y, c: v.c, p: v.p}; @@ -2341,8 +2315,12 @@ export default class ChessRules { this.subTurnTeleport = 1; this.captured = null; } + this.tryChangeTurn(move); + } + + tryChangeTurn(move) { if (this.isLastMove(move)) { - this.turn = C.GetOppCol(color); + this.turn = C.GetOppCol(this.turn); this.movesCount++; this.subTurn = 1; } @@ -2401,7 +2379,11 @@ export default class ChessRules { } // What is the score ? (Interesting if game is over) - getCurrentScore(move) { + getCurrentScore(move_s) { + const move = move_s[move_s.length - 1]; + // Shortcut in case the score was computed before: + if (move.result) + return move.result; const color = this.turn; const oppCol = C.GetOppCol(color); const kingPos = { @@ -2456,32 +2438,37 @@ export default class ChessRules { buildMoveStack(move, r) { this.moveStack.push(move); this.computeNextMove(move); - this.play(move); - 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 = [] + const then = () => { + const newTurn = this.turn; + if (this.moveStack.length == 1 && !this.hideMoves) + 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 { - 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 = [] - }); + 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 = []; + }); + } } - } + }; + // If hiding moves, then they are revealed in play() with callback + this.play(move, this.hideMoves ? then : null); + if (!this.hideMoves) + then(); } // Implemented in variants using (automatic) moveStack @@ -2580,46 +2567,80 @@ 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 } - playReceivedMove(moves, callback) { - const launchAnimation = () => { - const r = container.querySelector(".chessboard").getBoundingClientRect(); - const animateRec = i => { - this.animate(moves[i], () => { - this.play(moves[i]); - this.playVisual(moves[i], r); - if (i < moves.length - 1) - setTimeout(() => animateRec(i+1), 300); - else - callback(); - }); - }; - animateRec(0); + launchAnimation(moves, container, callback) { + if (this.hideMoves) { + for (let i=0; i {}); + return; + } + const r = container.querySelector(".chessboard").getBoundingClientRect(); + const animateRec = i => { + this.animate(moves[i], () => { + this.play(moves[i]); + this.playVisual(moves[i], r); + if (i < moves.length - 1) + setTimeout(() => animateRec(i+1), 300); + else + callback(); + }); }; + animateRec(0); + } + + playReceivedMove(moves, callback) { // 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"; container.style.display = "block"; - setTimeout(launchAnimation, 700); + setTimeout( + () => this.launchAnimation(moves, container, callback), + 700 + ); + } + else { + setTimeout( + () => this.launchAnimation(moves, container, callback), + delay || 0 + ); } - else - setTimeout(launchAnimation, delay || 0); }; let container = document.getElementById(this.containerId); if (document.hidden) { document.onvisibilitychange = () => { + // TODO here: page reload ?! (some issues if tab changed...) document.onvisibilitychange = undefined; checkDisplayThenAnimate(700); };