X-Git-Url: https://git.auder.net/css/rpsls.css?a=blobdiff_plain;f=base_rules.js;h=5b1a0136882ef5242376df17ff7c22643ade36bf;hb=33b427488bb6ee5c505c3a024bccedbef763f80e;hp=ebf8f1e388555c9ec28efaf0805797ab5aac29cd;hpb=462947f0a6c5f33033bbee5d42d38149be6d51cb;p=xogo.git diff --git a/base_rules.js b/base_rules.js index ebf8f1e..5b1a013 100644 --- a/base_rules.js +++ b/base_rules.js @@ -1,5 +1,5 @@ -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 PiPo from "/utils/PiPo.js"; import Move from "/utils/Move.js"; @@ -116,6 +116,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,7 +208,7 @@ 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)) : "") ); } @@ -213,7 +218,7 @@ export default class ChessRules { let fen, flags = "0707"; if (!this.options.randomness) // Deterministic: - fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0"; + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; else { // Randomize @@ -266,8 +271,7 @@ export default class ChessRules { fen = ( pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - " w 0" + pieces["w"].join("").toUpperCase() ); } return { fen: fen, o: {flags: flags} }; @@ -472,17 +476,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 +525,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 +622,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 +779,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 +1013,10 @@ export default class ChessRules { // TODO: onpointerdown/move/up ? See reveal.js /controllers/touch.js } + // NOTE: not called if isDiagram, or genFenOnly 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 +1073,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 +1241,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 +1259,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 +1276,7 @@ export default class ChessRules { }, 'k': { "class": "king", - moves: [ + both: [ { steps: [ [0, 1], [0, -1], [1, 0], [-1, 0], @@ -1326,7 +1340,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 +1387,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 +1501,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 +1637,7 @@ export default class ChessRules { m.next = mNext; } }); + return moves; } pawnPostProcess(moves, color, oppCol) { @@ -1629,7 +1657,7 @@ export default class ChessRules { m.appear.shift(); return; } - let finalPieces = ["p"]; + let finalPieces; if ( this.options["cannibal"] && this.board[x2][y2] != "" && @@ -1643,16 +1671,13 @@ export default class ChessRules { if (initPiece == "!") //cannibal king-pawn m.appear[0].p = C.CannibalKingCode[finalPieces[0]]; for (let i=1; i { + const exploreSteps = (stepArray, mode) => { for (let s of stepArray) { outerLoop: for (let step of s.steps) { if (o.segments) { @@ -1780,9 +1805,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]; @@ -1805,9 +1830,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 && @@ -1822,17 +1847,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); } } @@ -1855,7 +1878,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 @@ -2327,14 +2350,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}; @@ -2344,8 +2366,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; } @@ -2404,7 +2430,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 = { @@ -2459,32 +2489,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 @@ -2613,31 +2648,45 @@ export default class ChessRules { 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) {