From d1f16494b50b9d7b4527730ab9def581dab99f52 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 6 May 2026 22:18:19 +0200 Subject: [PATCH 01/16] Remove extra print --- bundle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bundle.py b/bundle.py index b1d794e..8353cf9 100755 --- a/bundle.py +++ b/bundle.py @@ -64,7 +64,6 @@ def run_bundle(): # 2. Update references for root, dirs, files in os.walk(DEST_DIR): for file in files: - print(file) if os.path.splitext(file)[1] in EXTENSIONS_TO_UPDATE and not re.match(r'^app\.[^\.]+\.js$', file): file_path = os.path.join(root, file) with open(file_path, 'r', encoding='utf-8') as f: -- 2.53.0 From 94a4d590a3d3b74db0ad643189409fa4847288f9 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Thu, 7 May 2026 03:03:34 +0200 Subject: [PATCH 02/16] Few fixes + improve 8-pieces; last TODOs --- css/common.css | 19 +++-- js/app.js | 6 +- js/base_rules.js | 1 + variants/Eightpieces/class.js | 132 ++++++++++++++++++++-------------- 4 files changed, 95 insertions(+), 63 deletions(-) diff --git a/css/common.css b/css/common.css index eabf641..38d0f95 100644 --- a/css/common.css +++ b/css/common.css @@ -305,7 +305,7 @@ piece.mark.transparent { } .option-input { display: inline-block; - margin-right: 10px; + margin-right: 15px; } .option-input input[type=number] { width: 64px; @@ -546,11 +546,14 @@ select:focus + .focus { width: 100%; } +label.input-checkbox { + cursor: pointer; +} /* https://dev.to/kallmanation/styling-a-checkbox-with-only-css-3o3p */ -label.checkbox > input[type="checkbox"] { +label.input-checkbox > input[type="checkbox"] { display: none; } -label.checkbox > input[type="checkbox"] + *::before { +label.input-checkbox > input[type="checkbox"] + *::before { content: ""; display: inline-block; vertical-align: bottom; @@ -562,22 +565,24 @@ label.checkbox > input[type="checkbox"] + *::before { border-width: 0.1rem; border-color: gray; } -label.checkbox > input[type="checkbox"]:checked + *::before { +label.input-checkbox > input[type="checkbox"]:checked + *::before { content: "✓"; font-size: 1.1rem; - /*padding:10px;*/ color: white; text-align: center; background: teal; border-color: teal; } -label.checkbox > input[type="checkbox"]:checked + * { +label.input-checkbox > input[type="checkbox"]:checked + * { color: teal; } -label.checkbox > span.spacer { +label.input-checkbox > span.spacer { width: 10px; content: " "; } +label.input-checkbox > span.after-spacer { + padding-left: 5px; +} /* https://theanam.github.io/css-only-loaders/ ("hour-glass") */ :root{ diff --git a/js/app.js b/js/app.js index 7d30a32..016a1c3 100644 --- a/js/app.js +++ b/js/app.js @@ -211,10 +211,10 @@ function prepareOptions() { inputAttrs.value = input.defaut; container.append( h('div', { class: 'option-input' }, [ - h('label', { class: 'input' }, [ + h('label', { class: "input-" + input.type }, [ h('input', inputAttrs), h('span', { class: 'spacer' }), - h('span', { textContent: input.label }) + h('span', { class: 'after-spacer', textContent: input.label }) ]) ]) ); @@ -315,7 +315,7 @@ async function fillGameInfos(gameInfos, oppIndex) { // Final assembling container.append( playerDiv, - //activeOptions.length > 0 ? optionsInfos : null, + optionsInfos, rulesDiv, btnWrap ); diff --git a/js/base_rules.js b/js/base_rules.js index 84cf1ea..4fc46ac 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -1470,6 +1470,7 @@ export default class ChessRules { return false; } + // Check if it's possible to go from x1,y1 to x2,y2 with 'range' steps compatibleStep([x1, y1], [x2, y2], step, range) { const epsilon = 1e-7; //arbitrary small value let shifts = [0]; diff --git a/variants/Eightpieces/class.js b/variants/Eightpieces/class.js index 676ae90..62e718f 100644 --- a/variants/Eightpieces/class.js +++ b/variants/Eightpieces/class.js @@ -13,6 +13,10 @@ export default class EightpiecesRules extends ChessRules { }; } + static get LANCERS() { + return ['c', 'd', 'e', 'f', 'g', 'h', 'm', 'o']; + } + getLancerOptions(x, y) { let options = []; if (y > 0) @@ -63,10 +67,6 @@ export default class EightpiecesRules extends ChessRules { }; } - static get LANCERS() { - return ['c', 'd', 'e', 'f', 'g', 'h', 'm', 'o']; - } - // obj == "-", {-1,-1} or ["]{x,y}["] static convertPush(obj) { if (typeof obj === "string") @@ -159,11 +159,6 @@ export default class EightpiecesRules extends ChessRules { }, super.pieces(color, x, y)); } - canStepOver(i, j, p, c) { - const colIJ = this.getColor(i, j); - return this.board[i][j] == "" || (V.LANCERS.includes(p) && c == colIJ); - } - canIplay(x, y) { if ( this.pushFrom.x == x && this.pushFrom.y == y && @@ -174,6 +169,11 @@ export default class EightpiecesRules extends ChessRules { return super.canIplay(x, y); } + canStepOver(i, j, p, c) { + const colIJ = this.getColor(i, j); + return this.board[i][j] == "" || (V.LANCERS.includes(p) && c == colIJ); + } + isImmobilized([x, y]) { const color = this.getColor(x, y); const oppCol = C.GetOppTurn(color); @@ -215,18 +215,33 @@ export default class EightpiecesRules extends ChessRules { return res; } +// TODO: sentry nudge + + // after pushedTo, if lancer : allow reorient + move, or just reorient (move to king) if stuck + + // later, if stuck, allow reorient only (just click) + // doClick(coords) { TODO + getPotentialMovesFrom([x, y], color) { if (this.pushFrom.x < 0 || this.pushedTo.x >= 0) { let smoves = super.getPotentialMovesFrom([x, y], color); // Forbid direction x,y --> pushFrom if x,y == pushedTo if (x == this.pushedTo.x && y == this.pushedTo.y) { smoves = smoves.filter(m => { + let sx = this.pushFrom.x - x, + sy = this.pushFrom.y - y; + let divideBy = sx != 0 && sy != 0 && Math.abs(sx) != Math.abs(sy) + ? 1 + : Math.max(sx, sy); return ( !super.compatibleStep( [x, y], [m.end.x, m.end.y], - [this.pushFrom.x - x, this.pushFrom.y - y] + [sx / divideBy, sy / divideBy] ) ); }); } + + // TODO: lancer special case, move as a queen after push + return smoves.concat(this.getPassMoves(x, y)); } // pushFrom.x >= 0 && pushedTo.x < 0 @@ -242,39 +257,7 @@ export default class EightpiecesRules extends ChessRules { m.appear[0].c = m.vanish[0].c = oppCol; m.appear.push( new PiPo({x:x, y:y, p:'s', c:this.turn}) ); }); - -console.log(pmoves); - return pmoves; - - - - } - - postPlay(move) { - if ( - move.vanish.length > 0 && - move.vanish[0].p == 's' && - move.appear[0].c != move.vanish[0].c - ) { - // Sentry push ("capturing" part) - this.pushFrom = {x: move.end.x, y: move.end.y}; - this.pushedTo = {x: -1, y: -1}; - } - else if (move.vanish.length > 0 && move.vanish[0].c != this.turn) - this.pushedTo = {x: move.end.x, y: move.end.y}; - else { - // All other cases: just reset both push variables - this.pushFrom = {x: -1, y: -1}; - this.pushedTo = {x: -1, y: -1}; - } - super.postPlay(move); - } - - isLastMove(move) { - if (move.vanish[0].p == 's' && move.appear[0].c != move.vanish[0].c) - return false; - return super.isLastMove(move); } postProcessPotentialMoves(moves) { @@ -287,7 +270,7 @@ console.log(pmoves); ( (m.vanish.length > 0 && V.LANCERS.includes(m.vanish[0].p)) && // Next line test checks that the lancer wasn't just pushed away - (m.start.x != this.pushFrom.x || m.start.y != this.pushFrom.y) + (m.start.x != this.pushedTo.x || m.start.y != this.pushedTo.y) ) ) { this.getLancerOptions(m.end.x, m.end.y).forEach(o => { @@ -297,25 +280,68 @@ console.log(pmoves); }) ); }); } - else if (m.vanish.length == m.appear.length || m.vanish[0].p != 's') - finalMoves.push(m); - else { + else if (m.vanish.length == 2 && m.vanish[0].p == 's') { // Sentry "capture" --> remove sentry from final square (TODO: blink?) - const [x, y] = [m.end.x, m.end.y] - const p = this.getPiece(x, y); - const c = this.getColor(x, y); finalMoves.push( new Move({ - appear: [new PiPo({x:m.end.x,y:m.end.y,c:c,p:p})], - vanish: [ m.vanish[0] ] + appear: [], + vanish: [ m.vanish[0] ], + end: m.end }) ); } + else + finalMoves.push(m); } return finalMoves; } + postPlay(move) { + if ( + move.appear.length == 0 && + move.vanish.length > 0 && + move.vanish[0].p == 's' + ) { + // Sentry push ("capturing" part) + this.pushFrom = {x: move.end.x, y: move.end.y}; + this.pushedTo = {x: -1, y: -1}; + } + else if (move.vanish.length > 0 && move.vanish[0].c != this.turn) + this.pushedTo = {x: move.end.x, y: move.end.y}; + else { + // All other cases: just reset both push variables + this.pushFrom = {x: -1, y: -1}; + this.pushedTo = {x: -1, y: -1}; + } + super.postPlay(move); + } + + isLastMove(move) { + if (move.appear.length == 0) //move.vanish[0].p == 's' + return false; + return super.isLastMove(move); + } + underAttack([x, y], oppCols) { + if (super.underAttack([x, y], oppCols)) + return true; // TODO: check enemy sentry(ies), for each, check all of our own pieces which attack the square (if belonging to opponent!). Then, call : - return super.underAttack([x, y], oppCols); + const oppCol = oppCols[0]; + for (let i=0; i < this.size.x; i++) { + for (let j=0; j < this.size.y; j++) { + if ( + this.board[i][j] != "" && + this.getPiece(i, j) == 's' && + this.getColor(i, j) == oppCol + ) { + this.pieces()['s'].both[0].steps.forEach(s => { + let ii = i + s[0]; + // TODO......... + if (true) + return true; + }); + } + } + } + return false; } // Lazy sentry attacks check: after push move @@ -334,7 +360,7 @@ console.log(pmoves); updateReserve(color, piece, count) { if (V.LANCERS.includes(piece)) // Show only one lancer orientation, and reorient when drop: - piece = color == 'w' ? 'c' : 'g'; + piece = (color == 'w' ? 'c' : 'g'); super.updateReserve(color, piece, count); } -- 2.53.0 From 9cc8230edff273c041c0843f096a1e4cf60dcd16 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 9 May 2026 00:21:25 +0200 Subject: [PATCH 03/16] Finish 8-pieces implementation --- index.html | 1 - js/app.js | 12 ++- js/base_rules.js | 19 ++-- js/sanitize.js | 19 ---- js/server.js | 19 ++-- variants/Allmate/class.js | 6 +- variants/Ambiguous/class.js | 2 +- variants/Atomic/class.js | 4 +- variants/Chaining/class.js | 4 +- variants/Convert/class.js | 4 +- variants/Dynamo/class.js | 2 +- variants/Eightpieces/class.js | 186 ++++++++++++++++++++++++++++------ 12 files changed, 195 insertions(+), 83 deletions(-) delete mode 100644 js/sanitize.js diff --git a/index.html b/index.html index 5baa1bb..5b4c35f 100644 --- a/index.html +++ b/index.html @@ -124,7 +124,6 @@ - diff --git a/js/app.js b/js/app.js index 016a1c3..950d2d3 100644 --- a/js/app.js +++ b/js/app.js @@ -90,8 +90,7 @@ function h(tag, attrs, children) { function setName() { // 'onChange' event on name input text field [HTML] - const name = $.getElementById("myName").value; - localStorage.setItem("name", sanitize(name, 30)); + localStorage.setItem("name", $.getElementById("myName").value); } // Turn a "tab" on, and "close" all others @@ -573,7 +572,8 @@ async function initializeGame(obj) { const options = obj.options || {}; // 1. Dynamic loading of variant js module - const module = await import('/' + await manifest(`variants/${obj.vname}/class.js`)); + const module = + await import('/' + await manifest(`variants/${obj.vname}/class.js`)); window.V = module.default; // Export aliases in global scope (used by variants classes) @@ -605,7 +605,11 @@ async function initializeGame(obj) { // Create SVG icons with a string, inserted securely. const infoIcon = h('div', { id: 'upLeftInfos', onclick: toggleGameInfos }); - infoIcon.innerHTML = ``; + infoIcon.innerHTML = `\ +`; const stopIcon = h('div', { id: 'upRightStop', onclick: confirmStopGame }); stopIcon.innerHTML = ` diff --git a/js/base_rules.js b/js/base_rules.js index 4fc46ac..5ff92f8 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -156,7 +156,7 @@ export default class ChessRules { ], vanish: [] }); - res.drag = {c: this.captured.c, p: this.captured.p}; +// res.drag = {c: this.captured.c, p: this.captured.p}; //TODO? return res; } return null; @@ -909,9 +909,14 @@ export default class ChessRules { pieceWidth = this.getPieceWidth(r.width); const cd = this.idToCoords(e.target.id); if (cd) { - const move = this.doClick(cd); - if (move) - this.buildMoveStack(move, r); + const move_s = this.doClick(cd); + if (move_s) { + if (Array.isArray(move_s)) //8-pieces (at least.. only?) + this.showChoices(move_s, r); + else + // Usual case, single move + this.buildMoveStack(move_s, r); + } else if (!this.clickOnly) { const [x, y] = Object.values(cd); if (typeof x != "number") @@ -1535,7 +1540,7 @@ export default class ChessRules { // All possible moves from selected square // TODO: generalize usage if arg "color" (e.g. Checkered) - getPotentialMovesFrom([x, y], color) { + getPotentialMovesFrom([x, y], color, noPP) { if (this.subTurnTeleport == 2) return []; if (typeof x == "string") @@ -1548,7 +1553,9 @@ export default class ChessRules { Array.prototype.push.apply(moves, this.getEnpassantCaptures([x, y])); if (this.isKing(0, 0, piece) && this.hasCastle) Array.prototype.push.apply(moves, this.getCastleMoves([x, y])); - return this.postProcessPotentialMoves(moves); + if (!noPP) + moves = this.postProcessPotentialMoves(moves); + return moves; } postProcessPotentialMoves(moves) { diff --git a/js/sanitize.js b/js/sanitize.js deleted file mode 100644 index fe553f8..0000000 --- a/js/sanitize.js +++ /dev/null @@ -1,19 +0,0 @@ -const sanitize = function(str, maxLength = 100, relax = false) -{ - if (typeof str !== 'string') return ""; - // 1. Cut string to avoid memory overload - let cleaned = str.substring(0, maxLength); - // 2. Replace special characters by HTML entities - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - const regexp = relax ? /[<>]/g : /[&<>"']/g - return cleaned.replace(regexp, m => map[m]); -} - -// Next line for usage on server (Node.js) -if (typeof window === 'undefined') module.exports = sanitize; diff --git a/js/server.js b/js/server.js index 85e6066..2c0211e 100644 --- a/js/server.js +++ b/js/server.js @@ -1,5 +1,4 @@ const params = require("./parameters.js"); -const sanitize = require("./sanitize.js"); const WebSocket = require("ws"); const wss = new WebSocket.Server({ port: params.socket_port, @@ -93,17 +92,19 @@ wss.on("connection", (socket, req) => { } // Basic security on recurrent fields - if (obj.vname) { - obj.vname = sanitize(obj.vname, 50); - if (obj.vname != "_random" && !variants.find(v => v.name == obj.vname)) + if ( + obj.vname && + obj.vname != "_random" && + !variants.find(v => v.name == obj.vname) + ) { return; //unknown variant name } - if (obj.name) //TODO: probably overkill.. - obj.name = sanitize(obj.name, 30); + if (obj.name) + obj.name = obj.name.substring(0, 32); if (obj.fen) - obj.fen = sanitize(obj.fen, 500, true); + obj.fen = obj.fen.substring(0, 1024); if (obj.gid) - obj.gid = sanitize(obj.gid, 20); + obj.gid = obj.gid.substring(0, 16); switch (obj.code) { // Send challenge (may trigger game creation) @@ -241,7 +242,7 @@ wss.on("connection", (socket, req) => { if ( !games[obj.gid] || !Array.isArray(obj.moves) || - obj.moves.length > 20 + obj.moves.length > 32 ) { return; } diff --git a/variants/Allmate/class.js b/variants/Allmate/class.js index eb435e8..1de9327 100644 --- a/variants/Allmate/class.js +++ b/variants/Allmate/class.js @@ -64,13 +64,13 @@ export default class AllmateRules extends ChessRules { for (let i=0; i m.end.x == target.x && m.end.y == target.y); } diff --git a/variants/Atomic/class.js b/variants/Atomic/class.js index 83f2a2b..d8d64f8 100644 --- a/variants/Atomic/class.js +++ b/variants/Atomic/class.js @@ -30,7 +30,7 @@ export default class AtomicRules extends ChessRules { return super.canIplay(x, y); } - getPotentialMovesFrom([x, y], color) { + getPotentialMovesFrom([x, y]) { if (this.options["rempawn"] && this.movesCount == 0) { if ([1, 6].includes(x)) { const c = this.getColor(x, y); @@ -52,7 +52,7 @@ export default class AtomicRules extends ChessRules { } return []; } - return super.getPotentialMovesFrom([x, y], color); + return super.getPotentialMovesFrom([x, y]); } doClick(square) { diff --git a/variants/Chaining/class.js b/variants/Chaining/class.js index f4794a2..7aaec70 100644 --- a/variants/Chaining/class.js +++ b/variants/Chaining/class.js @@ -86,7 +86,7 @@ export default class ChainingRules extends ChessRules { return super.getPiece(x, y); } - getPotentialMovesFrom([x, y], color) { + getPotentialMovesFrom([x, y]) { const L = this.lastMoveEnd.length; if ( L >= 1 && @@ -95,7 +95,7 @@ export default class ChainingRules extends ChessRules { // A self-capture was played: wrong square return []; } - return super.getPotentialMovesFrom([x, y], color); + return super.getPotentialMovesFrom([x, y]); } isLastMove(move) { diff --git a/variants/Convert/class.js b/variants/Convert/class.js index ecaa684..f2a0b13 100644 --- a/variants/Convert/class.js +++ b/variants/Convert/class.js @@ -87,7 +87,7 @@ export default class ConvertRules extends ChessRules { return super.getPiece(x, y); } - getPotentialMovesFrom([x, y], color) { + getPotentialMovesFrom([x, y]) { const L = this.lastMoveEnd.length; if ( L >= 1 && @@ -96,7 +96,7 @@ export default class ConvertRules extends ChessRules { // A capture was played: wrong square return []; } - return super.getPotentialMovesFrom([x, y], color); + return super.getPotentialMovesFrom([x, y]); } underAttack_aux([x, y], color, explored) { diff --git a/variants/Dynamo/class.js b/variants/Dynamo/class.js index de36887..3ec2c3f 100644 --- a/variants/Dynamo/class.js +++ b/variants/Dynamo/class.js @@ -205,7 +205,7 @@ export default class DynamoRules extends ChessRules { // Free to play any move (if piece of my color): let moves = []; if (sqCol == color) { - moves = super.getPotentialMovesFrom([x, y]) + moves = super.getPotentialMovesFrom([x, y]); if (this.canReachBorder(x, y)) this.addExitMove(moves, [x, y], kp); } diff --git a/variants/Eightpieces/class.js b/variants/Eightpieces/class.js index 62e718f..46d66d2 100644 --- a/variants/Eightpieces/class.js +++ b/variants/Eightpieces/class.js @@ -23,18 +23,18 @@ export default class EightpiecesRules extends ChessRules { options.push('m'); if (y < this.size.y) options.push('e'); - if (x < this.size.x) { + if (x < this.size.x - 1) { options.push('g'); if (y > 0) options.push('h'); - if (y < this.size.y) + if (y < this.size.y - 1) options.push('f'); } if (x > 0) { options.push('c'); if (y > 0) options.push('o'); - if (y < this.size.y) + if (y < this.size.y - 1) options.push('d'); } return options; @@ -62,7 +62,7 @@ export default class EightpiecesRules extends ChessRules { "/pppppppp/8/8/8/8/PPPPPPPP/" + s.w.join("").replace('l', random > 0 ? 'c' : 'd').toUpperCase(); return { - fen: 'jfs1kb1r/1P3ppp/3p1q2/p7/2P5/8/P2PPPPP/J1SQKBNR', // fen, + fen, o: {flags: s.flags} }; } @@ -94,7 +94,7 @@ export default class EightpiecesRules extends ChessRules { pieces(color, x, y) { const mirror = (this.playerColor == 'b'); - return Object.assign({ + return { 'j': { "class": "jailer", moves: [ @@ -156,7 +156,8 @@ export default class EightpiecesRules extends ChessRules { {steps: [[-1, -1]]} ] }, - }, super.pieces(color, x, y)); + ...super.pieces(color, x, y) + }; } canIplay(x, y) { @@ -215,16 +216,49 @@ export default class EightpiecesRules extends ChessRules { return res; } -// TODO: sentry nudge - - // after pushedTo, if lancer : allow reorient + move, or just reorient (move to king) if stuck - - // later, if stuck, allow reorient only (just click) - // doClick(coords) { TODO + // Reorient immobilized or stuck lancer + doClick(coords) { + if (typeof coords.x != "number") + return null; //click on reserves + if (this.board[coords.x][coords.y] != "") { + const p = this.getPiece(coords.x, coords.y), + c = this.getColor(coords.x, coords.y); + const lopts = this.getLancerOptions(coords.x, coords.y); + if ( + V.LANCERS.includes(p) && + ( + this.pushedTo.x < 0 && //not just pushed + !lopts.includes(p) //can't move + ) + || + ( + this.pieces()['j'].moves[0].steps.some(s => { + const [i, j] = [coords.x + s[0], coords.y + s[1]]; + return ( + this.onBoard(i, j) && + this.getPiece(i, j) == 'j' && + this.getColor(i, j) != c + ); + }) + ) + ) { + return lopts.filter(o => o != p).map(o => { + return new Move({ + appear: [ new PiPo({x: coords.x, y: coords.y, p: o, c: c}) ], + vanish: [ new PiPo({x: coords.x, y: coords.y, p: p, c: c}) ] + }); + }); + } + } + return null; + } - getPotentialMovesFrom([x, y], color) { + getPotentialMovesFrom([x, y]) { + const p = this.getPiece(x, y); + let color = this.getColor(x, y); + let oppCol = C.GetOppTurn(color) if (this.pushFrom.x < 0 || this.pushedTo.x >= 0) { - let smoves = super.getPotentialMovesFrom([x, y], color); + let smoves = super.getPotentialMovesFrom([x, y]); // Forbid direction x,y --> pushFrom if x,y == pushedTo if (x == this.pushedTo.x && y == this.pushedTo.y) { smoves = smoves.filter(m => { @@ -238,39 +272,111 @@ export default class EightpiecesRules extends ChessRules { [sx / divideBy, sy / divideBy] ) ); }); + if (V.LANCERS.includes(p)) { + // Allow all other directions without reorient + const ls = this.pieces()[p].both[0].steps[0]; + for (const lCode of V.LANCERS) { + const s = this.pieces()[lCode].both[0].steps[0]; + if (s[0] != ls[0] || s[1] != ls[1]) { + this.board[x][y] = color + lCode; + super.findDestSquares([x, y], {}).forEach(r => { + let mv = new Move({ + appear: [ + new PiPo({x: r.sq[0], y: r.sq[1], p: lCode, c: color})], + vanish: [ + new PiPo({x: x, y: y, p: p, c: color})], + noReorient: true + }); + if (this.board[r.sq[0]][r.sq[1]] != "") { + mv.vanish.push( + new PiPo({ + x: r.sq[0], + y: r.sq[1], + p: this.getPiece(r.sq[0], r.sq[1]), + c: oppCol + }) + ); + } + smoves.push(mv); + }); + } + } + this.board[x][y] = color + p; + // Add reorient-only moves, if stuck: + const lopts = this.getLancerOptions(x, y); + if (!lopts.includes(p)) { + const kp = this.searchKingPos(color)[0]; + Array.prototype.push.apply(smoves, lopts.map(o => { + return new Move({ + appear: [ new PiPo({x: x, y: y, p: o, c: color}) ], + vanish: [ new PiPo({x: x, y: y, p: p, c: color}) ], + end: {x: kp[0], y: kp[1]} + }); + }) ); + } + } } - - // TODO: lancer special case, move as a queen after push - return smoves.concat(this.getPassMoves(x, y)); } // pushFrom.x >= 0 && pushedTo.x < 0 if (x != this.pushFrom.x || y != this.pushFrom.y) return []; // After sentry "attack": move enemy as if it was ours - const p = this.getPiece(x, y); - this.board[x][y] = this.turn + p; - let pmoves = super.getPotentialMovesFrom([x, y], this.turn); - const oppCol = C.GetOppTurn(this.turn) + [color, oppCol] = [oppCol, color]; + this.board[x][y] = color + p; + let pmoves = super.getPotentialMovesFrom([x, y], color, true) + .filter(m => m.appear.length > 0); //exclude sentry "captures" + if (V.LANCERS.includes(p)) { + pmoves.forEach(m => m.noReorient = true); + // Allow all other steps by 1 square (nudge) + const ls = this.pieces()[p].both[0].steps[0]; + let nextP = p; + for (const lCode of V.LANCERS) { + const s = this.pieces()[lCode].both[0].steps[0]; + if ( + (s[0] != ls[0] || s[1] != ls[1]) && + this.onBoard(x + s[0], y + s[1]) && + this.board[x + s[0]][y + s[1]] == "" + ) { + let mv = new Move({ + appear: [new PiPo({x: x + s[0], y: y + s[1], p: lCode, c: color})], + vanish: [new PiPo({x: x, y: y, p: p, c: color})] + }); + mv.noReorient = true; + pmoves.push(mv); + } + } + } this.board[x][y] = oppCol + p; pmoves.forEach(m => { m.appear[0].c = m.vanish[0].c = oppCol; m.appear.push( new PiPo({x:x, y:y, p:'s', c:this.turn}) ); }); - return pmoves; + return this.postProcessPotentialMoves(pmoves); } postProcessPotentialMoves(moves) { moves = super.postProcessPotentialMoves(moves); let finalMoves = []; for (const m of moves) { + // Drop lancers "self captures" (not from sentry push): + if ( + !m.noReorient && + m.vanish.length == 2 && + m.vanish[0].c == m.vanish[1].c + ) { + continue; + } // Reorient a lancer after drop or regular move if ( - (m.vanish.length == 0 && ['c', 'g'].includes(m.appear[0].p)) || + !m.noReorient && ( - (m.vanish.length > 0 && V.LANCERS.includes(m.vanish[0].p)) && - // Next line test checks that the lancer wasn't just pushed away - (m.start.x != this.pushedTo.x || m.start.y != this.pushedTo.y) + (m.vanish.length == 0 && ['c', 'g'].includes(m.appear[0].p)) || + ( + (m.vanish.length > 0 && V.LANCERS.includes(m.vanish[0].p)) && + // Next line test checks that the lancer wasn't just pushed away + (m.start.x != this.pushedTo.x || m.start.y != this.pushedTo.y) + ) ) ) { this.getLancerOptions(m.end.x, m.end.y).forEach(o => { @@ -323,8 +429,8 @@ export default class EightpiecesRules extends ChessRules { underAttack([x, y], oppCols) { if (super.underAttack([x, y], oppCols)) return true; - // TODO: check enemy sentry(ies), for each, check all of our own pieces which attack the square (if belonging to opponent!). Then, call : const oppCol = oppCols[0]; + const color = C.GetOppTurn(oppCol); for (let i=0; i < this.size.x; i++) { for (let j=0; j < this.size.y; j++) { if ( @@ -332,12 +438,26 @@ export default class EightpiecesRules extends ChessRules { this.getPiece(i, j) == 's' && this.getColor(i, j) == oppCol ) { - this.pieces()['s'].both[0].steps.forEach(s => { - let ii = i + s[0]; - // TODO......... - if (true) - return true; - }); + this.board[i][j] = oppCol + 'b'; + // Find enemy sentries "attacks": + const rs = super.findDestSquares([i, j], {attackOnly: true}); + this.board[i][j] = oppCol + 's'; + // Can any of these pieces capture our king? + for (const r of rs) { + const p = this.getPiece(r.sq[0], r.sq[1]); + if (['j', 's'].includes(p)) + continue; + const specs = this.pieces(oppCol, r.sq[0], r.sq[1])[p]; + const steps = (specs.both || specs.attack)[0].steps; + let res = false; + for (const s of steps) { + let [ii, jj] = [r.sq[0] + s[0], r.sq[1] + s[1]]; + while (this.onBoard(ii, jj) && this.board[ii][jj] == "") + [ii, jj] = [ii + s[0], jj + s[1]]; + if (ii == x && jj == y) + return true; + } + } } } } -- 2.53.0 From 5c94505290b94cef028d7c3c7bb74bbc32794961 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 9 May 2026 00:42:52 +0200 Subject: [PATCH 04/16] Typo --- bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle.py b/bundle.py index 8353cf9..f703443 100755 --- a/bundle.py +++ b/bundle.py @@ -17,7 +17,7 @@ IGNORE_FILE_UPDATE = {"app.js"} # Files and folders to totally ignore IGNORE_FILES = { "LICENSE", "README.md", "TODO", "bundle.py", "js/parameters.js.dist", - "initialize.sh", "package-lock.json", "package.json", "js/server.js", ".gitignore" + "initialize.sh", "package-lock.json", "package.json", "js/server.js", ".gitignore", "nginx_config.example", "start.sh", "stop.sh", "assets.zip", "extras.zip", ".pid" } IGNORE_DIRS = {".git", "node_modules", DEST_DIR} -- 2.53.0 From 5313fe1a27d1d8a8201e5f06d05b5b49992f75be Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 9 May 2026 01:26:14 +0200 Subject: [PATCH 05/16] update --- TODO | 2 -- bundle.py | 2 +- favicon.ico | 1 - index.html | 1 + js/app.js | 14 +++++++++++--- nginx_config.example | 26 +++++++++++++++++++------- 6 files changed, 32 insertions(+), 14 deletions(-) delete mode 120000 favicon.ico diff --git a/TODO b/TODO index fd12ebe..f49a584 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -Smartphone issue --> Play button fails (seekGame à corriger) - Hmm... non ? --> Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number ==> plus simple : deux classes, images superposées. diff --git a/bundle.py b/bundle.py index f703443..0b3b451 100755 --- a/bundle.py +++ b/bundle.py @@ -9,7 +9,7 @@ import re # --- Configuration --- SOURCE_DIR = "." DEST_DIR = "dist" -EXTENSIONS_TO_HASH = [".html", ".js", ".css", ".svg"] #all edited files +EXTENSIONS_TO_HASH = [".html", ".js", ".css", ".svg", ".ico", ".png", ".jpg"] EXTENSIONS_TO_UPDATE = [".html", ".js", ".css"] #.svg don't contain refs DYNAMIC_LOAD_EXTENSIONS = (".html", ".js", ".css") #loaded from app.js diff --git a/favicon.ico b/favicon.ico deleted file mode 120000 index f6ff788..0000000 --- a/favicon.ico +++ /dev/null @@ -1 +0,0 @@ -assets/favicon.ico \ No newline at end of file diff --git a/index.html b/index.html index 5b4c35f..e5e3ded 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,7 @@ content="width=device-width, initial-scale=1"/> + `; +10S56.022,35.5,50.5,35.5z" fill="darkgrey"/>`; const stopIcon = h('div', { id: 'upRightStop', onclick: confirmStopGame }); - stopIcon.innerHTML = ` - + stopIcon.innerHTML = `\ + `; const board = h('div', { class: 'chessboard' }); diff --git a/nginx_config.example b/nginx_config.example index a798fb0..7d10d8c 100644 --- a/nginx_config.example +++ b/nginx_config.example @@ -21,20 +21,25 @@ server { error_log /var/log/nginx/xogo_error.log; access_log /var/log/nginx/xogo_access.log; - # 1. Entry point: always check if there was an update - location = /index.html { + # + # Entry point: always revalidate + # + location ~* ^/(index\.html|manifest\.json)$ { add_header Cache-Control "no-cache, must-revalidate"; - expires 0; } - # 2. Static hashed files (JS, CSS) + assets: agressive caching - location / { - try_files $uri $uri/ /index.html; + # + # Static hashed assets + # + location ~* \.(js|css|html|png|jpg|svg|ico|ttf)$ { expires 1y; add_header Cache-Control "public, immutable"; + try_files $uri =404; } - # 3. WebSocket server proxy + # + # WebSocket server proxy + # location /ws { proxy_pass http://localhost:WS_PORT; proxy_http_version 1.1; @@ -44,6 +49,13 @@ server { proxy_buffering off; } + # + # SPA fallback + # + location / { + try_files $uri $uri/ /index.html; + } + ssl_certificate /etc/letsencrypt/live/my.server.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/my.server.com/privkey.pem; } -- 2.53.0 From e4ffe9930115038efdf76678fcb0bd9dd97ea12e Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 9 May 2026 01:33:54 +0200 Subject: [PATCH 06/16] Update initialize script --- initialize.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/initialize.sh b/initialize.sh index 6fc6ad8..bf8d705 100755 --- a/initialize.sh +++ b/initialize.sh @@ -1,7 +1,7 @@ !#/bin/sh -wget https://xogo.live/assets.zip && unzip assets.zip -wget https://xogo.live/extras.zip && unzip extras.zip +wget https://xogo.casa/assets.zip && unzip assets.zip +wget https://xogo.casa/extras.zip && unzip extras.zip cp parameters.js.dist parameters.js npm i cd pieces/Avalam && python generateSVG.py && cd ../.. -- 2.53.0 From c5fd037c7c787097c5c731a2d8c1bd070be692ed Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 9 May 2026 12:42:46 +0200 Subject: [PATCH 07/16] Some fixes, improvements... --- js/base_rules.js | 21 ++++++++-- js/server.js | 2 +- variants/Alapo/class.js | 13 ++++-- variants/Apocalypse/class.js | 3 ++ variants/Atarigo/class.js | 9 +++- variants/Avalam/class.js | 6 ++- variants/Discoduel/class.js | 3 ++ variants/Giveaway/class.js | 4 ++ variants/Hex/class.js | 3 ++ variants/Sleepy/class.js | 81 ++++++++++++++++++++---------------- variants/Weiqi/class.js | 6 ++- 11 files changed, 102 insertions(+), 49 deletions(-) diff --git a/js/base_rules.js b/js/base_rules.js index 5ff92f8..347d5b2 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -66,7 +66,7 @@ export default class ChessRules { "cannibal", "capture", "crazyhouse", - "cylinder", //ok with all + "cylinder", //ok with ~all "dark", "doublemove", "madrasi", @@ -117,6 +117,11 @@ export default class ChessRules { return this.hasReserve; } + // Some variants don't have kings, not relevant + static get HasKing() { + return true; + } + get noAnimate() { return !!this.options["dark"]; } @@ -1187,6 +1192,8 @@ export default class ChessRules { } isKing(x, y, p) { + if (!V.HasKing) + return false; if (!p) p = this.getPiece(x, y); if (!this.options["cannibal"]) @@ -2244,6 +2251,8 @@ export default class ChessRules { // 'color' arg because some variants (e.g. Refusal) check opponent moves filterValid(moves, color) { + if (!V.HasKing) + return moves; color = color || this.turn; const oppCols = this.getOppCols(color); let kingPos = this.searchKingPos(color); @@ -2401,9 +2410,11 @@ export default class ChessRules { if (move.next) return false; const color = this.turn; - const oppKingPos = this.searchKingPos(C.GetOppTurn(color)); - if (oppKingPos.length == 0 || this.underCheck(oppKingPos, [color])) - return true; + if (V.HasKing) { + const oppKingPos = this.searchKingPos(C.GetOppTurn(color)); + if (oppKingPos.length == 0 || this.underCheck(oppKingPos, [color])) + return true; + } return ( ( !this.options["balance"] || @@ -2453,6 +2464,8 @@ export default class ChessRules { // Shortcut in case the score was computed before: if (move.result) return move.result; + if (!V.HasKing) + return "*"; //can't guess better.. const oppTurn = C.GetOppTurn(this.turn); const kingPos = { w: this.searchKingPos('w'), diff --git a/js/server.js b/js/server.js index 2c0211e..1104b94 100644 --- a/js/server.js +++ b/js/server.js @@ -54,7 +54,7 @@ function getRandomVariant() { if (params.dev) { const chokidar = require("chokidar"); const watcher = chokidar.watch( - ["*.js", "*.css", "utils/", "variants/"], + ["*.js", "*.css", "css/", "js/", "pieces/", "utils/", "variants/"], { persistent: true } ); diff --git a/variants/Alapo/class.js b/variants/Alapo/class.js index 4ad6f88..fa047eb 100644 --- a/variants/Alapo/class.js +++ b/variants/Alapo/class.js @@ -8,7 +8,7 @@ export default class AlapoRules extends ChessRules { static get Options() { return { select: C.Options.select, - styles: C.Options.styles.filter(s => s != "teleport") + styles: C.Options.styles.filter(s => !["recycle","teleport"].includes(s)) }; } @@ -18,6 +18,9 @@ export default class AlapoRules extends ChessRules { get hasEnpassant() { return false; } + static get HasKing() { + return false; + } getSvgChessboard() { let board = super.getSvgChessboard().slice(0, -6); @@ -49,7 +52,7 @@ export default class AlapoRules extends ChessRules { s.w.map(p => piece2pawn[p].toUpperCase()).join("") + "/" + s.w.join("").toUpperCase() ); - return { fen: fen, o: {} }; + return { fen, o: {} }; } // Triangles are rotated from opponent viewpoint (=> suffix "_inv") @@ -58,8 +61,10 @@ export default class AlapoRules extends ChessRules { return { 'r': allSpecs['r'], 'q': allSpecs['q'], - 'b': Object.assign({}, allSpecs['b'], - {"class": "bishop" + (this.playerColor != color ? "_inv" : "")}), + 'b': { + "class": "bishop" + (this.playerColor != color ? "_inv" : ""), + ...allSpecs['b'] + }, 's': { //"square" "class": "babyrook", both: [ diff --git a/variants/Apocalypse/class.js b/variants/Apocalypse/class.js index e77bb2c..c380eaa 100644 --- a/variants/Apocalypse/class.js +++ b/variants/Apocalypse/class.js @@ -16,6 +16,9 @@ export default class ApocalypseRules extends ChessRules { get hideMoves() { return true; } + static get HasKing() { + return false; + } pawnPromotions() { return ['n', 'p']; diff --git a/variants/Atarigo/class.js b/variants/Atarigo/class.js index 1b848c9..120afdc 100644 --- a/variants/Atarigo/class.js +++ b/variants/Atarigo/class.js @@ -8,7 +8,14 @@ export default class AtarigoRules extends WeiqiRules { static get Options() { let input = WeiqiRules.Options.input; input[0].defaut = 11; - return {input: input}; + return { + input, + styles: WeiqiRules.Options.styles + }; + } + + static get HasKing() { + return false; } getCurrentScore(move_s) { diff --git a/variants/Avalam/class.js b/variants/Avalam/class.js index 6e9fc3e..13cc55e 100644 --- a/variants/Avalam/class.js +++ b/variants/Avalam/class.js @@ -23,7 +23,8 @@ export default class AvalamRules extends ChessRules { type: "checkbox", defaut: false } - ] + ], + styles: ["doublemove", "progressive"] }; } @@ -33,6 +34,9 @@ export default class AvalamRules extends ChessRules { get hasEnpassant() { return false; } + static get HasKing() { + return false; + } pieces(color, x, y) { const steps = [ diff --git a/variants/Discoduel/class.js b/variants/Discoduel/class.js index 50ebee0..07502aa 100644 --- a/variants/Discoduel/class.js +++ b/variants/Discoduel/class.js @@ -14,6 +14,9 @@ export default class DiscoduelRules extends ChessRules { get hasFlags() { return false; } + static get HasKing() { + return false; + } genRandInitBaseFen() { return { diff --git a/variants/Giveaway/class.js b/variants/Giveaway/class.js index c08f75a..b34be7b 100644 --- a/variants/Giveaway/class.js +++ b/variants/Giveaway/class.js @@ -21,6 +21,7 @@ export default class GiveawayRules extends ChessRules { input: C.Options.input.filter(i => i.variable == "pawnfall"), styles: [ "atomic", "cannibal", "cylinder", "dark", + "doublemove", "progressive", "madrasi", "rifle", "teleport", "zen" ] }; @@ -29,6 +30,9 @@ export default class GiveawayRules extends ChessRules { get hasFlags() { return this.options["mode"] == "losers"; } + static get HasKing() { + return this.options["mode"] == "losers"; + } pawnPromotions() { let res = ['q', 'r', 'n', 'b']; diff --git a/variants/Hex/class.js b/variants/Hex/class.js index 05200be..22dee0d 100644 --- a/variants/Hex/class.js +++ b/variants/Hex/class.js @@ -38,6 +38,9 @@ export default class HexRules extends AbstractClickFillRules { get clickOnly() { return true; } + static get HasKing() { + return false; + } doClick(coords) { if ( diff --git a/variants/Sleepy/class.js b/variants/Sleepy/class.js index b40ebab..b50ca0b 100644 --- a/variants/Sleepy/class.js +++ b/variants/Sleepy/class.js @@ -2,58 +2,65 @@ import ChessRules from "/js/base_rules.js"; export default class SleepyRules extends ChessRules { + static get Options() { + return { + select: C.Options.select, + input: C.Options.input, + styles: ["balance", "capture", "cylinder", "dark", "rifle", "zen"] + }; + } + + setOtherVariables(fenParsed) { + super.setOtherVariables(fenParsed); + this.states = JSON.parse(fenParsed.states); + } + + getPartFen(o) { + return { + "states": (o.init ? '0'.repeat(64) : JSON.stringify(this.states)), + ...super.getPartFen(o) + }; + } + pieces(color, x, y) { - let res = super.pieces(color, x, y); - res['s'] = {"class": "sleepy-pawn", moveas: "p"}; - res['u'] = {"class": "sleepy-rook", moveas: "r"}; - res['o'] = {"class": "sleepy-knight", moveas: "n"}; - res['c'] = {"class": "sleepy-bishop", moveas: "b"}; - res['t'] = {"class": "sleepy-queen", moveas: "q"}; - return res; + return { + 's': {"class": "sleepy-pawn"}, + 'u': {"class": "sleepy-rook"}, + 'o': {"class": "sleepy-knight"}, + 'c': {"class": "sleepy-bishop"}, + 't': {"class": "sleepy-queen"}, + ...super.pieces(color, x, y) + }; } - static get V_PIECES() { + static get M_PIECES() { return ['p', 'r', 'n', 'b', 'q']; } static get S_PIECES() { return ['s', 'u', 'o', 'c', 't']; } - // Forbid sleepy pieces to capture - canTake([x1, y1], [x2, y2]) { - return ( - this.getColor(x1, y1) !== this.getColor(x2, y2) && - (['k'].concat(V.V_PIECES)).includes(this.getPiece(x1, y1)) - ); + getPotentialMovesOf(piece, [x, y]) { + if (V.S_PIECES.includes(piece)) + return []; + return super.getPotentialMovesOf(piece, [x, y]); } - - //TODO: - - - pawnPostProcess(moves, color, oppCols) { - let res = super.pawnPostProcess(moves, color, oppCols); - if (res.length > 0 && res[0].vanish[0].p == 's') { - // Fix promotions of non-violent pawns (if any) - res.forEach(m => { - if (m.appear[0].p != 's') - m.appear[0].p = V.NV_PIECES[V.V_PIECES.indexOf(m.appear[0].p)]; - }); - } - return res; + getStateIndex(coords) { + return this.size.y * coords.x + coords.y; } prePlay(move) { super.prePlay(move); - // NOTE: drop moves already taken into account in base prePlay() - if (move.vanish.length == 2 && move.appear.length == 1) { - const normal = V.V_PIECES.includes(move.vanish[1].p); - const pIdx = - (normal ? V.V_PIECES : V.NV_PIECES).indexOf(move.vanish[1].p); - const resPiece = (normal ? V.NV_PIECES : V.V_PIECES)[pIdx]; - super.updateReserve(C.GetOppTurn(this.turn), resPiece, - this.reserve[C.GetOppTurn(this.turn)][resPiece] + 1); - } + // 1) Wake up observed pieces + // TODO: findDestSquares(attackOnly = true) with changing piece color on board + // Loop on found squares + if index in S_PIECES then change + // 2) Update sleepy status + const indices = [move.start, move.end].map(this.getStateIndex); + this.states[indices[1]] = this.states[indices[0]] + 1; + this.states[indices[0]] = 0; + if (this.states[indices[1]] >= 3) + move.appear[0].p = V.S_PIECES[V.M_PIECES.indexOf(move.appear[0].p)]; } }; diff --git a/variants/Weiqi/class.js b/variants/Weiqi/class.js index 6f6b1d8..f9ca469 100644 --- a/variants/Weiqi/class.js +++ b/variants/Weiqi/class.js @@ -20,7 +20,8 @@ export default class WeiqiRules extends ChessRules { type: "checkbox", defaut: false } - ] + ], + styles: ["doublemove", "progressive"] }; } @@ -33,6 +34,9 @@ export default class WeiqiRules extends ChessRules { get clickOnly() { return true; } + static get HasKing() { + return false; + } getSvgChessboard() { const flipped = (this.playerColor == 'b'); -- 2.53.0 From b2c43d1b3ea0f8dc63eb0ff095149effd4c70a79 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 11 May 2026 01:58:32 +0200 Subject: [PATCH 08/16] Finish Sleepy, update TODO --- TODO | 8 ++--- js/variants.js | 2 ++ variants/Sleepy/class.js | 75 ++++++++++++++++++++++++++++++++-------- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/TODO b/TODO index f49a584..7114f80 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,6 @@ -Hmm... non ? --> Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number -==> plus simple : deux classes, images superposées. +==> plus simple : deux classes, images superposées (?) https://fr.wikipedia.org/wiki/Unlur -Yoxii ? -Idée new variant: "bed" random moves and "capture" (opponent?) pieces which cannot move on next turn (sleeping). (Crazybed ?) -one "bed" per player. -one turn = place bed (potentially with opponent piece), then normal move, then random bed move - dropping potential piece on initial square (awaken). +DuckChess + crazyduck --> move it at random ? Capturing ? Hmm. diff --git a/js/variants.js b/js/variants.js index 6519dcc..6b3e6aa 100644 --- a/js/variants.js +++ b/js/variants.js @@ -133,6 +133,7 @@ const variants = [ // {name: 'Shogi', desc: 'Japanese Chess'}, // {name: 'Shogun', desc: "General's Chess"}, // {name: 'Sittuyin', desc: 'Burmese Chess'}, + {name: 'Sleepy', desc: 'Lazy pieces', disp: 'Sleepy'}, // {name: 'Spartan', desc: 'Spartan versus Persians'}, // {name: 'Squatter', desc: 'Squat last rank'}, // {name: 'Stealthbomb', desc: 'Beware the bomb'}, @@ -154,6 +155,7 @@ const variants = [ // {name: 'Wormhole', desc: 'Squares disappear'}, // {name: 'Xiangqi', desc: 'Chinese Chess'}, // {name: 'Yote', desc: 'African Draughts'}, +// {name: 'Yoxii', desc: 'Moving totem'}, {name: "Zen", desc: "Reverse captures"} ]; diff --git a/variants/Sleepy/class.js b/variants/Sleepy/class.js index b50ca0b..07c9a9b 100644 --- a/variants/Sleepy/class.js +++ b/variants/Sleepy/class.js @@ -1,23 +1,31 @@ import ChessRules from "/js/base_rules.js"; +import PiPo from "/utils/PiPo.js"; export default class SleepyRules extends ChessRules { static get Options() { return { select: C.Options.select, - input: C.Options.input, - styles: ["balance", "capture", "cylinder", "dark", "rifle", "zen"] + input: C.Options.input.concat([ + { + label: "Reset friends", + variable: "refresh", + type: "checkbox", + defaut: false + } + ]), + styles: ["balance", "capture", "cylinder", "dark", "zen"] }; } setOtherVariables(fenParsed) { + this.states = fenParsed.states.split('').map(x => parseInt(x, 10)); super.setOtherVariables(fenParsed); - this.states = JSON.parse(fenParsed.states); } getPartFen(o) { return { - "states": (o.init ? '0'.repeat(64) : JSON.stringify(this.states)), + "states": (o.init ? '0'.repeat(64) : this.states.join('')), ...super.getPartFen(o) }; } @@ -46,21 +54,58 @@ export default class SleepyRules extends ChessRules { return super.getPotentialMovesOf(piece, [x, y]); } + // TODO: should post-process only the chosen move ? + postProcessPotentialMoves(moves) { + moves = super.postProcessPotentialMoves(moves); + moves.forEach(mv => { + mv.statePatch = {}; + // 1) Wake up observed pieces + const color = mv.vanish[0].c; + this.board[mv.start.x][mv.start.y] = ""; + const rs = super.findDestSquares( + [mv.end.x, mv.end.y], + { + attackOnly: true, + // NOTE: wake up others just before falling asleep + stepSpec: super.getStepSpec(mv.vanish[0].c, 0, 0, mv.vanish[0].p) + }, + (sq1, [x2, y2]) => this.getColor(x2, y2) == color + ); + this.board[mv.start.x][mv.start.y] = color + mv.vanish[0].p; + rs.forEach(r => { + const r_idx = this.getStateIndex({x: r.sq[0], y: r.sq[1]}); + if (!this.states) debugger; + if (this.states[r_idx] == 3) { + const sleepyPiece = this.getPiece(r.sq[0], r.sq[1]); + const awokenPiece = V.M_PIECES[V.S_PIECES.indexOf(sleepyPiece)] + mv.vanish.push( + new PiPo({x: r.sq[0], y: r.sq[1], c: color, p: sleepyPiece}) ); + mv.appear.push( + new PiPo({x: r.sq[0], y: r.sq[1], c: color, p: awokenPiece}) ); + mv.statePatch[r_idx] = 0; + } + else if (this.options["refresh"] && this.states[r_idx] >= 1) + mv.statePatch[r_idx] = 0; + }); + // 2) Update sleepy status + const m_idx = this.getStateIndex(mv.start); + if (this.states[m_idx] == 2) + mv.appear[0].p = V.S_PIECES[V.M_PIECES.indexOf(mv.appear[0].p)]; + mv.statePatch[m_idx] = 0; + mv.statePatch[this.getStateIndex(mv.end)] = this.states[m_idx] + 1; + }); + return moves; + } + getStateIndex(coords) { return this.size.y * coords.x + coords.y; } - prePlay(move) { - super.prePlay(move); - // 1) Wake up observed pieces - // TODO: findDestSquares(attackOnly = true) with changing piece color on board - // Loop on found squares + if index in S_PIECES then change - // 2) Update sleepy status - const indices = [move.start, move.end].map(this.getStateIndex); - this.states[indices[1]] = this.states[indices[0]] + 1; - this.states[indices[0]] = 0; - if (this.states[indices[1]] >= 3) - move.appear[0].p = V.S_PIECES[V.M_PIECES.indexOf(move.appear[0].p)]; + postPlay(move) { + // Apply state diff: + for (const [k, v] of Object.entries(move.statePatch)) + this.states[k] = v; + super.postPlay(move); } }; -- 2.53.0 From 3f3a0703f861fa0c411f7ec38e025f3ea727b719 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 11 May 2026 18:19:38 +0200 Subject: [PATCH 09/16] Fix Avalam, Bario, Balaklava --- css/common.css | 2 +- js/base_rules.js | 20 +++--- variants/Atarigo/class.js | 2 +- variants/Avalam/class.js | 16 ++++- variants/Avalam/style.css | 3 + variants/Avalanche/class.js | 2 - variants/Balaklava/class.js | 15 +---- variants/Bario/class.js | 123 ++++++++++++++++++++++-------------- 8 files changed, 104 insertions(+), 79 deletions(-) diff --git a/css/common.css b/css/common.css index 38d0f95..d9b2990 100644 --- a/css/common.css +++ b/css/common.css @@ -308,7 +308,7 @@ piece.mark.transparent { margin-right: 15px; } .option-input input[type=number] { - width: 64px; + width: 50px; } .btn-wrap { text-align: center; diff --git a/js/base_rules.js b/js/base_rules.js index 347d5b2..bc85aa8 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -261,15 +261,13 @@ export default class ChessRules { // "Parse" FEN: just return untransformed string data parseFen(fen) { - const fenParts = fen.split(" "); - let res = { - position: fenParts[0], - turn: fenParts[1], - movesCount: fenParts[2] + const [position, turn, movesCount, extraData] = fen.split(" "); + return { + position, + turn, + movesCount, + ...(extraData ? JSON.parse(extraData) : {}) }; - if (fenParts.length > 3) - res = Object.assign(res, JSON.parse(fenParts[3])); - return res; } // Return current fen (game state) @@ -913,7 +911,8 @@ export default class ChessRules { r = chessboard.getBoundingClientRect(); pieceWidth = this.getPieceWidth(r.width); const cd = this.idToCoords(e.target.id); - if (cd) { + const [x, y] = Object.values(cd || {x: -1, y: -1}); + if (cd && this.canIplay(x, y)) { const move_s = this.doClick(cd); if (move_s) { if (Array.isArray(move_s)) //8-pieces (at least.. only?) @@ -923,12 +922,11 @@ export default class ChessRules { this.buildMoveStack(move_s, r); } else if (!this.clickOnly) { - const [x, y] = Object.values(cd); if (typeof x != "number") startPiece = this.r_pieces[x][y]; else startPiece = this.g_pieces[x][y]; - if (startPiece && this.canIplay(x, y)) { + if (startPiece) { e.preventDefault(); start = cd; curPiece = startPiece.cloneNode(); diff --git a/variants/Atarigo/class.js b/variants/Atarigo/class.js index 120afdc..e743268 100644 --- a/variants/Atarigo/class.js +++ b/variants/Atarigo/class.js @@ -19,7 +19,7 @@ export default class AtarigoRules extends WeiqiRules { } getCurrentScore(move_s) { - if (move_s[0].vanish.length > 0) + if (move_s.some(mv => mv.vanish.length > 0)) return (this.turn == 'w' ? "0-1" : "1-0"); return "*"; } diff --git a/variants/Avalam/class.js b/variants/Avalam/class.js index 13cc55e..6c61af3 100644 --- a/variants/Avalam/class.js +++ b/variants/Avalam/class.js @@ -69,7 +69,7 @@ export default class AvalamRules extends ChessRules { genRandInitBaseFen() { let fen = ""; - if (this.freefill) + if (this.options.freefill) fen = "9/".repeat(8) + "9"; else if (this.options["randomness"] == 0) { fen = "2Bb5/1BbBb4/1bBbBbB2/1BbBbBbBb/BbBb1bBbB/" + @@ -128,7 +128,7 @@ export default class AvalamRules extends ChessRules { } doClick(coords) { - if (!this.freefill || this.board[coords.x][coords.y] != "") + if (!this.options.freefill || this.board[coords.x][coords.y] != "") return null; return new Move({ start: {x: coords.x, y: coords.y}, @@ -157,8 +157,16 @@ export default class AvalamRules extends ChessRules { getPotentialMovesFrom([x, y]) { const height = this.board[x][y].charCodeAt(1) - 97; - if (height == 5) + if ( + height == 5 + || + ( + this.options.freefill && + this.board.some(row => row.some(sq => sq == "")) + ) + ) { return []; + } let moves = []; for (let s of this.pieces(this.turn, x, y)['b'].both[0].steps) { const [i, j] = [x + s[0], y + s[1]]; @@ -181,6 +189,8 @@ export default class AvalamRules extends ChessRules { let towersCount = {w: 0, b: 0}; for (let i = 0; i < this.size.x; i++) { for (let j = 0; j < this.size.y; j++) { + if (this.board[i][j] == "") + return "*"; //freefill if (this.board[i][j] != "") { if (this.getPotentialMovesFrom([i, j]).length > 0) return '*'; diff --git a/variants/Avalam/style.css b/variants/Avalam/style.css index 04463d1..ab964d3 100644 --- a/variants/Avalam/style.css +++ b/variants/Avalam/style.css @@ -32,4 +32,7 @@ piece.black.stack5 { .board-sq { fill: grey; + stroke: darkblue; + stroke-width: 0.1; + paint-order: stroke fill; } diff --git a/variants/Avalanche/class.js b/variants/Avalanche/class.js index 2016588..28ec007 100644 --- a/variants/Avalanche/class.js +++ b/variants/Avalanche/class.js @@ -21,11 +21,9 @@ export default class AvalancheRules extends ChessRules { "capture", "crazyhouse", "cylinder", - "dark", "madrasi", "recycle", "rifle", - "teleport", "zen" ] }; diff --git a/variants/Balaklava/class.js b/variants/Balaklava/class.js index df441af..c9910d4 100644 --- a/variants/Balaklava/class.js +++ b/variants/Balaklava/class.js @@ -37,18 +37,9 @@ export default class BalaklavaRules extends ChessRules { } genRandInitBaseFen() { - const s = FenUtil.setupPieces( - ['r', 'm', 'b', 'q', 'k', 'b', 'm', 'r'], - { - randomness: this.options["randomness"], - diffCol: ['b'] - } - ); - return { - fen: s.b.join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + - s.w.join("").toUpperCase(), - o: {flags: s.flags} - }; + let res = super.genRandInitBaseFen(); + res.fen = res.fen.replaceAll(/[nN]/g, match => (match == 'n' ? 'm' : 'M')); + return res; } pawnPostProcess(moves) { diff --git a/variants/Bario/class.js b/variants/Bario/class.js index 93573f0..e1fd396 100644 --- a/variants/Bario/class.js +++ b/variants/Bario/class.js @@ -31,7 +31,7 @@ export default class BarioRules extends ChessRules { ); } - get onlyClick() { + get clickOnly() { return this.movesCount <= 1; } @@ -79,8 +79,12 @@ export default class BarioRules extends ChessRules { ); } + static get ReserveArray() { + return ['r', 'n', 'b', 'q']; + } + initReserves(reserveStr) { - super.initReserves(reserveStr, ['r', 'n', 'b', 'q']); + super.initReserves(reserveStr, V.ReserveArray); } setOtherVariables(fenParsed) { @@ -96,7 +100,11 @@ export default class BarioRules extends ChessRules { case 0: return i == this.captureUndef.x && j == this.captureUndef.y; case 1: - return this.getPiece(i, j) == 'u' && c == this.getColor(i, j); + return ( + this.board[i][j] != "" && + this.getPiece(i, j) == 'u' && + c == this.getColor(i, j) + ); } return false; //never reached } @@ -198,6 +206,63 @@ export default class BarioRules extends ChessRules { return false; } + resetReserve(move, computeNext) { + const variety = (c) => { + return ( + [...new Set( + Array.prototype.concat.apply([], + this.board.map(row => + row.filter(cell => + cell.charAt(0) == c && !['p', 'k'].includes(cell.charAt(1)) + ).map(cell => cell.charAt(1)) + ) + ) + )].length >= 2 + ); + }; + let next; + if (computeNext) { + next = {start: move.end, end: move.end, vanish: [], appear: []}; + this.playOnBoard(move); + } + const twoOrMorePieces = {w: variety('w'), b: variety('b')}; + const resetCols = + Object.keys(twoOrMorePieces).filter(k => twoOrMorePieces[k]); + if (resetCols.length >= 1) { + for (let i=0; i= 1) { + next.reset = true; + move.next = next; + } + } + } + + prePlay(move) { + super.prePlay(move); + if (move.reset && move.vanish[0].c != this.playerColor) + this.resetReserve(move); + } + postPlay(move) { const color = this.turn; if (this.movesCount <= 1 || move.reset || move.next) { @@ -239,53 +304,13 @@ export default class BarioRules extends ChessRules { computeNextMove(move) { if ( - !this.definition || this.playerColor != this.turn || - this.board.some(row => row.some(cell => - cell.charAt(0) == this.turn && cell.charAt(1) == 'u')) + this.definition && this.playerColor == this.turn && + this.board.every(row => row.every(cell => { + return ( + cell == "" || cell.charAt(0) != this.turn || cell.charAt(1) != 'u'); + })) ) { - return; - } - const variety = (c) => { - return ( - [...new Set( - Array.prototype.concat.apply([], - this.board.map(row => - row.filter(cell => - cell.charAt(0) == c && !['p', 'k'].includes(cell.charAt(1)) - ).map(cell => cell.charAt(1)) - ) - ) - )].length >= 2 - ); - }; - let next = {start: move.end, end: move.end, vanish: [], appear: []}; - this.playOnBoard(move); - const twoOrMorePieces = {w: variety('w'), b: variety('b')}; - const resetCols = - Object.keys(twoOrMorePieces).filter(k => twoOrMorePieces[k]); - if (resetCols.length >= 1) { - for (let i=0; i= 1) { - next.reset = true; - move.next = next; + this.resetReserve(move, true); } } -- 2.53.0 From 8db0498f51227b137be4a4fc57a9cd11cb0dcebc Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 12 May 2026 11:44:31 +0200 Subject: [PATCH 10/16] update --- TODO | 2 ++ js/base_rules.js | 28 ++++++++++++++++++++++------ variants/Bario/class.js | 8 +++++--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/TODO b/TODO index 7114f80..2020aca 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ +Issue with Dark Bario + Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number ==> plus simple : deux classes, images superposées (?) diff --git a/js/base_rules.js b/js/base_rules.js index bc85aa8..1039596 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -1088,6 +1088,7 @@ export default class ChessRules { // DARK METHODS updateEnlightened() { + console.log("new light"); this.oldEnlightened = this.enlightened; this.enlightened = ArrayFun.init(this.size.x, this.size.y, false); // Add pieces positions + all squares reachable by moves (includes Zen): @@ -1097,6 +1098,9 @@ export default class ChessRules { { this.enlightened[x][y] = true; this.getPotentialMovesFrom([x, y]).forEach(m => { + +console.log(m.end); + this.enlightened[m.end.x][m.end.y] = true; }); } @@ -2310,9 +2314,7 @@ export default class ChessRules { }); } - prePlay(move) { - if (this.hasCastle) - this.updateCastleFlags(move); + tryPrePlayCrazyhouse(move) { if (this.options["crazyhouse"]) { if (move.appear.length > 0 && move.vanish.length > 0) { // Assumption: something is moving @@ -2338,6 +2340,9 @@ export default class ChessRules { delete this.ispawn[square]; }); } + } + + tryPrePlayAtomicReserve(move) { const minSize = Math.min(move.appear.length, move.vanish.length); if ( this.hasReserve && @@ -2365,6 +2370,13 @@ export default class ChessRules { } } + prePlay(move) { + if (this.hasCastle) + this.updateCastleFlags(move); + this.tryPrePlayCrazyhouse(move); + this.tryPrePlayAtomicReserve(move); + } + play(move) { this.prePlay(move); if (this.hasEnpassant) @@ -2373,9 +2385,7 @@ export default class ChessRules { this.postPlay(move); } - postPlay(move) { - if (this.options["dark"]) - this.updateEnlightened(); + tryPostPlayTeleport(move) { if (this.options["teleport"]) { if ( this.subTurnTeleport == 1 && @@ -2391,6 +2401,10 @@ export default class ChessRules { this.subTurnTeleport = 1; this.captured = null; } + } + + postPlay(move) { + this.tryPostPlayTeleport(move); this.tryChangeTurn(move); } @@ -2399,6 +2413,8 @@ export default class ChessRules { this.turn = C.GetOppTurn(this.turn); this.movesCount++; this.subTurn = 1; + if (this.options["dark"]) + this.updateEnlightened(); } else if (!move.next) this.subTurn++; diff --git a/variants/Bario/class.js b/variants/Bario/class.js index e1fd396..e66f356 100644 --- a/variants/Bario/class.js +++ b/variants/Bario/class.js @@ -10,7 +10,7 @@ export default class BarioRules extends ChessRules { input: C.Options.input, styles: [ "atomic", "cannibal", "capture", "cylinder", - "dark", "madrasi", "rifle", "teleport" + "dark", "madrasi", "rifle", "teleport", "zen" ] }; } @@ -279,7 +279,7 @@ export default class BarioRules extends ChessRules { ); if (typeof move.start.x == "number" && !captureUndef) // Normal move (including Teleport) - super.postPlay(move); + super.tryPostPlayTeleport(move); else if (typeof move.start.x == "string") { super.updateReserve( color, move.start.y, this.reserve[color][move.start.y] - 1); @@ -293,13 +293,15 @@ export default class BarioRules extends ChessRules { this.tryChangeTurn(null, captureUndef); } } - +//TODO // NOTE: not "trying", the turn always change here (TODO?) tryChangeTurn(move, captureUndef) { this.definition = null; this.subTurn = captureUndef ? 0 : 1; this.turn = C.GetOppTurn(this.turn); this.movesCount++; + if (this.options["dark"] && this.movesCount >= 2) + this.updateEnlightened(); } computeNextMove(move) { -- 2.53.0 From eb85492e272f03a904fb7725d1a8a1d08a722565 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 13 May 2026 14:43:30 +0200 Subject: [PATCH 11/16] Fix Bario + bundle.py, start Emergo --- bundle.py | 4 +- initialize.sh | 4 +- js/base_rules.js | 4 - package-lock.json | 1 - package.json | 2 + variants/Bario/class.js | 45 ++--- variants/Emergo/class.js | 363 ++++++++++++++------------------------ variants/Emergo/style.css | 35 ++++ 8 files changed, 193 insertions(+), 265 deletions(-) create mode 100644 variants/Emergo/style.css diff --git a/bundle.py b/bundle.py index 0b3b451..8876a05 100755 --- a/bundle.py +++ b/bundle.py @@ -17,8 +17,8 @@ IGNORE_FILE_UPDATE = {"app.js"} # Files and folders to totally ignore IGNORE_FILES = { "LICENSE", "README.md", "TODO", "bundle.py", "js/parameters.js.dist", - "initialize.sh", "package-lock.json", "package.json", "js/server.js", ".gitignore", - "nginx_config.example", "start.sh", "stop.sh", "assets.zip", "extras.zip", ".pid" + "initialize.sh", "package-lock.json", "package.json", "js/server.js", + "nginx_config.example", "start.sh", "stop.sh", ".pid", ".gitignore" } IGNORE_DIRS = {".git", "node_modules", DEST_DIR} diff --git a/initialize.sh b/initialize.sh index bf8d705..9e187d0 100755 --- a/initialize.sh +++ b/initialize.sh @@ -1,8 +1,8 @@ -!#/bin/sh +#!/bin/sh wget https://xogo.casa/assets.zip && unzip assets.zip wget https://xogo.casa/extras.zip && unzip extras.zip -cp parameters.js.dist parameters.js +cp js/parameters.js.dist js/parameters.js npm i cd pieces/Avalam && python generateSVG.py && cd ../.. #cd pieces/Emergo && python generateSVG.py && cd ../.. diff --git a/js/base_rules.js b/js/base_rules.js index 1039596..894e8aa 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -1088,7 +1088,6 @@ export default class ChessRules { // DARK METHODS updateEnlightened() { - console.log("new light"); this.oldEnlightened = this.enlightened; this.enlightened = ArrayFun.init(this.size.x, this.size.y, false); // Add pieces positions + all squares reachable by moves (includes Zen): @@ -1098,9 +1097,6 @@ export default class ChessRules { { this.enlightened[x][y] = true; this.getPotentialMovesFrom([x, y]).forEach(m => { - -console.log(m.end); - this.enlightened[m.end.x][m.end.y] = true; }); } diff --git a/package-lock.json b/package-lock.json index b657351..dbc6f9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "xogo", "dependencies": { "ws": "^7.5.3" }, diff --git a/package.json b/package.json index acf1faa..eadd717 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,6 @@ { + "name": "xogo", + "version": "0.1.0", "main": "js/server.js", "dependencies": { "ws": "^7.5.3" diff --git a/variants/Bario/class.js b/variants/Bario/class.js index e66f356..0470f4a 100644 --- a/variants/Bario/class.js +++ b/variants/Bario/class.js @@ -103,7 +103,7 @@ export default class BarioRules extends ChessRules { return ( this.board[i][j] != "" && this.getPiece(i, j) == 'u' && - c == this.getColor(i, j) + this.getColor(i, j) == c ); } return false; //never reached @@ -267,39 +267,40 @@ export default class BarioRules extends ChessRules { const color = this.turn; if (this.movesCount <= 1 || move.reset || move.next) { if (!move.next) - this.tryChangeTurn(); + this.tryChangeTurn(move); return; } if (this.subTurn == 0) this.captureUndef = null; //already used - const captureUndef = ( - move.vanish.length == 2 && //exclude subTurn == 0 - move.vanish[1].c != color && - move.vanish[1].p == 'u' - ); - if (typeof move.start.x == "number" && !captureUndef) - // Normal move (including Teleport) - super.tryPostPlayTeleport(move); - else if (typeof move.start.x == "string") { + if (typeof move.start.x == "string") { super.updateReserve( color, move.start.y, this.reserve[color][move.start.y] - 1); if (move.vanish.length == 1 && move.vanish[0].p == 'u') this.definition = move.end; - this.subTurn++; } else { - this.subTurn = 0; - this.captureUndef = move.end; - this.tryChangeTurn(null, captureUndef); + if ( + move.vanish.length == 2 && //exclude subTurn == 0 + move.vanish[1].c != color && + move.vanish[1].p == 'u' + ) { + this.captureUndef = move.end; + } + else + super.tryPostPlayTeleport(move); } + this.tryChangeTurn(move); } -//TODO - // NOTE: not "trying", the turn always change here (TODO?) - tryChangeTurn(move, captureUndef) { - this.definition = null; - this.subTurn = captureUndef ? 0 : 1; - this.turn = C.GetOppTurn(this.turn); - this.movesCount++; + + tryChangeTurn(move) { + if (typeof move.start.x == "string") + this.subTurn++; //0 to 1, or 1 to 2 + else { + this.subTurn = !!this.captureUndef ? 0 : 1; + this.definition = null; + this.turn = C.GetOppTurn(this.turn); + this.movesCount++; + } if (this.options["dark"] && this.movesCount >= 2) this.updateEnlightened(); } diff --git a/variants/Emergo/class.js b/variants/Emergo/class.js index 06e73b1..2635e63 100644 --- a/variants/Emergo/class.js +++ b/variants/Emergo/class.js @@ -1,8 +1,9 @@ -import { ChessRules, Move, PiPo } from "@/js/base_rules"; -import { randInt } from "@/utils/alea"; +import { ChessRules } from "@/js/base_rules"; +import PiPo from "/utils/PiPo.js"; +import Move from "/utils/Move.js"; import { ArrayFun } from "@/utils/array"; -export class EmergoRules extends ChessRules { +export default class EmergoRules extends ChessRules { // Simple encoding: A to L = 1 to 12, from left to right, if white controls. // Lowercase if black controls. @@ -12,152 +13,86 @@ export class EmergoRules extends ChessRules { return null; } - static get HasFlags() { + get hasFlags() { return false; } - - static get HasEnpassant() { + get hasEnpassant() { return false; } - static get DarkBottomRight() { - return true; - } - - // board element == file name: - static board2fen(b) { + // board element == piece class ref: + board2fen(b) { return b; } - static fen2board(f) { + fen2board(f) { return f; } - static IsGoodPosition(position) { - if (position.length == 0) return false; - const rows = position.split("/"); - if (rows.length != V.size.x) return false; - for (let row of rows) { - let sumElts = 0; - for (let i = 0; i < row.length; i++) { - // Add only 0.5 per symbol because 2 per piece - if (row[i].toLowerCase().match(/^[a-lA-L@]$/)) sumElts += 0.5; - else { - const num = parseInt(row[i], 10); - if (isNaN(num) || num <= 0) return false; - sumElts += num; - } - } - if (sumElts != V.size.y) return false; - } - return true; - } - - static GetBoard(position) { + getBoard(position) { const rows = position.split("/"); - let board = ArrayFun.init(V.size.x, V.size.y, ""); + let board = ArrayFun.init(this.size.x, this.size.y, ""); for (let i = 0; i < rows.length; i++) { let j = 0; for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) { const character = rows[i][indexInRow]; const num = parseInt(character, 10); - // If num is a number, just shift j: - if (!isNaN(num)) j += num; + if (!isNaN(num)) + // If num is a number, just shift j: + j += num; else // Something at position i,j - board[i][j++] = V.fen2board(character + rows[i][++indexInRow]); + board[i][j++] = this.fen2board(character + rows[i][++indexInRow]); } } return board; } - getPpath(b) { - return "Emergo/" + b; - } - getColor(x, y) { - if (x >= V.size.x) return x == V.size.x ? "w" : "b"; - if (this.board[x][y].charCodeAt(0) < 97) return 'w'; + if (x >= this.size.x) + return x == this.size.x ? "w" : "b"; + if (this.board[x][y].charCodeAt(0) < 97) + return 'w'; return 'b'; } - getPiece() { - return V.PAWN; //unused - } - - static IsGoodFen(fen) { - if (!ChessRules.IsGoodFen(fen)) return false; - const fenParsed = V.ParseFen(fen); - // 3) Check reserves - if ( - !fenParsed.reserve || - !fenParsed.reserve.match(/^([0-9]{1,2},?){2,2}$/) - ) { - return false; - } - return true; - } - - static ParseFen(fen) { - const fenParts = fen.split(" "); - return Object.assign( - ChessRules.ParseFen(fen), - { reserve: fenParts[3] } - ); + getPiece(x, y) { + return this.board[x][y]; } - static get size() { + get size() { return { x: 9, y: 9 }; } - static GenRandInitFen() { - return "9/9/9/9/9/9/9/9/9 w 0 12,12"; - } - - getFen() { - return super.getFen() + " " + this.getReserveFen(); + genRandInitBaseFen() { + return { fen: "9/9/9/9/9/9/9/9/9 w 0 12,12", o: {} }; } - getFenForRepeat() { - return super.getFenForRepeat() + "_" + this.getReserveFen(); - } + static get ReserveArray() { + // Piece type doesn't matter + return ['@']; //TODO :: - getReserveFen() { - return ( - (!this.reserve["w"] ? 0 : this.reserve["w"][V.PAWN]) + "," + - (!this.reserve["b"] ? 0 : this.reserve["b"][V.PAWN]) - ); - } - getReservePpath(index, color) { - return "Emergo/" + (color == 'w' ? 'A' : 'a') + '@'; - } - static get RESERVE_PIECES() { - return [V.PAWN]; //only array length matters } - setOtherVariables(fen) { - const reserve = - V.ParseFen(fen).reserve.split(",").map(x => parseInt(x, 10)); - this.reserve = { w: null, b: null }; - if (reserve[0] > 0) this.reserve['w'] = { [V.PAWN]: reserve[0] }; - if (reserve[1] > 0) this.reserve['b'] = { [V.PAWN]: reserve[1] }; + setOtherVariables(fenParsed) { + super.setOtherVariables(fenParsed); // Local stack of captures during a turn (squares + directions) this.captures = [ [] ]; } atLeastOneCaptureFrom([x, y], color, forbiddenStep) { - for (let s of V.steps[V.BISHOP]) { + for (let s of super.pieces()['b'].both[0].steps) { if ( !forbiddenStep || (s[0] != -forbiddenStep[0] || s[1] != -forbiddenStep[1]) ) { const [i, j] = [x + s[0], y + s[1]]; if ( - V.OnBoard(i + s[0], j + s[1]) && - this.board[i][j] != V.EMPTY && + this.onBoard(i + s[0], j + s[1]) && + this.board[i][j] != "" && this.getColor(i, j) != color && - this.board[i + s[0]][j + s[1]] == V.EMPTY + this.board[i + s[0]][j + s[1]] == "" ) { return true; } @@ -176,10 +111,10 @@ export class EmergoRules extends ChessRules { captures[L-1].square, color, captures[L-1].step) ); } - 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.board[i][j] != "" && this.getColor(i, j) == color && this.atLeastOneCaptureFrom([i, j], color) ) { @@ -198,7 +133,8 @@ export class EmergoRules extends ChessRules { res = [i]; maxLength = caps[i].length; } - else if (caps[i].length == maxLength) res.push(i); + else if (caps[i].length == maxLength) + res.push(i); } return res; }; @@ -207,26 +143,28 @@ export class EmergoRules extends ChessRules { let res = []; const L = locSteps.length; const lastStep = (L > 0 ? locSteps[L-1] : null); - for (let s of V.steps[V.BISHOP]) { - if (!!lastStep && s[0] == -lastStep[0] && s[1] == -lastStep[1]) continue; + for (let s of super.pieces()['b'].both[0].steps) { + if (!!lastStep && s[0] == -lastStep[0] && s[1] == -lastStep[1]) + continue; const [i, j] = [x + s[0], y + s[1]]; if ( - V.OnBoard(i + s[0], j + s[1]) && - this.board[i + s[0]][j + s[1]] == V.EMPTY && - this.board[i][j] != V.EMPTY && + this.onBoard(i + s[0], j + s[1]) && + this.board[i + s[0]][j + s[1]] == "" && + this.board[i][j] != "" && this.getColor(i, j) != color ) { const move = this.getBasicMove([x, y], [i + s[0], j + s[1]], [i, j]); locSteps.push(s); - V.PlayOnBoard(this.board, move); + this.playOnBoard(move); const nextRes = this.getLongestCaptures_aux([i + s[0], j + s[1]], color, locSteps); res.push(1 + nextRes); locSteps.pop(); - V.UndoOnBoard(this.board, move); + this.undoOnBoard(move); } } - if (res.length == 0) return 0; + if (res.length == 0) + return 0; return Math.max(...res); } @@ -234,23 +172,24 @@ export class EmergoRules extends ChessRules { let res = []; const L = locSteps.length; const lastStep = (L > 0 ? locSteps[L-1] : null); - for (let s of V.steps[V.BISHOP]) { - if (!!lastStep && s[0] == -lastStep[0] && s[1] == -lastStep[1]) continue; + for (let s of super.pieces()['b'].both[0].steps) { + if (!!lastStep && s[0] == -lastStep[0] && s[1] == -lastStep[1]) + continue; const [i, j] = [x + s[0], y + s[1]]; if ( - V.OnBoard(i + s[0], j + s[1]) && - this.board[i + s[0]][j + s[1]] == V.EMPTY && - this.board[i][j] != V.EMPTY && + this.onBoard(i + s[0], j + s[1]) && + this.board[i + s[0]][j + s[1]] == "" && + this.board[i][j] != "" && this.getColor(i, j) != color ) { const move = this.getBasicMove([x, y], [i + s[0], j + s[1]], [i, j]); locSteps.push(s); - V.PlayOnBoard(this.board, move); + this.playOnBoard(move); const stepRes = this.getLongestCaptures_aux([i + s[0], j + s[1]], color, locSteps); res.push({ step: s, length: 1 + stepRes }); locSteps.pop(); - V.UndoOnBoard(this.board, move); + this.undoOnBoard(move); } } return this.maxLengthIndices(res).map(i => res[i]);; @@ -271,10 +210,10 @@ export class EmergoRules extends ChessRules { ); } else { - 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.board[i][j] != "" && this.getColor(i, j) == color ) { let locSteps = []; @@ -303,9 +242,11 @@ export class EmergoRules extends ChessRules { const firstCodes = (color == 'w' ? [65, 97] : [97, 65]); const cpCapt = this.board[capt[0]][capt[1]]; let count1 = [cp1.charCodeAt(0) - firstCodes[0], -1]; - if (cp1[1] != '@') count1[1] = cp1.charCodeAt(1) - firstCodes[0]; + if (cp1[1] != '@') + count1[1] = cp1.charCodeAt(1) - firstCodes[0]; let countC = [cpCapt.charCodeAt(0) - firstCodes[1], -1]; - if (cpCapt[1] != '@') countC[1] = cpCapt.charCodeAt(1) - firstCodes[1]; + if (cpCapt[1] != '@') + countC[1] = cpCapt.charCodeAt(1) - firstCodes[1]; count1[1]++; countC[0]--; let colorChange = false, @@ -315,7 +256,8 @@ export class EmergoRules extends ChessRules { colorChange = true; countC = [countC[1], -1]; } - else captVanish = true; + else + captVanish = true; } const incPrisoners = String.fromCharCode(firstCodes[0] + count1[1]); let mv = new Move({ @@ -346,13 +288,24 @@ export class EmergoRules extends ChessRules { return mv; } - getReserveMoves(x) { - const color = this.turn; - if (!this.reserve[color] || this.atLeastOneCapture(color)) return []; + +// TODO: + style + getSquareColorClass(x, y) { + return ((x+y) % 2 == 0 ? "dark-square": "light-square"); + } + + + + + getDropMovesFrom([c, p]) { + const color = c; + if (!this.reserve[color] || this.atLeastOneCapture(color)) + return []; let moves = []; + const oppCol = C.GetOppTurn(color); const shadowPiece = - this.reserve[V.GetOppCol(color)] == null - ? this.reserve[color][V.PAWN] - 1 + !this.reserve[oppCol] + ? this.reserve[color]['p'] - 1 : 0; const appearColor = String.fromCharCode( (color == 'w' ? 'A' : 'a').charCodeAt(0) + shadowPiece); @@ -361,35 +314,36 @@ export class EmergoRules extends ChessRules { new Move({ appear: [ new PiPo({ x: i, y: j, c: appearColor, p: '@' }) ], vanish: [], - start: { x: V.size.x + (color == 'w' ? 0 : 1), y: 0 } + start: { x: this.size.x + (color == 'w' ? 0 : 1), y: 0 } }) ); }; - const oppCol = V.GetOppCol(color); const opponentCanCapture = this.atLeastOneCapture(oppCol); - for (let i = 0; i < V.size.x; i++) { - for (let j = i % 2; j < V.size.y; j += 2) { + for (let i = 0; i < this.size.x; i++) { + for (let j = i % 2; j < this.size.y; j += 2) { if ( - this.board[i][j] == V.EMPTY && + this.board[i][j] == "" && // prevent playing on central square at move 1: (this.movesCount >= 1 || i != 4 || j != 4) ) { - if (opponentCanCapture) addMove([i, j]); + if (opponentCanCapture) + addMove([i, j]); else { let canAddMove = true; - for (let s of V.steps[V.BISHOP]) { + for (let s of super.pieces()['b'].both[0].steps) { if ( - V.OnBoard(i + s[0], j + s[1]) && - V.OnBoard(i - s[0], j - s[1]) && - this.board[i + s[0]][j + s[1]] != V.EMPTY && - this.board[i - s[0]][j - s[1]] == V.EMPTY && + this.onBoard(i + s[0], j + s[1]) && + this.onBoard(i - s[0], j - s[1]) && + this.board[i + s[0]][j + s[1]] != "" && + this.board[i - s[0]][j - s[1]] == "" && this.getColor(i + s[0], j + s[1]) == oppCol ) { canAddMove = false; break; } } - if (canAddMove) addMove([i, j]); + if (canAddMove) + addMove([i, j]); } } } @@ -397,13 +351,15 @@ export class EmergoRules extends ChessRules { return moves; } - getPotentialMovesFrom([x, y], longestCaptures) { - if (x >= V.size.x) { - if (longestCaptures.length == 0) return this.getReserveMoves(x); + getPossibleMovesFrom([x, y], longestCaptures) { + if (x >= this.size.x) { + if (longestCaptures.length == 0) + return this.getReserveMoves(x); return []; } const color = this.turn; - if (!!this.reserve[color] && !this.atLeastOneCapture(color)) return []; + if (!!this.reserve[color] && !this.atLeastOneCapture(color)) + return []; const L0 = this.captures.length; const captures = this.captures[L0 - 1]; const L = captures.length; @@ -427,46 +383,21 @@ export class EmergoRules extends ChessRules { // Just search simple moves: for (let s of V.steps[V.BISHOP]) { const [i, j] = [x + s[0], y + s[1]]; - if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) + if (this.onBoard(i, j) && this.board[i][j] == "") moves.push(this.getBasicMove([x, y], [i, j])); } return moves; } - getAllValidMoves() { - const color = this.turn; - const longestCaptures = this.getAllLongestCaptures(color); - let potentialMoves = []; - for (let i = 0; i < V.size.x; i++) { - for (let j = 0; j < V.size.y; j++) { - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { - Array.prototype.push.apply( - potentialMoves, - this.getPotentialMovesFrom([i, j], longestCaptures) - ); - } - } - } - // Add reserve moves - potentialMoves = potentialMoves.concat( - this.getReserveMoves(V.size.x + (color == "w" ? 0 : 1)) - ); - return potentialMoves; - } - - getPossibleMovesFrom([x, y]) { + getPotentialMovesFrom([x, y]) { const longestCaptures = this.getAllLongestCaptures(this.getColor(x, y)); - return this.getPotentialMovesFrom([x, y], longestCaptures); + return this.getPossibleMovesFrom([x, y], longestCaptures); } filterValid(moves) { return moves; } - getCheckSquares() { - return []; - } - play(move) { const color = this.turn; move.turn = color; //for undo @@ -496,81 +427,45 @@ export class EmergoRules extends ChessRules { } } - undo(move) { - V.UndoOnBoard(this.board, move); - if (!move.notTheEnd) { - this.turn = move.turn; - this.movesCount--; - this.captures.pop(); - } - if (move.vanish.length == 0) { - const color = (move.appear[0].c == 'A' ? 'w' : 'b'); - const firstCode = (color == 'w' ? 65 : 97); - const reserveCount = move.appear[0].c.charCodeAt() - firstCode + 1; - if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 0 }; - this.reserve[color][V.PAWN] += reserveCount; - } - else if (move.vanish.length == 2) { - const L0 = this.captures.length; - let captures = this.captures[L0 - 1]; - captures.pop(); - } - } - atLeastOneMove() { const color = this.turn; - if (this.atLeastOneCapture(color)) return true; - for (let i = 0; i < V.size.x; i++) { - for (let j = 0; j < V.size.y; j++) { - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { - const moves = this.getPotentialMovesFrom([i, j], []); - if (moves.length > 0) return true; + if (this.atLeastOneCapture(color)) + return 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) == color) { + const moves = this.getPossibleMovesFrom([i, j], []); + if (moves.length > 0) + return true; } } } + + + // TODO: adapt const reserveMoves = - this.getReserveMoves(V.size.x + (this.turn == "w" ? 0 : 1)); + this.getReserveMoves(this.size.x + (this.turn == "w" ? 0 : 1)); return (reserveMoves.length > 0); } + + getCurrentScore() { const color = this.turn; + const testColorCode = (c) => { + return (c <= 90 && color == 'w') || (c >= 97 && color == 'b'); + }; // If no pieces on board + reserve, I lose - if (!!this.reserve[color]) return "*"; - let atLeastOnePiece = false; - outerLoop: for (let i=0; i < V.size.x; i++) { - for (let j=0; j < V.size.y; j++) { - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { - atLeastOnePiece = true; - break outerLoop; - } - } - } - if (!atLeastOnePiece) return (color == 'w' ? "0-1" : "1-0"); - if (!this.atLeastOneMove()) return "1/2"; + if (!!this.reserve[color]) + return "*"; + const atLeastOnePiece = this.board.some(row => row.some(cell => { + return cell != "" && testColorCode(cell.charCodeAt(0)); + }); + if (!atLeastOnePiece) + return (color == 'w' ? "0-1" : "1-0"); + if (!this.atLeastOneMove()) + return "1/2"; return "*"; } - getComputerMove() { - // Random mover for now (TODO) - const color = this.turn; - let mvArray = []; - let mv = null; - while (this.turn == color) { - const moves = this.getAllValidMoves(); - mv = moves[randInt(moves.length)]; - mvArray.push(mv); - this.play(mv); - } - for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]); - return (mvArray.length > 1 ? mvArray : mvArray[0]); - } - - getNotation(move) { - if (move.vanish.length == 0) return "@" + V.CoordsToSquare(move.end); - const L0 = this.captures.length; - if (this.captures[L0 - 1].length > 0) return V.CoordsToSquare(move.end); - return V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end); - } - }; diff --git a/variants/Emergo/style.css b/variants/Emergo/style.css new file mode 100644 index 0000000..9650899 --- /dev/null +++ b/variants/Emergo/style.css @@ -0,0 +1,35 @@ +piece.white.stack { + background-image: url('/pieces/Emergo/white_.svg'); +} + + + + +piece.white.stack2 { + background-image: url('/pieces/Avalam/white_stack2.svg'); +} +piece.white.stack3 { + background-image: url('/pieces/Avalam/white_stack3.svg'); +} +piece.white.stack4 { + background-image: url('/pieces/Avalam/white_stack4.svg'); +} +piece.white.stack5 { + background-image: url('/pieces/Avalam/white_stack5.svg'); +} + +piece.black.stack { + background-image: url('/pieces/Avalam/black_stack.svg'); +} +piece.black.stack2 { + background-image: url('/pieces/Avalam/black_stack2.svg'); +} +piece.black.stack3 { + background-image: url('/pieces/Avalam/black_stack3.svg'); +} +piece.black.stack4 { + background-image: url('/pieces/Avalam/black_stack4.svg'); +} +piece.black.stack5 { + background-image: url('/pieces/Avalam/black_stack5.svg'); +} -- 2.53.0 From ab84e7d192fb684316dff689e9ce75a937143dd4 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 13 May 2026 14:49:35 +0200 Subject: [PATCH 12/16] update --- initialize.sh | 2 +- pieces/Emergo/generateSVG_composite.py | 98 ++++++++++++++++ pieces/Emergo/generateSVG_simple.py | 150 +++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100755 pieces/Emergo/generateSVG_composite.py create mode 100755 pieces/Emergo/generateSVG_simple.py diff --git a/initialize.sh b/initialize.sh index 9e187d0..a7fd40d 100755 --- a/initialize.sh +++ b/initialize.sh @@ -5,4 +5,4 @@ wget https://xogo.casa/extras.zip && unzip extras.zip cp js/parameters.js.dist js/parameters.js npm i cd pieces/Avalam && python generateSVG.py && cd ../.. -#cd pieces/Emergo && python generateSVG.py && cd ../.. +cd pieces/Emergo && python generateSVG.py && cd ../.. diff --git a/pieces/Emergo/generateSVG_composite.py b/pieces/Emergo/generateSVG_composite.py new file mode 100755 index 0000000..85726d5 --- /dev/null +++ b/pieces/Emergo/generateSVG_composite.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +# Compose each piece SVG with numbers +# https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 +# https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths + +preamble = """ + + + + + + + +""" + +black_top = '' +white_bottom = '' +white_top = '' +black_bottom = '' + +digits = { + "top": [ + # 1 (unused here) + '= 1: + f.write(digits["top"][top] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "white" else 'stroke="orange"') + '/>') + f.write("\n") + if bottom >= 1: + f.write(digits["bottom"][bottom] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "black" else 'stroke="orange"') + '/>') + f.write("\n") + f.write(final) + f.close() diff --git a/pieces/Emergo/generateSVG_simple.py b/pieces/Emergo/generateSVG_simple.py new file mode 100755 index 0000000..51f734e --- /dev/null +++ b/pieces/Emergo/generateSVG_simple.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +# Compose each piece SVG with numbers +# https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 +# https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths + +preamble = """ + +""" + +black = '' +white = '' + +digits = [ + # 1 (unused) + '= 1: + f.write(digits[number] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if color == "white" else 'stroke="orange"') + '/>') + f.write("\n") + f.write(final) + f.close() + + +preamble = """ + + + + + + + +""" + +black_top = '' +white_bottom = '' +white_top = '' +black_bottom = '' + +digits = { + "top": [ + # 1 (unused here) + '= 1: + f.write(digits["top"][top] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "white" else 'stroke="orange"') + '/>') + f.write("\n") + if bottom >= 1: + f.write(digits["bottom"][bottom] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "black" else 'stroke="orange"') + '/>') + f.write("\n") + f.write(final) + f.close() -- 2.53.0 From ac6a5734903ffeec0740b6e2ab38717b5d62c098 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 13 May 2026 16:13:06 +0200 Subject: [PATCH 13/16] Ignore generated SVG. Attempt refactoring pieces() --> pieceDef() --- TODO | 2 - js/base_rules.js | 88 +++++++++-------- pieces/Avalam/.gitignore | 1 + pieces/Avalam/black_stack.svg | 5 - pieces/Avalam/black_stack2.svg | 6 -- pieces/Avalam/black_stack3.svg | 6 -- pieces/Avalam/black_stack4.svg | 6 -- pieces/Avalam/black_stack5.svg | 6 -- pieces/Avalam/white_stack.svg | 5 - pieces/Avalam/white_stack2.svg | 6 -- pieces/Avalam/white_stack3.svg | 6 -- pieces/Avalam/white_stack4.svg | 6 -- pieces/Avalam/white_stack5.svg | 6 -- pieces/Emergo/.gitignore | 1 + .../{generateSVG_simple.py => generateSVG.py} | 15 ++- pieces/Emergo/generateSVG_composite.py | 98 ------------------- variants/Emergo/class.js | 67 ++++++------- 17 files changed, 92 insertions(+), 238 deletions(-) create mode 100644 pieces/Avalam/.gitignore delete mode 100644 pieces/Avalam/black_stack.svg delete mode 100644 pieces/Avalam/black_stack2.svg delete mode 100644 pieces/Avalam/black_stack3.svg delete mode 100644 pieces/Avalam/black_stack4.svg delete mode 100644 pieces/Avalam/black_stack5.svg delete mode 100644 pieces/Avalam/white_stack.svg delete mode 100644 pieces/Avalam/white_stack2.svg delete mode 100644 pieces/Avalam/white_stack3.svg delete mode 100644 pieces/Avalam/white_stack4.svg delete mode 100644 pieces/Avalam/white_stack5.svg create mode 100644 pieces/Emergo/.gitignore rename pieces/Emergo/{generateSVG_simple.py => generateSVG.py} (92%) delete mode 100755 pieces/Emergo/generateSVG_composite.py diff --git a/TODO b/TODO index 2020aca..7114f80 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -Issue with Dark Bario - Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number ==> plus simple : deux classes, images superposées (?) diff --git a/js/base_rules.js b/js/base_rules.js index 894e8aa..1a7543c 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -352,7 +352,8 @@ export default class ChessRules { if (o.init) return Array(2 * V.ReserveArray.length).fill('0').join(""); return ( - ['w', 'b'].map(c => Object.values(this.reserve[c]).join("")).join("") + ['w', 'b'].map(c => Object.values(this.reserve[c]).map( + v => v.toString(36)).join("")).join("") ); } @@ -497,7 +498,7 @@ export default class ChessRules { } getRankInReserve(c, p) { - const pieces = Object.keys(this.pieces(c, c, p)); + const pieces = Object.keys(V.ReserveArray); const lastIndex = pieces.findIndex(pp => pp == p) let toTest = pieces.slice(0, lastIndex); return toTest.reduce( @@ -674,7 +675,8 @@ export default class ChessRules { if (this.board[i][j] != "") { const color = this.getColor(i, j); const piece = this.getPiece(i, j); - addPiece(i, j, "g_pieces", this.pieces(color, i, j)[piece]["class"]); + addPiece(i, j, "g_pieces", + this.pieceDef(piece, color, i, j)["class"]); this.g_pieces[i][j].classList.add(V.GetColorClass(color)); if (this.enlightened && !this.enlightened[i][j]) this.g_pieces[i][j].classList.add("hidden"); @@ -741,7 +743,7 @@ export default class ChessRules { r_cell.style.height = sqResSize + "px"; rcontainer.appendChild(r_cell); let piece = document.createElement("piece"); - C.AddClass_es(piece, this.pieces(c, c, p)[p]["class"]); + C.AddClass_es(piece, this.pieceDef(p, c, c, p)["class"]); piece.classList.add(V.GetColorClass(c)); piece.style.width = "100%"; piece.style.height = "100%"; @@ -1060,7 +1062,7 @@ export default class ChessRules { const piece = document.createElement("piece"); const cdisp = moves[i].choice || moves[i].appear[0].p; C.AddClass_es(piece, - this.pieces(color, moves[i].end.x, moves[i].end.y)[cdisp]["class"]); + this.pieceDef(cdisp, color, moves[i].end.x, moves[i].end.y)["class"]); piece.classList.add(V.GetColorClass(color)); piece.style.width = "100%"; piece.style.height = "100%"; @@ -1110,7 +1112,7 @@ export default class ChessRules { enlightEnpassant() { // NOTE: shortcut, pawn has only one attack type, doesn't depend on square // TODO: (0, 0) is wrong, would need to place an attacker here... - const steps = this.pieces(this.playerColor, 0, 0)["p"].attack[0].steps; + const steps = this.pieceDef('p', this.playerColor, 0, 0).attack[0].steps; for (let step of steps) { const x = this.epSquare.x - step[0], //NOTE: epSquare.x not on edge y = this.getY(this.epSquare.y - step[1]); @@ -1186,7 +1188,7 @@ export default class ChessRules { getPieceType(x, y, p) { if (!p) p = this.getPiece(x, y); - return this.pieces(this.getColor(x, y), x, y)[p].moveas || p; + return this.pieceDef(p, this.getColor(x, y), x, y).moveas || p; } isKing(x, y, p) { @@ -1229,10 +1231,11 @@ export default class ChessRules { return (color == 'w' && x >= 6) || (color == 'b' && x <= 1); } - pieces(color, x, y) { - const pawnShift = this.getPawnShift(color || 'w'); - return { - 'p': { + pieceDef(piece, color, x, y) { + switch (piece) { + case 'p': { + const pawnShift = this.getPawnShift(color || 'w'); + return { "class": "pawn", moves: [ { @@ -1246,14 +1249,17 @@ export default class ChessRules { range: 1 } ] - }, - 'r': { + }; + } + case 'r': + return { "class": "rook", both: [ {steps: [[0, 1], [0, -1], [1, 0], [-1, 0]]} ] - }, - 'n': { + }; + case 'n': + return { "class": "knight", both: [ { @@ -1264,14 +1270,16 @@ export default class ChessRules { range: 1 } ] - }, - 'b': { + }; + case 'b': + return { "class": "bishop", both: [ {steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]]} ] - }, - 'q': { + }; + case 'q': + return { "class": "queen", both: [ { @@ -1281,8 +1289,9 @@ export default class ChessRules { ] } ] - }, - 'k': { + }; + case 'k': + return { "class": "king", both: [ { @@ -1293,14 +1302,14 @@ export default class ChessRules { range: 1 } ] - }, - // Cannibal kings: - '!': {"class": "king-pawn", moveas: "p"}, - '#': {"class": "king-rook", moveas: "r"}, - '$': {"class": "king-knight", moveas: "n"}, - '%': {"class": "king-bishop", moveas: "b"}, - '*': {"class": "king-queen", moveas: "q"} - }; + }; + // Cannibal kings: + case '!': return {"class": "king-pawn", moveas: "p"}; + case '#': return {"class": "king-rook", moveas: "r"}; + case '$': return {"class": "king-knight", moveas: "n"}; + case '%': return {"class": "king-bishop", moveas: "b"}; + case '*': return {"class": "king-queen", moveas: "q"}; + } } // NOTE: using special symbols to not interfere with variants' pieces codes @@ -1359,13 +1368,11 @@ export default class ChessRules { } getStepSpec(color, x, y, piece) { - 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]; + let pieceType = + !piece + ? this.getPieceType(x, y) + : this.pieceDef(piece, color, x, y).moveas || piece; + let res = this.pieceDef(pieceType, color, x, y); if (!res["both"]) res.both = []; if (!res["moves"]) @@ -2510,7 +2517,7 @@ export default class ChessRules { move.appear.forEach(a => { this.g_pieces[a.x][a.y] = document.createElement("piece"); C.AddClass_es(this.g_pieces[a.x][a.y], - this.pieces(a.c, a.x, a.y)[a.p]["class"]); + this.pieceDef(a.p, a.c, a.x, a.y)["class"]); this.g_pieces[a.x][a.y].classList.add(V.GetColorClass(a.c)); this.g_pieces[a.x][a.y].style.width = pieceWidth + "px"; this.g_pieces[a.x][a.y].style.height = pieceWidth + "px"; @@ -2585,11 +2592,12 @@ export default class ChessRules { } const maxDist = this.getMaxDistance(r); const apparentColor = this.getColor(start.x, start.y); - const pieces = this.pieces(apparentColor, start.x, start.y); if (drag) { const startCode = this.getPiece(start.x, start.y); - C.RemoveClass_es(movingPiece, pieces[startCode]["class"]); - C.AddClass_es(movingPiece, pieces[drag.p]["class"]); + C.RemoveClass_es(movingPiece, + this.pieceDef(startCode, apparentColor, start.x, start.y)["class"]); + C.AddClass_es(movingPiece, + this.pieceDef(drag.p, apparentColor, start.x, start.y)["class"]); if (apparentColor != drag.c) { movingPiece.classList.remove(V.GetColorClass(apparentColor)); movingPiece.classList.add(V.GetColorClass(drag.c)); diff --git a/pieces/Avalam/.gitignore b/pieces/Avalam/.gitignore new file mode 100644 index 0000000..756b22f --- /dev/null +++ b/pieces/Avalam/.gitignore @@ -0,0 +1 @@ +*.svg diff --git a/pieces/Avalam/black_stack.svg b/pieces/Avalam/black_stack.svg deleted file mode 100644 index 1ee0e2b..0000000 --- a/pieces/Avalam/black_stack.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pieces/Avalam/black_stack2.svg b/pieces/Avalam/black_stack2.svg deleted file mode 100644 index de29024..0000000 --- a/pieces/Avalam/black_stack2.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/black_stack3.svg b/pieces/Avalam/black_stack3.svg deleted file mode 100644 index 3518349..0000000 --- a/pieces/Avalam/black_stack3.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/black_stack4.svg b/pieces/Avalam/black_stack4.svg deleted file mode 100644 index 6d92f7c..0000000 --- a/pieces/Avalam/black_stack4.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/black_stack5.svg b/pieces/Avalam/black_stack5.svg deleted file mode 100644 index 9ea4b0a..0000000 --- a/pieces/Avalam/black_stack5.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/white_stack.svg b/pieces/Avalam/white_stack.svg deleted file mode 100644 index d997ee1..0000000 --- a/pieces/Avalam/white_stack.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pieces/Avalam/white_stack2.svg b/pieces/Avalam/white_stack2.svg deleted file mode 100644 index b238b2d..0000000 --- a/pieces/Avalam/white_stack2.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/white_stack3.svg b/pieces/Avalam/white_stack3.svg deleted file mode 100644 index 3a3df31..0000000 --- a/pieces/Avalam/white_stack3.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/white_stack4.svg b/pieces/Avalam/white_stack4.svg deleted file mode 100644 index 91a4df3..0000000 --- a/pieces/Avalam/white_stack4.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Avalam/white_stack5.svg b/pieces/Avalam/white_stack5.svg deleted file mode 100644 index 7e8cf11..0000000 --- a/pieces/Avalam/white_stack5.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pieces/Emergo/.gitignore b/pieces/Emergo/.gitignore new file mode 100644 index 0000000..756b22f --- /dev/null +++ b/pieces/Emergo/.gitignore @@ -0,0 +1 @@ +*.svg diff --git a/pieces/Emergo/generateSVG_simple.py b/pieces/Emergo/generateSVG.py similarity index 92% rename from pieces/Emergo/generateSVG_simple.py rename to pieces/Emergo/generateSVG.py index 51f734e..258af71 100755 --- a/pieces/Emergo/generateSVG_simple.py +++ b/pieces/Emergo/generateSVG.py @@ -4,7 +4,11 @@ # https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 # https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths -preamble = """ +############################### +# 1. Simple pieces (mono-color) +############################### + +preamble_simple = """ """ @@ -45,7 +49,7 @@ for color in ["white", "black"]: for number in range(12): filename = chr(65 + number + chrShift) + "@.svg" f = open(filename, "w") - f.write(preamble) + f.write(preamble_simple) f.write("\n") f.write(white if color == "white" else black) f.write("\n") @@ -55,8 +59,11 @@ for color in ["white", "black"]: f.write(final) f.close() +################################ +# 1. Composite pieces (bi-color) +################################ -preamble = """ +preamble_composite = """ @@ -134,7 +141,7 @@ for colorTop in ["white", "black"]: for bottom in range(12): filename = chr(65 + top + chrShift) + chr(65 + bottom + chrShift) + ".svg" f = open(filename, "w") - f.write(preamble) + f.write(preamble_composite) f.write("\n") f.write(black_bottom if colorTop == "white" else white_bottom) f.write("\n") diff --git a/pieces/Emergo/generateSVG_composite.py b/pieces/Emergo/generateSVG_composite.py deleted file mode 100755 index 85726d5..0000000 --- a/pieces/Emergo/generateSVG_composite.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python - -# Compose each piece SVG with numbers -# https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 -# https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths - -preamble = """ - - - - - - - -""" - -black_top = '' -white_bottom = '' -white_top = '' -black_bottom = '' - -digits = { - "top": [ - # 1 (unused here) - '= 1: - f.write(digits["top"][top] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "white" else 'stroke="orange"') + '/>') - f.write("\n") - if bottom >= 1: - f.write(digits["bottom"][bottom] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "black" else 'stroke="orange"') + '/>') - f.write("\n") - f.write(final) - f.close() diff --git a/variants/Emergo/class.js b/variants/Emergo/class.js index 2635e63..f40627b 100644 --- a/variants/Emergo/class.js +++ b/variants/Emergo/class.js @@ -19,6 +19,12 @@ export default class EmergoRules extends ChessRules { get hasEnpassant() { return false; } + get hasReserve() { + return true; + } + static get HasKing() { + return false; + } // board element == piece class ref: board2fen(b) { @@ -48,11 +54,15 @@ export default class EmergoRules extends ChessRules { } getColor(x, y) { - if (x >= this.size.x) - return x == this.size.x ? "w" : "b"; - if (this.board[x][y].charCodeAt(0) < 97) - return 'w'; - return 'b'; + const sq = (typeof x == "string" ? x : this.board[x][y]); + return sq.charCodeAt(0) < 97 ? 'w' : 'b'; + } + + pieceDef(piece, color, x, y) { + //this.board[x][y] + // --> TODO + // Moving always the same, but look differs + // class: classUp, class: classDOwn + composition } getPiece(x, y) { @@ -64,21 +74,17 @@ export default class EmergoRules extends ChessRules { } genRandInitBaseFen() { - return { fen: "9/9/9/9/9/9/9/9/9 w 0 12,12", o: {} }; + return { fen: "9/9/9/9/9/9/9/9/9", o: {} }; } static get ReserveArray() { - // Piece type doesn't matter - return ['@']; //TODO :: - - - + return ['a@']; } setOtherVariables(fenParsed) { super.setOtherVariables(fenParsed); - // Local stack of captures during a turn (squares + directions) - this.captures = [ [] ]; + // Last capture during a turn (square + direction) + this.lastCapture = null; } atLeastOneCaptureFrom([x, y], color, forbiddenStep) { @@ -102,13 +108,10 @@ export default class EmergoRules extends ChessRules { } atLeastOneCapture(color) { - const L0 = this.captures.length; - const captures = this.captures[L0 - 1]; - const L = captures.length; - if (L > 0) { + if (!!this.lastCapture) { return ( this.atLeastOneCaptureFrom( - captures[L-1].square, color, captures[L-1].step) + this.lastCapture.square, color, this.lastCapture.step) ); } for (let i = 0; i < this.size.x; i++) { @@ -196,17 +199,14 @@ export default class EmergoRules extends ChessRules { } getAllLongestCaptures(color) { - const L0 = this.captures.length; - const captures = this.captures[L0 - 1]; - const L = captures.length; let caps = []; - if (L > 0) { - let locSteps = [ captures[L-1].step ]; + if (!!this.lastCapture) { + let locSteps = [ this.lastCapture.step ]; let res = - this.getLongestCapturesFrom(captures[L-1].square, color, locSteps); + this.getLongestCapturesFrom(this.lastCapture.square, color, locSteps); Array.prototype.push.apply( caps, - res.map(r => Object.assign({ square: captures[L-1].square }, r)) + res.map(r => Object.assign({ square: this.lastCapture.square }, r)) ); } else { @@ -352,7 +352,7 @@ export default class EmergoRules extends ChessRules { } getPossibleMovesFrom([x, y], longestCaptures) { - if (x >= this.size.x) { + if (typeof x === "string") { if (longestCaptures.length == 0) return this.getReserveMoves(x); return []; @@ -360,14 +360,11 @@ export default class EmergoRules extends ChessRules { const color = this.turn; if (!!this.reserve[color] && !this.atLeastOneCapture(color)) return []; - const L0 = this.captures.length; - const captures = this.captures[L0 - 1]; - const L = captures.length; let moves = []; if (longestCaptures.length > 0) { if ( - L > 0 && - (x != captures[L-1].square[0] || y != captures[L-1].square[1]) + !!this.lastCapture && + (x != this.lastCapture.square[0] || y != this.lastCapture.square[1]) ) { return []; } @@ -403,12 +400,10 @@ export default class EmergoRules extends ChessRules { move.turn = color; //for undo V.PlayOnBoard(this.board, move); if (move.vanish.length == 2) { - const L0 = this.captures.length; - let captures = this.captures[L0 - 1]; - captures.push({ + this.lastCapture = { square: [move.end.x, move.end.y], step: [(move.end.x - move.start.x)/2, (move.end.y - move.start.y)/2] - }); + }; if (this.atLeastOneCapture(color)) // There could be other captures (mandatory) move.notTheEnd = true; @@ -423,7 +418,7 @@ export default class EmergoRules extends ChessRules { if (!move.notTheEnd) { this.turn = V.GetOppCol(color); this.movesCount++; - this.captures.push([]); + this.lastCapture = null; } } -- 2.53.0 From 0ea512cc08585d8805085957e922da2c431bdc2e Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 18 May 2026 16:17:37 +0200 Subject: [PATCH 14/16] update --- variants/Emergo/class.js | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/variants/Emergo/class.js b/variants/Emergo/class.js index f40627b..be4d392 100644 --- a/variants/Emergo/class.js +++ b/variants/Emergo/class.js @@ -66,6 +66,7 @@ export default class EmergoRules extends ChessRules { } getPiece(x, y) { + // Both characters required to describe the (aggregated) "piece" return this.board[x][y]; } @@ -198,37 +199,6 @@ export default class EmergoRules extends ChessRules { return this.maxLengthIndices(res).map(i => res[i]);; } - getAllLongestCaptures(color) { - let caps = []; - if (!!this.lastCapture) { - let locSteps = [ this.lastCapture.step ]; - let res = - this.getLongestCapturesFrom(this.lastCapture.square, color, locSteps); - Array.prototype.push.apply( - caps, - res.map(r => Object.assign({ square: this.lastCapture.square }, r)) - ); - } - else { - 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) == color - ) { - let locSteps = []; - let res = this.getLongestCapturesFrom([i, j], color, locSteps); - Array.prototype.push.apply( - caps, - res.map(r => Object.assign({ square: [i, j] }, r)) - ); - } - } - } - } - return this.maxLengthIndices(caps).map(i => caps[i]); - } - getBasicMove([x1, y1], [x2, y2], capt) { const cp1 = this.board[x1][y1]; if (!capt) { @@ -288,8 +258,6 @@ export default class EmergoRules extends ChessRules { return mv; } - -// TODO: + style getSquareColorClass(x, y) { return ((x+y) % 2 == 0 ? "dark-square": "light-square"); } -- 2.53.0 From 26a9181b8b713eca42fdcf75593715e33394cf71 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 27 May 2026 00:38:28 +0200 Subject: [PATCH 15/16] update --- variants/Emergo/class.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/Emergo/class.js b/variants/Emergo/class.js index be4d392..3c29f33 100644 --- a/variants/Emergo/class.js +++ b/variants/Emergo/class.js @@ -59,6 +59,8 @@ export default class EmergoRules extends ChessRules { } pieceDef(piece, color, x, y) { + const color = this.getColor(piece); + piece.charAt(0) //this.board[x][y] // --> TODO // Moving always the same, but look differs -- 2.53.0 From b07ccdcdccaaa0395cd6874d5603c089d50ec34a Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sun, 31 May 2026 01:07:35 +0200 Subject: [PATCH 16/16] Emergo: big step forward --- README.md | 12 +- TODO | 3 - initialize.sh | 1 - js/base_rules.js | 18 +- pieces/Emergo/.gitignore | 1 - pieces/Emergo/generateSVG.py | 157 ---------------- {pieces => variants}/Avalam/generateSVG.py | 0 variants/Emergo/class.js | 203 ++++++++++++++++++--- variants/Emergo/rules.html | 1 + variants/Emergo/style.css | 38 +--- 10 files changed, 205 insertions(+), 229 deletions(-) delete mode 100644 pieces/Emergo/.gitignore delete mode 100755 pieces/Emergo/generateSVG.py rename {pieces => variants}/Avalam/generateSVG.py (100%) create mode 100644 variants/Emergo/rules.html diff --git a/README.md b/README.md index 8ac79c3..a62b739 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ # xogo.casa -Simplified version of old vchess.club, to focus on the essential : the game. +Simplified version of old vchess.club, to focus on the essential. ## Requirements (dev) -PHP + Python + Node.js + npm. +PHP + Node.js + npm. + ```npm i -g nodemon``` ## Usage -Initialisation (done once): retrieve 'binaries' -(files binary or not which never change). +Initialisation (done only once): ```./initialize.sh``` -Rename and edit the parameters.js.dist file: +Edit the parameters.js file: -```cp js/parameters.js.dist js/parameters.js``` +```vim js/parameters.js``` Finally: diff --git a/TODO b/TODO index 7114f80..81f9dfb 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,3 @@ -Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number -==> plus simple : deux classes, images superposées (?) - https://fr.wikipedia.org/wiki/Unlur DuckChess + crazyduck --> move it at random ? Capturing ? Hmm. diff --git a/initialize.sh b/initialize.sh index a7fd40d..5b9300f 100755 --- a/initialize.sh +++ b/initialize.sh @@ -5,4 +5,3 @@ wget https://xogo.casa/extras.zip && unzip extras.zip cp js/parameters.js.dist js/parameters.js npm i cd pieces/Avalam && python generateSVG.py && cd ../.. -cd pieces/Emergo && python generateSVG.py && cd ../.. diff --git a/js/base_rules.js b/js/base_rules.js index 1a7543c..e5900af 100644 --- a/js/base_rules.js +++ b/js/base_rules.js @@ -505,6 +505,9 @@ export default class ChessRules { (oldV,newV) => oldV + (this.reserve[c][newV] > 0 ? 1 : 0), 0); } + // Placeholder for variants building SVG pieces "on demand" + setPieceBackground(domPiece, piece, color, x, y) {} + static AddClass_es(elt, class_es) { if (!Array.isArray(class_es)) class_es = [class_es]; @@ -636,9 +639,11 @@ export default class ChessRules { if (!r) r = chessboard.getBoundingClientRect(); const pieceWidth = this.getPieceWidth(r.width); - const addPiece = (i, j, arrName, classes) => { + const addPiece = (i, j, arrName, classes, piece, color) => { this[arrName][i][j] = document.createElement("piece"); C.AddClass_es(this[arrName][i][j], classes); + if (!!piece) + this.setPieceBackground(this[arrName][i][j], piece, color, i, j); this[arrName][i][j].style.width = pieceWidth + "px"; this[arrName][i][j].style.height = pieceWidth + "px"; let [ip, jp] = this.getPixelPosition(i, j, r); @@ -661,11 +666,12 @@ export default class ChessRules { } else this[arrName] = ArrayFun.init(this.size.x, this.size.y, null); - if (arrName == "d_pieces") + if (arrName == "d_pieces") { this.marks.forEach((m) => { const mCoords = this.coordsFromUsual(m); addPiece(mCoords.x, mCoords.y, arrName, "mark"); }); + } }; if (this.marks) conditionalReset("d_pieces"); @@ -676,7 +682,7 @@ export default class ChessRules { const color = this.getColor(i, j); const piece = this.getPiece(i, j); addPiece(i, j, "g_pieces", - this.pieceDef(piece, color, i, j)["class"]); + this.pieceDef(piece, color, i, j)["class"], piece, color); this.g_pieces[i][j].classList.add(V.GetColorClass(color)); if (this.enlightened && !this.enlightened[i][j]) this.g_pieces[i][j].classList.add("hidden"); @@ -744,6 +750,7 @@ export default class ChessRules { rcontainer.appendChild(r_cell); let piece = document.createElement("piece"); C.AddClass_es(piece, this.pieceDef(p, c, c, p)["class"]); + this.setPieceBackground(piece, p, c, c, p); piece.classList.add(V.GetColorClass(c)); piece.style.width = "100%"; piece.style.height = "100%"; @@ -1063,6 +1070,8 @@ export default class ChessRules { const cdisp = moves[i].choice || moves[i].appear[0].p; C.AddClass_es(piece, this.pieceDef(cdisp, color, moves[i].end.x, moves[i].end.y)["class"]); + this.setPieceBackground(piece, + cdisp, color, moves[i].end.x, moves[i].end.y); piece.classList.add(V.GetColorClass(color)); piece.style.width = "100%"; piece.style.height = "100%"; @@ -2518,6 +2527,7 @@ export default class ChessRules { this.g_pieces[a.x][a.y] = document.createElement("piece"); C.AddClass_es(this.g_pieces[a.x][a.y], this.pieceDef(a.p, a.c, a.x, a.y)["class"]); + this.setPieceBackground(this.g_pieces[a.x][a.y], a.p, a.c, a.x, a.y); this.g_pieces[a.x][a.y].classList.add(V.GetColorClass(a.c)); this.g_pieces[a.x][a.y].style.width = pieceWidth + "px"; this.g_pieces[a.x][a.y].style.height = pieceWidth + "px"; @@ -2598,6 +2608,8 @@ export default class ChessRules { this.pieceDef(startCode, apparentColor, start.x, start.y)["class"]); C.AddClass_es(movingPiece, this.pieceDef(drag.p, apparentColor, start.x, start.y)["class"]); + this.setPieceBackground(movingPiece, + drag.p, apparentColor, start.x, start.y); if (apparentColor != drag.c) { movingPiece.classList.remove(V.GetColorClass(apparentColor)); movingPiece.classList.add(V.GetColorClass(drag.c)); diff --git a/pieces/Emergo/.gitignore b/pieces/Emergo/.gitignore deleted file mode 100644 index 756b22f..0000000 --- a/pieces/Emergo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.svg diff --git a/pieces/Emergo/generateSVG.py b/pieces/Emergo/generateSVG.py deleted file mode 100755 index 258af71..0000000 --- a/pieces/Emergo/generateSVG.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env python - -# Compose each piece SVG with numbers -# https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 -# https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths - -############################### -# 1. Simple pieces (mono-color) -############################### - -preamble_simple = """ - -""" - -black = '' -white = '' - -digits = [ - # 1 (unused) - '= 1: - f.write(digits[number] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if color == "white" else 'stroke="orange"') + '/>') - f.write("\n") - f.write(final) - f.close() - -################################ -# 1. Composite pieces (bi-color) -################################ - -preamble_composite = """ - - - - - - - -""" - -black_top = '' -white_bottom = '' -white_top = '' -black_bottom = '' - -digits = { - "top": [ - # 1 (unused here) - '= 1: - f.write(digits["top"][top] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "white" else 'stroke="orange"') + '/>') - f.write("\n") - if bottom >= 1: - f.write(digits["bottom"][bottom] + ' fill="none" stroke-width="5" ' + ('stroke="red"' if colorTop == "black" else 'stroke="orange"') + '/>') - f.write("\n") - f.write(final) - f.close() diff --git a/pieces/Avalam/generateSVG.py b/variants/Avalam/generateSVG.py similarity index 100% rename from pieces/Avalam/generateSVG.py rename to variants/Avalam/generateSVG.py diff --git a/variants/Emergo/class.js b/variants/Emergo/class.js index 3c29f33..4084edc 100644 --- a/variants/Emergo/class.js +++ b/variants/Emergo/class.js @@ -53,23 +53,8 @@ export default class EmergoRules extends ChessRules { return board; } - getColor(x, y) { - const sq = (typeof x == "string" ? x : this.board[x][y]); - return sq.charCodeAt(0) < 97 ? 'w' : 'b'; - } - - pieceDef(piece, color, x, y) { - const color = this.getColor(piece); - piece.charAt(0) - //this.board[x][y] - // --> TODO - // Moving always the same, but look differs - // class: classUp, class: classDOwn + composition - } - - getPiece(x, y) { - // Both characters required to describe the (aggregated) "piece" - return this.board[x][y]; + getSquareColorClass(x, y) { + return ((x+y) % 2 == 0 ? "dark-square": "light-square"); } get size() { @@ -88,6 +73,183 @@ export default class EmergoRules extends ChessRules { super.setOtherVariables(fenParsed); // Last capture during a turn (square + direction) this.lastCapture = null; + // Compute pieces drawings when required, and cache it: + this.svgEncodedPieces = {}; + } + + getColor(x, y) { + const sq = (typeof x == "string" ? x : this.board[x][y]); + return sq.charCodeAt(0) < 97 ? 'w' : 'b'; + } + + getPiece(x, y) { + // Both characters required to describe the (aggregated) "piece" + return this.board[x][y]; + } + + pieceDef(piece, color, x, y) { + // Captures handled in getPotentialMoves directly + return { + moves: [{ steps: [ [-1, -1], [-1, 1], [1, -1], [1, 1] ] }], + "class": "emergo-piece", + }; + } + + static GetSvgNumber(n, position) { + switch (position) { + case "full": { + switch (n) { + case 1: //unused + return '`; + if (piece.charAt(1) == '@') { + const number = ... + rawSvg += ` + `; + if (number > 1) { + rawSvg += V.GetSvgNumber(number, "full") + ` + fill="none" stroke-width="5" + stroke="${color=='w' ? 'red' : 'orange'}" + />`; + } + } + else { //composite + const numTop = ..., + numBottom = ...; + const isTopWhite = + rawSvg += ` + + + + + + + `; + if (numTop > 1) { + rawSvg += V.GetSvgNumber(numTop, "top") + ` + fill="none" stroke-width="5" + stroke="${color=='w' ? 'red' : 'orange'}" + />` + } + if (numBottom > 1) { + rawSvg += V.GetSvgNumber(numBottom, "bottom") + ` + fill="none" stroke-width="5" + stroke="${color=='b' ? 'red' : 'orange'}" + />`; + } + } + rawSvg += ""; + + // On encode le SVG pour qu'il soit lisible dans un "url()" CSS + const svgBase64 = btoa(unescape(encodeURIComponent(rawSvg))); + this.svgEncodedPieces[piece] = svgBase64; + return svgBase64; + } + + // Draw piece + setPieceBackground(domPiece, piece, color, x, y) { + const svgBase64 = this.getSvgEncodedPiece(piece, color); + domPiece.style.backgroundImage = + `url('data:image/svg+xml;base64,${svgBase64}')`; } atLeastOneCaptureFrom([x, y], color, forbiddenStep) { @@ -260,13 +422,6 @@ export default class EmergoRules extends ChessRules { return mv; } - getSquareColorClass(x, y) { - return ((x+y) % 2 == 0 ? "dark-square": "light-square"); - } - - - - getDropMovesFrom([c, p]) { const color = c; if (!this.reserve[color] || this.atLeastOneCapture(color)) diff --git a/variants/Emergo/rules.html b/variants/Emergo/rules.html new file mode 100644 index 0000000..c65158e --- /dev/null +++ b/variants/Emergo/rules.html @@ -0,0 +1 @@ +

TODO

diff --git a/variants/Emergo/style.css b/variants/Emergo/style.css index 9650899..1cc5116 100644 --- a/variants/Emergo/style.css +++ b/variants/Emergo/style.css @@ -1,35 +1,5 @@ -piece.white.stack { - background-image: url('/pieces/Emergo/white_.svg'); -} - - - - -piece.white.stack2 { - background-image: url('/pieces/Avalam/white_stack2.svg'); -} -piece.white.stack3 { - background-image: url('/pieces/Avalam/white_stack3.svg'); -} -piece.white.stack4 { - background-image: url('/pieces/Avalam/white_stack4.svg'); -} -piece.white.stack5 { - background-image: url('/pieces/Avalam/white_stack5.svg'); -} - -piece.black.stack { - background-image: url('/pieces/Avalam/black_stack.svg'); -} -piece.black.stack2 { - background-image: url('/pieces/Avalam/black_stack2.svg'); -} -piece.black.stack3 { - background-image: url('/pieces/Avalam/black_stack3.svg'); -} -piece.black.stack4 { - background-image: url('/pieces/Avalam/black_stack4.svg'); -} -piece.black.stack5 { - background-image: url('/pieces/Avalam/black_stack5.svg'); +.emergo-piece { + background-image: var(--piece-img); + /*background-size: contain; + background-repeat: no-repeat;*/ } -- 2.53.0