From: Benjamin Auder Date: Tue, 23 Jun 2026 21:10:53 +0000 (+0200) Subject: Almost fixed Fanorona X-Git-Url: https://git.auder.net/doc/figure/scripts/current/app_dev.php/DESCRIPTION?a=commitdiff_plain;ds=inline;p=xogo.git Almost fixed Fanorona --- diff --git a/js/base_rules.js b/js/base_rules.js index 532932b..a6c2c59 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -865,11 +865,15 @@ export default class ChessRules { y = (this.playerColor == i ? y = r.height + 5 : - 5 - rsqSize); } else { + const delta = Math.abs(this.size.x - this.size.y); const sqSize = r.width / Math.max(this.size.x, this.size.y); const flipped = this.flippedBoard; - x = (flipped ? this.size.y - 1 - j : j) * sqSize + - Math.abs(this.size.x - this.size.y) * sqSize / 2; + x = (flipped ? this.size.y - 1 - j : j) * sqSize; + if (this.size.x > this.size.y) + x += delta * sqSize / 2; y = (flipped ? this.size.x - 1 - i : i) * sqSize; + if (this.size.y > this.size.x) + y += delta * sqSize / 2; } return [r.x + x, r.y + y]; } diff --git a/js/variants.js b/js/variants.js index 7315dac..abdac56 100644 --- a/js/variants.js +++ b/js/variants.js @@ -54,7 +54,7 @@ const variants = [ {name: 'Enpassant', desc: 'Capture en passant', disp: 'En-passant'}, {name: 'Evolution', desc: 'Faster development'}, {name: 'Extinction', desc: 'Capture all of a kind'}, -// {name: 'Fanorona', desc: 'Malagasy Draughts'}, + {name: 'Fanorona', desc: 'Malagasy Draughts'}, // {name: 'Football', desc: 'Score a goal'}, // {name: 'Forward', desc: 'Moving forward'}, // {name: 'Freecapture', desc: 'Capture both colors', disp: 'Free Capture'}, diff --git a/pieces/Fanorona/arrow_behind.svg b/pieces/Fanorona/arrow_behind.svg new file mode 100644 index 0000000..7f12c46 --- /dev/null +++ b/pieces/Fanorona/arrow_behind.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/pieces/Fanorona/arrow_front.svg b/pieces/Fanorona/arrow_front.svg new file mode 100644 index 0000000..834739c --- /dev/null +++ b/pieces/Fanorona/arrow_front.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/variants/Fanorona/class.js b/variants/Fanorona/class.js index d9a9cdd..4e71efd 100644 --- a/variants/Fanorona/class.js +++ b/variants/Fanorona/class.js @@ -1,8 +1,8 @@ import ChessRules from "/js/base_rules.js"; -import WeiqiRules from "/variants/Weiqi/class.js"; -// TODO: PiPo +import AbstractOnGridRules from "/variants/_OnGrid/class.js"; +import PiPo from "/utils/PiPo.js"; -export default class FanoronaRules extends ChessRules { +export default class FanoronaRules extends AbstractOnGridRules { static get Options() { return {}; @@ -21,25 +21,21 @@ export default class FanoronaRules extends ChessRules { genRandInitBaseFen() { return { - fen: "sssssssss/sssssssss/sSsS1sSsS/SSSSSSSSS/SSSSSSSSS w 0", + fen: "sssssssss/sssssssss/sSsS1sSsS/SSSSSSSSS/SSSSSSSSS", o: {} }; } setOtherVariables(fen) { + super.setOtherVariables(fen); // Local stack of captures during a turn (squares + directions) - this.captures = [ [] ]; //TODO + this.captures = []; } get size() { return { x: 5, y: 9 }; } - getSvgChessboard() { - return WeiqiRules.SvgChessboard_share( - this.playerColor, this.size, this.coordsToId); - } - getPiece() { return 's'; } @@ -48,11 +44,9 @@ export default class FanoronaRules extends ChessRules { if (piece == 's') //stone return { "class": "stone" }; // Arrow - if (piece.charCodeAt(0) >= 105) { - return { - "class": "arrow-" + (piece.charCodeAt(0) >= 105 ? "base" : "point") - }; - } + return { + "class": "arrow-" + (piece.charCodeAt(0) >= 105 ? "behind" : "front") + }; } // a,b,c,d,e,f,g,h : dot on point, N to NO @@ -68,46 +62,58 @@ export default class FanoronaRules extends ChessRules { // Draw arrow setPieceBackground(domPiece, piece) { - if (piece == 'p') + if (piece == 's') return; domPiece.style.setProperty('--rotate-by', V.ArrowToAngle(piece)); } - // After moving, add stones captured in "step" direction from new location - // [x, y] to mv.vanish (if any captured stone!) - addCapture([x, y], step, move) { - let [i, j] = [x + step[0], y + step[1]]; - const oppCol = V.GetOppCol(move.vanish[0].c); - 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); - } - - - // TODO from here - - - getPotentialMovesFrom([x, y]) { - const L0 = this.captures.length; - const captures = this.captures[L0 - 1]; - const L = captures.length; + 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) { - var c = captures[L-1]; + const c = this.captures[L-1]; if (x != c.square.x + c.step[0] || y != c.square.y + c.step[1]) - return []; + return (!justCapt ? [] : false); } - const oppCol = V.GetOppCol(this.turn); - let steps = V.steps[V.ROOK]; - if ((x + y) % 2 == 0) steps = steps.concat(V.steps[V.BISHOP]); + 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 (L > 0 && c.step[0] == s[0] && c.step[1] == s[1]) { + 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({ @@ -120,73 +126,48 @@ export default class FanoronaRules extends ChessRules { continue; } let [i, j] = [x + s[0], y + s[1]]; - if (captures.some(c => c.square.x == i && c.square.y == j)) continue; - if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { - // The move is potentially allowed. Might lead to 2 different captures ---------> TODO + 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 = this.addCapture([i, j], s, mv); + 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); - mv = super.getBasicMove([x, y], [i, j]); } - const capt_bw = this.addCapture([x, y], [-s[0], -s[1]], mv); - if (capt_bw) moves.push(mv); // Captures take priority (if available) - if (!capt && !capt_bw && L == 0) moves.push(mv); + if (!justCapt && !capt && !capt_bw && L == 0) + moves.push(mv); } } - return moves; + return (!justCapt ? moves : false); } atLeastOneCapture() { - const color = this.turn; - const oppCol = V.GetOppCol(color); - const L0 = this.captures.length; - const captures = this.captures[L0 - 1]; - const L = captures.length; + const oppCol = C.GetOppTurn(this.turn); + const L = this.captures.length; + // Called after at least one capture, so L > 0 if (L > 0) { - // If some adjacent enemy stone, with free space to capture it, - // toward a square not already visited, through a different step - // from last one: then yes. - const c = captures[L-1]; - const [x, y] = [c.square.x + c.step[0], c.square.y + c.step[1]]; - let steps = V.steps[V.ROOK]; - if ((x + y) % 2 == 0) steps = steps.concat(V.steps[V.BISHOP]); - // TODO: half of the steps explored are redundant - for (let s of steps) { - if (s[0] == c.step[0] && s[1] == c.step[1]) continue; - const [i, j] = [x + s[0], y + s[1]]; - if ( - !V.OnBoard(i, j) || - this.board[i][j] != V.EMPTY || - captures.some(c => c.square.x == i && c.square.y == j) - ) { - continue; - } - if ( - V.OnBoard(i + s[0], j + s[1]) && - this.board[i + s[0]][j + s[1]] != V.EMPTY && - this.getColor(i + s[0], j + s[1]) == oppCol - ) { - return true; - } - if ( - V.OnBoard(x - s[0], y - s[1]) && - this.board[x - s[0]][y - s[1]] != V.EMPTY && - this.getColor(x - s[0], y - s[1]) == oppCol - ) { - return true; - } - } - return false; + const c = this.captures[L-1]; + return this.getPotentialMovesFrom([c.square.x, c.square.y], true); } - for (let i = 0; i < V.size.x; i++) { - for (let j = 0; j < V.size.y; j++) { + for (let i = 0; i < this.size.x; i++) { + for (let j = 0; j < this.size.y; j++) { if ( - this.board[i][j] != V.EMPTY && - this.getColor(i, j) == color && - // TODO: this could be more efficient - this.getPotentialMovesFrom([i, j]).some(m => m.vanish.length >= 2) + this.board[i][j] != "" && + this.getColor(i, j) == this.turn && + this.getPotentialMovesFrom([i, j], true) ) { return true; } @@ -195,33 +176,14 @@ export default class FanoronaRules extends ChessRules { return false; } - static KeepCaptures(moves) { - return moves.filter(m => m.vanish.length >= 2); - } - - getPossibleMovesFrom(sq) { - let moves = this.getPotentialMovesFrom(sq); - const L0 = this.captures.length; - const captures = this.captures[L0 - 1]; - if (captures.length > 0) return this.getPotentialMovesFrom(sq); - const captureMoves = V.KeepCaptures(moves); - if (captureMoves.length > 0) return captureMoves; - if (this.atLeastOneCapture()) return []; - return moves; - } - filterValid(moves) { return moves; } play(move) { - const color = this.turn; - move.turn = color; //for undo - V.PlayOnBoard(this.board, move); + this.playOnBoard(move); if (move.vanish.length >= 2) { - const L0 = this.captures.length; - let captures = this.captures[L0 - 1]; - captures.push({ + this.captures.push({ square: move.start, step: [move.end.x - move.start.x, move.end.y - move.start.y] }); @@ -230,23 +192,22 @@ export default class FanoronaRules extends ChessRules { move.notTheEnd = true; } if (!move.notTheEnd) { - this.turn = V.GetOppCol(color); + this.turn = C.GetOppTurn(this.turn); this.movesCount++; - this.captures.push([]); + this.captures = []; } } getCurrentScore() { - const color = this.turn; // If no stones on board, I lose if ( this.board.every(b => { return b.every(cell => { - return (cell == "" || cell[0] != color); + return (cell == "" || cell[0] != this.turn); }); }) ) { - return (color == 'w' ? "0-1" : "1-0"); + return (this.turn == 'w' ? "0-1" : "1-0"); } return "*"; } diff --git a/variants/Fanorona/style.css b/variants/Fanorona/style.css index 287477f..29e9c78 100644 --- a/variants/Fanorona/style.css +++ b/variants/Fanorona/style.css @@ -9,12 +9,12 @@ piece.black.stone { background-image: url('/pieces/Weiqi/white_stone.svg'); } -.arrow-point { - background-image: url('/pieces/Fanorona/arrow_point.svg'); +.arrow-front { + background-image: url('/pieces/Fanorona/arrow_front.svg'); rotate: var(--rotate-by); } -.arrow-base { - background-image: url('/pieces/Fanorona/arrow_base.svg'); +.arrow-behind { + background-image: url('/pieces/Fanorona/arrow_behind.svg'); rotate: var(--rotate-by); } diff --git a/variants/Weiqi/class.js b/variants/Weiqi/class.js index 735c0f0..2ccfd0a 100644 --- a/variants/Weiqi/class.js +++ b/variants/Weiqi/class.js @@ -1,9 +1,10 @@ import ChessRules from "/js/base_rules.js"; +import AbstractOnGridRules from "/variants/_OnGrid/class.js"; import Move from "/utils/Move.js"; import PiPo from "/utils/PiPo.js"; import {ArrayFun} from "/utils/array.js"; -export default class WeiqiRules extends ChessRules { +export default class WeiqiRules extends AbstractOnGridRules { static get Options() { return { @@ -38,48 +39,6 @@ export default class WeiqiRules extends ChessRules { return false; } - static SvgChessboard_share(color, bsize, coordsToId) { - const flipped = (color == 'b'); - let board = ` - `; - for (let i=0; i < bsize.x; i++) { - for (let j=0; j < bsize.y; j++) { - const ii = (flipped ? bsize.x - 1 - i : i); - const jj = (flipped ? bsize.y - 1 - j : j); - board += ` - `; - } - } - // Add lines to delimitate "squares" - for (let i = 0; i < bsize.x; i++) { - const y = i * 10 + 5, maxX = bsize.y * 10 - 5; - board += ` - `; - } - for (let i = 0; i < bsize.x; i++) { - const x = i * 10 + 5, maxY = bsize.x * 10 - 5; - board += ` - `; - } - board += ""; - return board; - } - - getSvgChessboard() { - return V.SvgChessboard_share(this.playerColor, this.size, this.coordsToId); - } - get size() { return { x: this.options["bsize"], @@ -135,6 +94,10 @@ export default class WeiqiRules extends ChessRules { return {"class": classe_s}; } + canIplay(x, y) { + return (this.playerColor == this.turn && this.board[x][y] == ""); + } + doClick(coords) { const [x, y] = [coords.x, coords.y]; if (this.board[x][y] != "" || this.turn != this.playerColor) diff --git a/variants/_OnGrid/class.js b/variants/_OnGrid/class.js new file mode 100644 index 0000000..f4f1d2d --- /dev/null +++ b/variants/_OnGrid/class.js @@ -0,0 +1,45 @@ +import ChessRules from "/js/base_rules.js"; + +export default class AbstractOnGridRules extends ChessRules { + + getSvgChessboard() { + const flipped = (this.playerColor == 'b'); + let board = ` + `; + for (let i=0; i < this.size.x; i++) { + for (let j=0; j < this.size.y; j++) { + const ii = (flipped ? this.size.x - 1 - i : i); + const jj = (flipped ? this.size.y - 1 - j : j); + board += ` + `; + } + } + // Add lines to delimitate "squares" + for (let i = 0; i < this.size.x; i++) { + const y = i * 10 + 5, + maxX = this.size.y * 10 - 5; + board += ` + `; + } + for (let i = 0; i < this.size.y; i++) { + const x = i * 10 + 5, + maxY = this.size.x * 10 - 5; + board += ` + `; + } + board += ""; + return board; + } + +};