X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=base_rules.js;h=88197ea0bed834c970659b95068aa5bec6f0b1aa;hb=182ba661e9c6e30fc641f4e71f29ae76961f4510;hp=e3a34fbada483cf022b756c71d3f23b682c6c09a;hpb=a548cb4e3ad8099e977da9bb4a4184973beb56e3;p=xogo.git diff --git a/base_rules.js b/base_rules.js index e3a34fb..88197ea 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") @@ -197,16 +202,26 @@ 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 + " 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"; + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; 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 +231,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 +241,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 +248,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"; @@ -262,23 +271,10 @@ export default class ChessRules { fen = ( pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - " w 0" + pieces["w"].join("").toUpperCase() ); } - // 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 +292,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 +354,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 +364,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,18 +407,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.isDiagram = o.diagram; - this.marks = o.marks; this.graphicalInit(); } @@ -474,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) { @@ -525,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 @@ -626,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); @@ -781,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); @@ -1076,6 +1074,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 @@ -1476,7 +1489,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; } @@ -1612,6 +1625,7 @@ export default class ChessRules { m.next = mNext; } }); + return moves; } pawnPostProcess(moves, color, oppCol) { @@ -1631,7 +1645,7 @@ export default class ChessRules { m.appear.shift(); return; } - let finalPieces = ["p"]; + let finalPieces; if ( this.options["cannibal"] && this.board[x2][y2] != "" && @@ -1645,16 +1659,13 @@ 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)); } @@ -2196,19 +2208,21 @@ export default class ChessRules { let newKingPP = null, sqIdx = 0, res = true; //a priori valid - const oldKingPP = m.vanish.find(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: 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); + sqIdx = kingPos.findIndex(kp => + kp[0] == oldKingPP.x && kp[1] == oldKingPP.y); kingPos[sqIdx] = [newKingPP.x, newKingPP.y]; } else res = false; //king vanished } - res &&= !this.underCheck(square_s, oppCol); + res &&= !this.underCheck(kingPos, oppCol); if (oldKingPP && newKingPP) kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y]; this.undoOnBoard(m); @@ -2343,6 +2357,10 @@ 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.movesCount++; @@ -2403,7 +2421,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 = { @@ -2458,32 +2480,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 @@ -2582,42 +2609,75 @@ 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) {