From: Benjamin Auder Date: Tue, 13 Oct 2026 07:46:18 +0000 (+0200) Subject: Add some TODOs related to faster Fanorona moves generation X-Git-Url: https://git.auder.net/variants/Dynamo/app_dev.php/_configurator/?a=commitdiff_plain;h=bfe74f7d9945ae6cebfe14ca1dba8b4b81bbb56c;p=xogo.git Add some TODOs related to faster Fanorona moves generation --- diff --git a/.gitignore b/.gitignore index 3189358..938d608 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /.pid /js/parameters.js /dist/ +/.unversioned/ diff --git a/TODO b/TODO index 81f9dfb..656eaf8 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,6 @@ -https://fr.wikipedia.org/wiki/Unlur +clarify moveStack logic around line 2600 baseRules.js +implement in Fanorona getPotentialMove_s() function +## New variants : +https://fr.wikipedia.org/wiki/Unlur DuckChess + crazyduck --> move it at random ? Capturing ? Hmm. diff --git a/js/base_rules.js b/js/base_rules.js index d82dc07..dd93b81 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -127,6 +127,11 @@ export default class ChessRules { return (this.playerColor == 'b'); } + // Some variants do not precompute moves + get twoSquareMove() { + return false; + } + // Some variants use click infos: doClick(coords) { if (typeof coords.x != "number") @@ -396,6 +401,7 @@ export default class ChessRules { this.turn = fenParsed.turn; this.movesCount = parseInt(fenParsed.movesCount, 10); this.setOtherVariables(fenParsed); + this.re_setGameVariables(); } // Turn position fen into double array ["wb","wp","bk",...] @@ -466,6 +472,13 @@ export default class ChessRules { this.ispawn = {}; } + // Called before 1st move, and after receiving each opponent move. + re_setGameVariables() { + // Useful for example when captures are mandatory: + if (this.options["capture"]) + this.canCapture = this.atLeastOneCapture(this.turn); + } + //////////////// // VISUAL UTILS @@ -908,7 +921,8 @@ export default class ChessRules { let start = null, r = null, startPiece, curPiece = null, - pieceWidth; + pieceWidth, + potentialMoves; const mousedown = (e) => { // Disable zoom on smartphones: if (e.touches && e.touches.length > 1) @@ -944,6 +958,8 @@ export default class ChessRules { startPiece.style.opacity = "0.4"; chessboard.style.cursor = "none"; } + if (!this.twoSquareMove) + potentialMoves = this.getPotentialMovesFrom([x, y]); } } }; @@ -971,8 +987,12 @@ export default class ChessRules { const cd = (landingElt ? this.idToCoords(landingElt.id) : undefined); if (cd) { - // NOTE: clearly suboptimal, but much easier, and not a big deal. - const potentialMoves = this.getPotentialMove_s([x, y], [cd.x, cd.y]); + if (this.twoSquareMove) + potentialMoves = this.getPotentialMove_s([x, y], [cd.x, cd.y]); + else { + potentialMoves = + potentialMoves.filter(m => m.end.x == cd.x && m.end.y == cd.y); + } const moves = this.filterValid(potentialMoves); if (moves.length >= 2) this.showChoices(moves, r); @@ -1583,12 +1603,17 @@ export default class ChessRules { return moves; } + // Redefined in variants not computing everything on mousedown + getPotentialMove_s([x1, y1], [x2, y2]) { + return []; + } + postProcessPotentialMoves(moves) { if (moves.length == 0) return []; const color = this.getColor(moves[0].start.x, moves[0].start.y); const oppCols = this.getOppCols(color); - if (this.options["capture"] && this.atLeastOneCapture(color)) + if (this.options["capture"] && this.canCapture) moves = this.capturePostProcess(moves, oppCols); if (this.options["atomic"]) moves = this.atomicPostProcess(moves, color, oppCols); @@ -2556,7 +2581,7 @@ export default class ChessRules { buildMoveStack(move, r) { this.moveStack.push(move); this.computeNextMove(move); - const then = () => { + const actions = () => { const newTurn = this.turn; if (this.moveStack.length == 1 && !this.hideMoves) this.playVisual(move, r); @@ -2584,9 +2609,9 @@ export default class ChessRules { } }; // If hiding moves, then they are revealed in play() with callback - this.play(move, this.hideMoves ? then : null); + this.play(move, this.hideMoves ? actions : null); if (!this.hideMoves) - then(); + actions(); } // Implemented in variants using (automatic) moveStack diff --git a/oldF.js b/oldF.js deleted file mode 100644 index 4e71efd..0000000 --- a/oldF.js +++ /dev/null @@ -1,215 +0,0 @@ -import ChessRules from "/js/base_rules.js"; -import AbstractOnGridRules from "/variants/_OnGrid/class.js"; -import PiPo from "/utils/PiPo.js"; - -export default class FanoronaRules extends AbstractOnGridRules { - - static get Options() { - return {}; - } - - get hasFlags() { - return false; - } - - get hasEnpassant() { - return false; - } - static get HasKing() { - return false; - } - - genRandInitBaseFen() { - return { - fen: "sssssssss/sssssssss/sSsS1sSsS/SSSSSSSSS/SSSSSSSSS", - o: {} - }; - } - - setOtherVariables(fen) { - super.setOtherVariables(fen); - // Local stack of captures during a turn (squares + directions) - this.captures = []; - } - - get size() { - return { x: 5, y: 9 }; - } - - getPiece() { - return 's'; - } - - pieceDef(piece, color, x, y) { - if (piece == 's') //stone - return { "class": "stone" }; - // Arrow - return { - "class": "arrow-" + (piece.charCodeAt(0) >= 105 ? "behind" : "front") - }; - } - - // a,b,c,d,e,f,g,h : dot on point, N to NO - // i,j,k,l,m,n,o,p : dot on base, N to NO - static ArrowToAngle(arrow) { - let ccode = arrow.charCodeAt(0); - if (ccode >= 105) { //i - ccode -= 8; - arrow = String.fromCharCode(ccode); - } - return (45 * (ccode - 97)) + "deg"; - } - - // Draw arrow - setPieceBackground(domPiece, piece) { - if (piece == 's') - return; - domPiece.style.setProperty('--rotate-by', V.ArrowToAngle(piece)); - } - - getPotentialMovesFrom([x, y], justCapt) { - // After moving, add stones captured in "step" direction from new location - // [x, y] to mv.vanish (if any captured stone!) - const oppCol = C.GetOppTurn(this.turn); - const addCapture = ([x, y], step, move) => { - let [i, j] = [x + step[0], y + step[1]]; - while ( - this.onBoard(i, j) && - this.board[i][j] != "" && - this.getColor(i, j) == oppCol - ) { - move.vanish.push(new PiPo({ x: i, y: j, c: oppCol, p: 's' })); - [i, j] = [i + step[0], j + step[1]]; - } - return (move.vanish.length >= 2); - }; - const stepToArrow = (s, forward) => { - const baseShift = (forward ? 0 : 8), - colShift = (this.playerColor=='w' ? 0 : 4); - const doShift = (c) => { - return String.fromCharCode( - 97 + (c.charCodeAt(0) - 97 + colShift) % 8 + baseShift); - }; - switch (s) { - case "-1_0": return doShift('a'); - case "-1_1": return doShift('b'); - case "0_1": return doShift('c'); - case "1_1": return doShift('d'); - case "1_0": return doShift('e'); - case "1_-1": return doShift('f'); - case "0_-1": return doShift('g'); - case "-1_-1": return doShift('h'); - } - return ''; //never reached - }; - const L = this.captures.length; - if (L > 0) { - const c = this.captures[L-1]; - if (x != c.square.x + c.step[0] || y != c.square.y + c.step[1]) - return (!justCapt ? [] : false); - } - let steps = super.pieceDef('r').both[0].steps; - if ((x + y) % 2 == 0) - steps = steps.concat(super.pieceDef('b').both[0].steps); - let moves = []; - for (let s of steps) { - if (!justCapt && L > 0 && c.step[0] == s[0] && c.step[1] == s[1]) { - // Add a move to say "I'm done capturing" - moves.push( - new Move({ - appear: [], - vanish: [], - start: { x: x, y: y }, - end: { x: x - s[0], y: y - s[1] } - }) - ); - continue; - } - let [i, j] = [x + s[0], y + s[1]]; - if (this.captures.some(c => c.square.x == i && c.square.y == j)) - continue; - if (this.onBoard(i, j) && this.board[i][j] == "") { - // The move is possible. Might lead to 2 different captures - let mv = super.getBasicMove([x, y], [i, j]); - const capt = addCapture([i, j], s, mv); - if (capt) { - if (!!justCapt) - return true; - mv.choice = stepToArrow(s[0] + "_" + s[1], true); - moves.push(mv); - mv = super.getBasicMove([x, y], [i, j]); //cheap enough - } - const capt_bw = addCapture([x, y], [-s[0], -s[1]], mv); - if (capt_bw) { - if (!!justCapt) - return true; - mv.choice = stepToArrow(s[0] + "_" + s[1], false); - moves.push(mv); - } - // Captures take priority (if available) - if (!justCapt && !capt && !capt_bw && L == 0) - moves.push(mv); - } - } - return (!justCapt ? moves : false); - } - - atLeastOneCapture() { - const oppCol = C.GetOppTurn(this.turn); - const L = this.captures.length; - // Called after at least one capture, so L > 0 - if (L > 0) { - const c = this.captures[L-1]; - return this.getPotentialMovesFrom([c.square.x, c.square.y], true); - } - for (let i = 0; i < this.size.x; i++) { - for (let j = 0; j < this.size.y; j++) { - if ( - this.board[i][j] != "" && - this.getColor(i, j) == this.turn && - this.getPotentialMovesFrom([i, j], true) - ) { - return true; - } - } - } - return false; - } - - filterValid(moves) { - return moves; - } - - play(move) { - this.playOnBoard(move); - if (move.vanish.length >= 2) { - this.captures.push({ - square: move.start, - step: [move.end.x - move.start.x, move.end.y - move.start.y] - }); - if (this.atLeastOneCapture()) - // There could be other captures (optional) - move.notTheEnd = true; - } - if (!move.notTheEnd) { - this.turn = C.GetOppTurn(this.turn); - this.movesCount++; - this.captures = []; - } - } - - getCurrentScore() { - // If no stones on board, I lose - if ( - this.board.every(b => { - return b.every(cell => { - return (cell == "" || cell[0] != this.turn); - }); - }) - ) { - return (this.turn == 'w' ? "0-1" : "1-0"); - } - return "*"; - } - -}; diff --git a/variants/Fanorona/class.js b/variants/Fanorona/class.js index 63fb5b7..1079861 100644 --- a/variants/Fanorona/class.js +++ b/variants/Fanorona/class.js @@ -57,6 +57,10 @@ export default class FanoronaRules extends AbstractOnGridRules { this.captures = []; } + re_setGameVariables() { + this.canCapture = this.atLeastOneCapture(); + } + get size() { return { x: 5, y: 9 }; } @@ -158,7 +162,7 @@ export default class FanoronaRules extends AbstractOnGridRules { continue; } let [i, j] = [x + s[0], y + s[1]]; - if (this.captures.some(c => c.square.x == i && c.square.y == j)) + if (this.captures.some(cc => cc.square.x == i && cc.square.y == j)) continue; if (this.onBoard(i, j) && this.board[i][j] == "") { // The move is possible. Might lead to 2 different captures @@ -208,7 +212,7 @@ export default class FanoronaRules extends AbstractOnGridRules { } filterValid(moves) { - if (this.captures.length > 0 || !this.atLeastOneCapture()) + if (this.captures.length > 0 || !this.canCapture) return moves; return moves.filter(m => m.vanish.length >= 2); }