From: Benjamin Auder Date: Fri, 8 May 2026 22:21:25 +0000 (+0200) Subject: Finish 8-pieces implementation X-Git-Url: https://git.auder.net/variants/img/pieces/doc/current/$%7BgetWhatsApp(link)%7D?a=commitdiff_plain;h=9cc8230edff273c041c0843f096a1e4cf60dcd16;p=xogo.git Finish 8-pieces implementation --- 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; + } + } } } }