From e750faf6e94d050f94039a8769132ebbfb3a5c5c Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sun, 3 May 2026 21:35:36 +0200 Subject: [PATCH] refactoring in progress --- README.md | 9 +- base_pieces.css => css/base_pieces.css | 0 common.css => css/common.css | 0 iOS_fix.css => css/iOS_fix.css | 0 index.html | 11 +- app.js => js/app.js | 209 ++++++++++++++++++-- base_rules.js => js/base_rules.js | 2 +- js/parameters.js | 16 ++ parameters.js.dist => js/parameters.js.dist | 0 js/sanitize.js | 18 ++ server.js => js/server.js | 91 +++++++-- variants.js => js/variants.js | 0 start.sh | 2 +- variants/Absorption/style.css | 2 +- variants/Alice/style.css | 2 +- variants/Align4/style.css | 2 +- variants/Allmate/style.css | 2 +- variants/Ambiguous/style.css | 2 +- variants/Antiking2/style.css | 9 +- variants/Antimatter/style.css | 2 +- variants/Apocalypse/style.css | 2 +- variants/Arena/style.css | 2 +- variants/Atomic/style.css | 2 +- variants/Avalanche/style.css | 2 +- variants/Balaklava/style.css | 2 +- variants/Balanced/style.css | 2 +- variants/Bario/style.css | 2 +- variants/Baroque/style.css | 2 +- variants/Benedict/style.css | 2 +- variants/Berolina/style.css | 2 +- variants/Bicolour/style.css | 2 +- variants/Brotherhood/style.css | 2 +- variants/Cannibal/style.css | 2 +- variants/Capablanca/style.css | 2 +- variants/Capture/style.css | 2 +- variants/Chaining/style.css | 2 +- variants/Chakart/style.css | 2 +- variants/Checkered/class.js | 3 +- variants/Checkered/style.css | 2 +- variants/Checkless/style.css | 2 +- variants/Chess960/style.css | 2 +- variants/Circular/style.css | 2 +- variants/Clorange/style.css | 2 +- variants/Convert/style.css | 2 +- variants/Copycat/style.css | 2 +- variants/Coregal/style.css | 2 +- variants/Coronation/style.css | 2 +- variants/Crazyhouse/style.css | 2 +- variants/Crossing/style.css | 2 +- variants/Cwda/style.css | 2 +- variants/Cylinder/style.css | 2 +- variants/Dark/style.css | 2 +- variants/Diamond/style.css | 2 +- variants/Dice/style.css | 2 +- variants/Discoduel/style.css | 2 +- variants/Doublearmy/style.css | 2 +- variants/Doublemove/style.css | 2 +- variants/Dynamo/style.css | 2 +- variants/Eightpieces/class.js | 128 +++++++++--- variants/Eightpieces/style.css | 2 +- variants/Giveaway/style.css | 2 +- variants/Madrasi/style.css | 2 +- variants/Progressive/style.css | 2 +- variants/Recycle/style.css | 2 +- variants/Refusal/style.css | 2 +- variants/Rifle/style.css | 2 +- variants/Sleepy/style.css | 2 +- variants/Suction/style.css | 2 +- variants/Teleport/style.css | 2 +- variants/Zen/style.css | 2 +- variants/_Antiking/style.css | 2 +- 71 files changed, 475 insertions(+), 133 deletions(-) rename base_pieces.css => css/base_pieces.css (100%) rename common.css => css/common.css (100%) rename iOS_fix.css => css/iOS_fix.css (100%) rename app.js => js/app.js (76%) rename base_rules.js => js/base_rules.js (99%) create mode 100644 js/parameters.js rename parameters.js.dist => js/parameters.js.dist (100%) create mode 100644 js/sanitize.js rename server.js => js/server.js (80%) rename variants.js => js/variants.js (100%) mode change 120000 => 100644 variants/Antiking2/style.css diff --git a/README.md b/README.md index 0b900c5..5555f0d 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,15 @@ PHP + Node.js + npm. ## Usage -Initialisation (done once): +Initialisation (done once): retrieve 'binaries' +(files binary or not which never change). ```./initialize.sh``` -You may want to edit the parameters.js file. Then: +Rename and edit the parameters.js.dist file: + +```cp js/parameters.js.dist js/parameters.js``` + +Finally: ```./start.sh``` (and later, ```./stop.sh```) diff --git a/base_pieces.css b/css/base_pieces.css similarity index 100% rename from base_pieces.css rename to css/base_pieces.css diff --git a/common.css b/css/common.css similarity index 100% rename from common.css rename to css/common.css diff --git a/iOS_fix.css b/css/iOS_fix.css similarity index 100% rename from iOS_fix.css rename to css/iOS_fix.css diff --git a/index.html b/index.html index 669b406..5baa1bb 100644 --- a/index.html +++ b/index.html @@ -7,13 +7,13 @@ + rel="stylesheet" href="/css/common.css"/> - - + + + + diff --git a/app.js b/js/app.js similarity index 76% rename from app.js rename to js/app.js index c5d5c7f..4a92707 100644 --- a/app.js +++ b/js/app.js @@ -3,12 +3,12 @@ let $ = document; //shortcut /////////////////// // Initialisations -// https://stackoverflow.com/a/27747377/12660887 -function generateId (len) { - const dec2hex = (dec) => dec.toString(16).padStart(2, "0"); - let arr = new Uint8Array(len / 2); //len/2 because 2 chars per hex value - window.crypto.getRandomValues(arr); //fill with random integers - return Array.from(arr, dec2hex).join(''); +function generateId(len) { + const chars = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const arr = new Uint8Array(len); + window.crypto.getRandomValues(arr); + return Array.from(arr, (byte) => chars[byte % chars.length]).join(''); } // Populate variants dropdown list @@ -50,9 +50,30 @@ inputName.onfocus = () => setActive(true); ///////// // Utils +function h(tag, attrs, children) { + const el = document.createElement(tag); + if (attrs) { + Object.keys(attrs).forEach(k => { + // Special treatment for events (ex: onclick) + if (k.startsWith("on")) + el[k.toLowerCase()] = attrs[k]; + else + el.setAttribute(k, attrs[k]); + }); + } + if (children) { + if (Array.isArray(children)) + children.forEach(c => c && el.append(c)); + else + el.append(children); + } + return el; +} + function setName() { // 'onChange' event on name input text field [HTML] - localStorage.setItem("name", $.getElementById("myName").value); + const name = $.getElementById("myName").value; + localStorage.setItem("name", sanitize(name, 30)); } // Turn a "tab" on, and "close" all others @@ -117,17 +138,86 @@ function showNewGameForm() { }); } } -function backToNormalSeek() { +function backToNormalSeek() { //TODO: index.html...... toggleVisible("newGame"); } -function toggleStyle(event, obj) { - const word = obj.innerHTML; - options[word] = !options[word]; - event.target.classList.toggle("highlight-word"); +let options; +function prepareOptions() { + options = {}; + const container = $.getElementById("gameOptions"); + container.innerHTML = ""; + if (V.Options.select) { + V.Options.select.forEach(select => { + const selectEl = h('select', { + id: `var_${select.variable}`, + onchange: (e) => { options[select.variable] = e.target.value; } + }, select.options.map(opt => + h('option', { + value: opt.value, + selected: opt.value == select.defaut + }, opt.label) + )); + container.append( + h('div', { class: 'option-select' }, [ + h('label', { for: `var_${select.variable}` }, select.label), + h('div', { class: 'select' }, [ + selectEl, + h('span', { class: 'focus' }) + ]) + ]) + ); + }); + } + if (V.Options.input) { + V.Options.input.forEach(input => { + const inputAttrs = { + id: `var_${input.variable}`, + type: input.type, + onchange: (e) => { + options[input.variable] = + (input.type == "checkbox" ? e.target.checked : e.target.value); + } + }; + if (input.type == "checkbox" && input.defaut) + inputAttrs.checked = true; + else if (input.defaut) + inputAttrs.value = input.defaut; + container.append( + h('div', { class: 'option-input' }, [ + h('label', { class: 'input' }, [ + h('input', inputAttrs), + h('span', { class: 'spacer' }), + h('span', { textContent: input.label }) + ]) + ]) + ); + }); + } + if (V.Options.styles) { + const wordsDiv = h('div', { class: 'words' }); + let i = 0; + while (i < V.Options.styles.length) { + const row = h('div', { class: 'row' }); + for (let j = i; j < i + 4 && j < V.Options.styles.length; j++) { + const styleName = V.Options.styles[j]; + row.append( + h('span', { + textContent: styleName, + onclick: (e) => { + options[styleName] = !options[styleName]; + e.target.classList.toggle("highlight-word"); + } + }) + ); + } + wordsDiv.append(row); + i += 4; + } + container.append(wordsDiv); + } } -let options; function prepareOptions() { options = {}; let optHtml = ""; @@ -210,6 +300,64 @@ function getGameLink() { }); } + + + +function fillGameInfos(gameInfos, oppIndex) { + fetch(`/variants/${gameInfos.vname}/rules.html`) + .then(res => res.text()) + .then(txt => { + const container = $.getElementById("gameInfos"); + container.innerHTML = ""; // Nettoyage initial + + // 1. Infos Joueurs + const playerDiv = h('div', { class: 'players-info' }, [ + h('p', null, [ + h('span', { class: 'bold', textContent: gameInfos.vdisp }), + h('span', { textContent: ` vs. ${gameInfos.players[oppIndex].name}` }) + ]) + ]); + + // 2. Traitement des Options (Filtrage + Groupement par 4) + const optionsInfos = h('div', { class: 'options-info' }); + const activeOptions = Object.entries(gameInfos.options).filter(opt => !!opt[1]); + + let i = 0; + while (i < activeOptions.length) { + const row = h('div', { class: 'row' }); + for (let j = i; j < i + 4 && j < activeOptions.length; j++) { + const [key, val] = activeOptions[j]; + const label = (val === true ? key : `${key}:${val}`); + row.append(h('span', { class: 'option', textContent: label + " " })); + } + optionsInfos.append(row); + i += 4; + } + + // 3. Règles (on garde innerHTML ici car le HTML vient de ton fichier local rules.html) + const rulesDiv = h('div', { class: 'rules' }); + rulesDiv.innerHTML = txt; + + // 4. Bouton de retour + const btnWrap = h('div', { class: 'btn-wrap' }, [ + h('button', { + onclick: toggleGameInfos, + textContent: "Back to game" + }) + ]); + + // Assemblage final + container.append( + playerDiv, + activeOptions.length > 0 ? optionsInfos : null, + rulesDiv, + btnWrap + ); + }); +} + + + function fillGameInfos(gameInfos, oppIndex) { fetch(`/variants/${gameInfos.vname}/rules.html`) .then(res => res.text()) @@ -221,16 +369,16 @@ function fillGameInfos(gameInfos, oppIndex) { vs. ${gameInfos.players[oppIndex].name}

`; - const options = Object.entries(gameInfos.options); - if (options.length > 0) { + const _options = Object.entries(gameInfos.options); + if (_options.length > 0) { htmlContent += '
'; let i = 0; - while (i < options.length) { + while (i < _options.length) { htmlContent += '
'; for (let j=i; j { break; if (document.hidden) notifyMe("move"); + // TODO: moves not sanitized (most likely: won't "fix"...) vr.playReceivedMove(obj.moves, () => { if (vr.getCurrentScore(obj.moves) != "*") { localStorage.removeItem("gid"); @@ -492,6 +641,30 @@ const afterPlay = (move_s, newTurn, ops) => { } }; + + + + + + +function initializeGame(obj) { + const container = $.getElementById("boardContainer"); + container.innerHTML = ""; // Nettoyage + + // Créer les boutons de contrôle proprement + const infoBtn = createSVGButton("upLeftInfos", toggleGameInfos); + const stopBtn = createSVGButton("upRightStop", confirmStopGame); + const board = $.createElement("div"); + board.className = "chessboard"; + + container.append(infoBtn, stopBtn, board); + + + + + + + let vr = null, playerColor, lastVname = undefined; function initializeGame(obj) { const options = obj.options || {}; diff --git a/base_rules.js b/js/base_rules.js similarity index 99% rename from base_rules.js rename to js/base_rules.js index 3866aa3..84cf1ea 100644 --- a/base_rules.js +++ b/js/base_rules.js @@ -1482,7 +1482,7 @@ export default class ChessRules { // Zero step but non-zero interval => impossible (!Number.isFinite(rx) && !Number.isNaN(rx)) || (!Number.isFinite(ry) && !Number.isNaN(ry)) || - // Negative number of step (impossible) + // Negative number of steps (impossible) (rx < 0 || ry < 0) || // Not the same number of steps in both directions: (!Number.isNaN(rx) && !Number.isNaN(ry) && Math.abs(rx - ry) > epsilon) diff --git a/js/parameters.js b/js/parameters.js new file mode 100644 index 0000000..15c987a --- /dev/null +++ b/js/parameters.js @@ -0,0 +1,16 @@ +const Params = +{ + // Usage on client: + http_server: "http://localhost:8000", //https://xogo.casa + socket_server: "ws://localhost:8080", //wss://xogo.casa + + // Usage on (socket) server: + socket_port: 8080, //... + dev: true, + + // Usage on both: + socket_path: "/ws", +}; + +// Next line for usage on server (Node.js) +if (typeof window === 'undefined') module.exports = Params; diff --git a/parameters.js.dist b/js/parameters.js.dist similarity index 100% rename from parameters.js.dist rename to js/parameters.js.dist diff --git a/js/sanitize.js b/js/sanitize.js new file mode 100644 index 0000000..b1aa8d8 --- /dev/null +++ b/js/sanitize.js @@ -0,0 +1,18 @@ +const sanitize = function(str, maxLength = 100) +{ + 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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return cleaned.replace(/[&<>"']/g, m => map[m]); +} + +// Next line for usage on server (Node.js) +if (typeof window === 'undefined') module.exports = sanitize; diff --git a/server.js b/js/server.js similarity index 80% rename from server.js rename to js/server.js index 06d9866..0199fdb 100644 --- a/server.js +++ b/js/server.js @@ -1,4 +1,5 @@ -const params = require("./parameters.js"); +const params = require("./js/parameters.js"); +const sanitize = require("./js/sanitize.js"); const WebSocket = require("ws"); const wss = new WebSocket.Server({ port: params.socket_port, @@ -17,16 +18,16 @@ function send(sid, code, data) { // If a player deletes local infos and then tries to resume a game, // sockets[oppSid] will probably not exist anymore: if (socket) - socket.send(JSON.stringify(Object.assign({code: code}, data))); + socket.send(JSON.stringify({code, ...data})); } function initializeGame(vname, players, options) { const gid = Crypto.randomBytes(randstrSize).toString("hex").slice(0, randstrSize); games[gid] = { - vname: vname, - players: players, - options: options, + vname, + players, + options, time: Date.now(), moveHash: {} //set of moves hashes seen so far }; @@ -35,11 +36,12 @@ function initializeGame(vname, players, options) { // Provide seed in case of, so that both players initialize with same FEN function launchGame(gid) { - const gameInfo = Object.assign( - {seed: Math.floor(Math.random() * 19840), gid: gid}, - games[gid] - ); - // players array is supposed to be full: + const gameInfo = { + seed: Math.floor(Math.random() * 19840), + gid, + ...games[gid] + }; + // Players array is supposed to be full: for (const p of games[gid].players) send(p.sid, "gamestart", gameInfo); } @@ -50,20 +52,59 @@ function getRandomVariant() { return variants[index].name; } +if (params.dev) { + const chokidar = require("chokidar"); + const watcher = chokidar.watch( + ["*.js", "*.css", "utils/", "variants/"], + { persistent: true } + ); + + // When a file changes: notify each connected client + watcher.on("change", path => { + console.log(`File changed: ${path}. Notifying clients...`); + wss.clients.forEach(socket => { + if (socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify({ + code: "filechange", + path + })); + } + }); + }); +} + wss.on("connection", (socket, req) => { const sid = req.url.split("=")[1]; //...?sid=... + if (sockets[sid]) { + // If SID is already taken, connexion is refused: + socket.send(JSON.stringify({ code: "ECONNREFUSED" })); + setTimeout(() => socket.terminate(), 100); + return; + } sockets[sid] = socket; socket.isAlive = true; socket.on("pong", () => socket.isAlive = true); - if (params.dev == true) { - const chokidar = require("chokidar"); - const watcher = chokidar.watch( - ["*.js", "*.css", "utils/", "variants/"], - {persistent: true}); - watcher.on("change", path => send(sid, "filechange", {path: path})); - } socket.on("message", (msg) => { - const obj = JSON.parse(msg); + let obj; + try { + obj = JSON.parse(msg); + } catch (e) { + return; //ignore les messages JSON malformés + } + + // 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)) + return; //unknown variant name + } + if (obj.name) + obj.name = sanitize(obj.name, 30); + if (obj.fen) + obj.fen = sanitize(obj.fen, 500); + if (obj.gid) + obj.gid = sanitize(obj.gid, 20); + switch (obj.code) { // Send challenge (may trigger game creation) case "seekgame": { @@ -96,7 +137,7 @@ wss.on("connection", (socket, req) => { // Launch game let players = [ {sid: sid, name: obj.name, randvar: randvar}, - Object.assign({}, challenges[oppIndex]) + {...challenges[oppIndex]} ]; delete challenges[oppIndex]; if (Math.random() < 0.5) @@ -141,7 +182,7 @@ wss.on("connection", (socket, req) => { vname = getRandomVariant(); games[obj.gid].players.forEach(p => p.randvar = allrand); const gid = initializeGame(vname, - games[obj.gid].players.reverse(), + [...games[obj.gid].players].reverse(), games[obj.gid].options); launchGame(gid); } @@ -196,6 +237,14 @@ wss.on("connection", (socket, req) => { break; // Relay a move + update games object case "newmove": + // Basic sanitizing on moves sent: + if ( + !games[obj.gid] || + !Array.isArray(obj.moves) || + obj.moves.length > 20 + ) { + return; + } // NOTE: still potential racing issues, but... fingers crossed const hash = Crypto.createHash("md5") .update(JSON.stringify(obj.fen)) @@ -229,7 +278,7 @@ wss.on("connection", (socket, req) => { break; //only one challenge per player } } - for (let g of Object.values(games)) { + for (const g of Object.values(games)) { const myIndex = g.players.findIndex(p => p && p.sid == sid); if (myIndex >= 0) { if (g.rematch && g.rematch[myIndex] > 0) g.rematch[myIndex] = 0; diff --git a/variants.js b/js/variants.js similarity index 100% rename from variants.js rename to js/variants.js diff --git a/start.sh b/start.sh index 53517f8..fc6d664 100755 --- a/start.sh +++ b/start.sh @@ -1,5 +1,5 @@ #!/bin/sh -nodemon -w server.js & +nodemon -w js/server.js & echo $! > .pid php -S localhost:8000 & diff --git a/variants/Absorption/style.css b/variants/Absorption/style.css index 14714ab..abce63c 100644 --- a/variants/Absorption/style.css +++ b/variants/Absorption/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.black.amazon { background-image: url('/pieces/black_amazon.svg'); diff --git a/variants/Alice/style.css b/variants/Alice/style.css index 2683462..fc82364 100644 --- a/variants/Alice/style.css +++ b/variants/Alice/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.alice-pawn { background-image: url('/pieces/yellow_pawn.svg'); diff --git a/variants/Align4/style.css b/variants/Align4/style.css index a3550bc..95e35b2 100644 --- a/variants/Align4/style.css +++ b/variants/Align4/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Allmate/style.css b/variants/Allmate/style.css index a3550bc..95e35b2 100644 --- a/variants/Allmate/style.css +++ b/variants/Allmate/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Ambiguous/style.css b/variants/Ambiguous/style.css index f8dac72..a8ca5e6 100644 --- a/variants/Ambiguous/style.css +++ b/variants/Ambiguous/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.target { background-image: url('/pieces/Ambiguous/yellow_target.svg'); diff --git a/variants/Antiking2/style.css b/variants/Antiking2/style.css deleted file mode 120000 index aee569a..0000000 --- a/variants/Antiking2/style.css +++ /dev/null @@ -1 +0,0 @@ -../_Antiking/style.css \ No newline at end of file diff --git a/variants/Antiking2/style.css b/variants/Antiking2/style.css new file mode 100644 index 0000000..f84086d --- /dev/null +++ b/variants/Antiking2/style.css @@ -0,0 +1,8 @@ +@import url("/css/css/base_pieces.css"); + +piece.black.antiking { + background-image: url('/pieces/_Antiking/black_antiking.svg'); +} +piece.white.antiking { + background-image: url('/pieces/_Antiking/white_antiking.svg'); +} diff --git a/variants/Antimatter/style.css b/variants/Antimatter/style.css index a3550bc..95e35b2 100644 --- a/variants/Antimatter/style.css +++ b/variants/Antimatter/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Apocalypse/style.css b/variants/Apocalypse/style.css index daca9e4..c4caf0a 100644 --- a/variants/Apocalypse/style.css +++ b/variants/Apocalypse/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); div.illegal-text { position: relative; diff --git a/variants/Arena/style.css b/variants/Arena/style.css index a3550bc..95e35b2 100644 --- a/variants/Arena/style.css +++ b/variants/Arena/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Atomic/style.css b/variants/Atomic/style.css index a3550bc..95e35b2 100644 --- a/variants/Atomic/style.css +++ b/variants/Atomic/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Avalanche/style.css b/variants/Avalanche/style.css index a3550bc..95e35b2 100644 --- a/variants/Avalanche/style.css +++ b/variants/Avalanche/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Balaklava/style.css b/variants/Balaklava/style.css index f44a5b6..fb1ce5e 100644 --- a/variants/Balaklava/style.css +++ b/variants/Balaklava/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.mammoth { background-image: url('/pieces/Balaklava/white_mammoth.svg'); diff --git a/variants/Balanced/style.css b/variants/Balanced/style.css index a3550bc..95e35b2 100644 --- a/variants/Balanced/style.css +++ b/variants/Balanced/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Bario/style.css b/variants/Bario/style.css index 76aab16..fb534b1 100644 --- a/variants/Bario/style.css +++ b/variants/Bario/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.undefined { background-image: url('/pieces/white_mystery.svg'); diff --git a/variants/Baroque/style.css b/variants/Baroque/style.css index d35931f..4ec9ba8 100644 --- a/variants/Baroque/style.css +++ b/variants/Baroque/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); @import url("/variants/_SpecialCaptures/style.css"); piece.white.immobilizer { diff --git a/variants/Benedict/style.css b/variants/Benedict/style.css index 1cb0afd..8062b94 100644 --- a/variants/Benedict/style.css +++ b/variants/Benedict/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.black.cleopatra { background-image: url('/pieces/Benedict/black_cleopatra.svg'); diff --git a/variants/Berolina/style.css b/variants/Berolina/style.css index 9250552..e7a5bad 100644 --- a/variants/Berolina/style.css +++ b/variants/Berolina/style.css @@ -1,2 +1,2 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); @import url("/variants/_Berolina/style.css"); diff --git a/variants/Bicolour/style.css b/variants/Bicolour/style.css index a3550bc..95e35b2 100644 --- a/variants/Bicolour/style.css +++ b/variants/Bicolour/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Brotherhood/style.css b/variants/Brotherhood/style.css index a3550bc..95e35b2 100644 --- a/variants/Brotherhood/style.css +++ b/variants/Brotherhood/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Cannibal/style.css b/variants/Cannibal/style.css index a3550bc..95e35b2 100644 --- a/variants/Cannibal/style.css +++ b/variants/Cannibal/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Capablanca/style.css b/variants/Capablanca/style.css index 10d9eac..88c715d 100644 --- a/variants/Capablanca/style.css +++ b/variants/Capablanca/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.black.empress { background-image: url('/pieces/black_empress.svg'); diff --git a/variants/Capture/style.css b/variants/Capture/style.css index 290a6f4..fd44963 100644 --- a/variants/Capture/style.css +++ b/variants/Capture/style.css @@ -1 +1 @@ -@import url("/base_pieces.css") +@import url("/css/base_pieces.css") diff --git a/variants/Chaining/style.css b/variants/Chaining/style.css index a3550bc..95e35b2 100644 --- a/variants/Chaining/style.css +++ b/variants/Chaining/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Chakart/style.css b/variants/Chakart/style.css index 6f45bed..7f26838 100644 --- a/variants/Chakart/style.css +++ b/variants/Chakart/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.egg { background-image: url('/pieces/Chakart/egg.svg'); diff --git a/variants/Checkered/class.js b/variants/Checkered/class.js index f73ed0e..72c7f3f 100644 --- a/variants/Checkered/class.js +++ b/variants/Checkered/class.js @@ -165,7 +165,8 @@ export default class CheckeredRules extends ChessRules { super.setOtherVariables(fenParsed); // Non-capturing last checkered move (if any) const cmove = fenParsed.cmove; - if (cmove == "-") this.cmove = null; + if (cmove == "-") + this.cmove = null; else { this.cmove = { start: C.SquareToCoords(cmove.substr(0, 2)), diff --git a/variants/Checkered/style.css b/variants/Checkered/style.css index 0dec143..7ad8f79 100644 --- a/variants/Checkered/style.css +++ b/variants/Checkered/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.checkered.pawn { background-image: url('/pieces/Checkered/checkered_pawn.svg'); diff --git a/variants/Checkless/style.css b/variants/Checkless/style.css index 290a6f4..fd44963 100644 --- a/variants/Checkless/style.css +++ b/variants/Checkless/style.css @@ -1 +1 @@ -@import url("/base_pieces.css") +@import url("/css/base_pieces.css") diff --git a/variants/Chess960/style.css b/variants/Chess960/style.css index a3550bc..95e35b2 100644 --- a/variants/Chess960/style.css +++ b/variants/Chess960/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Circular/style.css b/variants/Circular/style.css index 290a6f4..fd44963 100644 --- a/variants/Circular/style.css +++ b/variants/Circular/style.css @@ -1 +1 @@ -@import url("/base_pieces.css") +@import url("/css/base_pieces.css") diff --git a/variants/Clorange/style.css b/variants/Clorange/style.css index 98324a9..3b8d5e7 100644 --- a/variants/Clorange/style.css +++ b/variants/Clorange/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.nv-pawn { background-image: url('/pieces/yellow_pawn.svg'); diff --git a/variants/Convert/style.css b/variants/Convert/style.css index a3550bc..95e35b2 100644 --- a/variants/Convert/style.css +++ b/variants/Convert/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Copycat/style.css b/variants/Copycat/style.css index a3550bc..95e35b2 100644 --- a/variants/Copycat/style.css +++ b/variants/Copycat/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Coregal/style.css b/variants/Coregal/style.css index 668e543..6cba3f6 100644 --- a/variants/Coregal/style.css +++ b/variants/Coregal/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.black.royal_queen { background-image: url('/pieces/Coregal/black_royal_queen.svg'); diff --git a/variants/Coronation/style.css b/variants/Coronation/style.css index 290a6f4..fd44963 100644 --- a/variants/Coronation/style.css +++ b/variants/Coronation/style.css @@ -1 +1 @@ -@import url("/base_pieces.css") +@import url("/css/base_pieces.css") diff --git a/variants/Crazyhouse/style.css b/variants/Crazyhouse/style.css index a3550bc..95e35b2 100644 --- a/variants/Crazyhouse/style.css +++ b/variants/Crazyhouse/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Crossing/style.css b/variants/Crossing/style.css index a3550bc..95e35b2 100644 --- a/variants/Crossing/style.css +++ b/variants/Crossing/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Cwda/style.css b/variants/Cwda/style.css index f9f7679..4f4a5f4 100644 --- a/variants/Cwda/style.css +++ b/variants/Cwda/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.c_rook { background-image: url('/pieces/Cwda/c_white_rook.svg'); diff --git a/variants/Cylinder/style.css b/variants/Cylinder/style.css index a3550bc..95e35b2 100644 --- a/variants/Cylinder/style.css +++ b/variants/Cylinder/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Dark/style.css b/variants/Dark/style.css index a3550bc..95e35b2 100644 --- a/variants/Dark/style.css +++ b/variants/Dark/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Diamond/style.css b/variants/Diamond/style.css index a3550bc..95e35b2 100644 --- a/variants/Diamond/style.css +++ b/variants/Diamond/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Dice/style.css b/variants/Dice/style.css index ad11010..7f13777 100644 --- a/variants/Dice/style.css +++ b/variants/Dice/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); @font-face { font-family: chess-font; diff --git a/variants/Discoduel/style.css b/variants/Discoduel/style.css index a3550bc..95e35b2 100644 --- a/variants/Discoduel/style.css +++ b/variants/Discoduel/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Doublearmy/style.css b/variants/Doublearmy/style.css index e50c2f4..01cddb0 100644 --- a/variants/Doublearmy/style.css +++ b/variants/Doublearmy/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.black.commoner { background-image: url('/pieces/black_commoner.svg'); diff --git a/variants/Doublemove/style.css b/variants/Doublemove/style.css index a3550bc..95e35b2 100644 --- a/variants/Doublemove/style.css +++ b/variants/Doublemove/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Dynamo/style.css b/variants/Dynamo/style.css index a3550bc..95e35b2 100644 --- a/variants/Dynamo/style.css +++ b/variants/Dynamo/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Eightpieces/class.js b/variants/Eightpieces/class.js index f836570..80a6681 100644 --- a/variants/Eightpieces/class.js +++ b/variants/Eightpieces/class.js @@ -67,14 +67,31 @@ export default class EightpiecesRules extends ChessRules { return ['c', 'd', 'e', 'f', 'g', 'h', 'm', 'o']; } + // obj == "-", {-1,-1} or ["]{x,y}["] + static convertPush(obj) { + if (typeof obj === "string") + // Reading from FEN + return obj == "-" ? {x: -1, y: -1} : JSON.parse(obj); + // Sending to FEN + return obj.x < 0 ? "-" : JSON.stringify(obj); + } + + getPartFen(o) { + return Object.assign( + { + pushFrom: o.init ? "-" : V.convertPush(this.pushFrom), + pushedTo: o.init ? "-" : V.convertPush(this.pushedTo) + }, + super.getPartFen(o) + ); + } + setOtherVariables(fenParsed) { super.setOtherVariables(fenParsed); - //this.pushFrom = - //this.afterPush = + this.pushFrom = V.convertPush(fenParsed.pushFrom); + this.pushedTo = V.convertPush(fenParsed.pushedTo); } - // TODO: FEN utils pushFrom et afterPush - pieces(color, x, y) { const mirror = (this.playerColor == 'b'); return Object.assign({ @@ -147,6 +164,16 @@ export default class EightpiecesRules extends ChessRules { return this.board[i][j] == "" || (V.LANCERS.includes(p) && c == colIJ); } + canIplay(x, y) { + if ( + this.pushFrom.x == x && this.pushFrom.y == y && + this.getColor(x, y) != this.playerColor + ) { + return true; + } + return super.canIplay(x, y); + } + isImmobilized([x, y]) { const color = this.getColor(x, y); const oppCol = C.GetOppTurn(color); @@ -188,35 +215,81 @@ export default class EightpiecesRules extends ChessRules { return res; } - -// TODO: finish lancers - // http://ftp.chessvariants.com/rules/8-piece-chess - - getPotentialMovesFrom([x, y], color) { - if (!this.pushFrom) { - return this.getPassMoves(x, y).concat( - super.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 => { + return ( !super.compatibleStep( + [x, y], [m.end.x, m.end.y], + [this.pushFrom.x - x, this.pushFrom.y - y] + ) ); + }); + } + 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 - return []; //TODO + 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) + 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}) ); + }); + +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); } - getSentryPushes(x, y) { - // TODO: return all squares piece on x, y can be pushed to - return [{x: x+1, y: y-1}]; + isLastMove(move) { + if (move.vanish[0].p == 's' && move.appear[0].c != move.vanish[0].c) + return false; + return super.isLastMove(move); } - // Post-process sentry pushes (if any), regular lancer moves, lancer drops.. postProcessPotentialMoves(moves) { moves = super.postProcessPotentialMoves(moves); let finalMoves = []; for (const m of moves) { - //if (m.vanish.length == 0 && ... TODO: drop - if (m.vanish.length > 0 && V.LANCERS.includes(m.vanish[0].p)) { - // TODO: how to know it's regular? (not sentry push) + // Reorient a lancer after drop or regular move + if ( + (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.pushFrom.x || m.start.y != this.pushFrom.y) + ) + ) { this.getLancerOptions(m.end.x, m.end.y).forEach(o => { finalMoves.push( new Move({ appear: [new PiPo({x:m.end.x,y:m.end.y,c:m.appear[0].c,p:o})], @@ -227,16 +300,14 @@ export default class EightpiecesRules extends ChessRules { else if (m.vanish.length == m.appear.length || m.vanish[0].p != 's') finalMoves.push(m); else { - // Sentry "capture" --> turn into pushes + // 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); - this.getSentryPushes(x, y).forEach(sq => { - finalMoves.push( new Move({ - appear: m.appear.concat(new PiPo({x:sq.x, y:sq.y, p:p, c:c})), - vanish: m.vanish - }) ); - }); + finalMoves.push( new Move({ + appear: [new PiPo({x:m.end.x,y:m.end.y,c:c,p:p})], + vanish: [ m.vanish[0] ] + }) ); } } return finalMoves; @@ -262,7 +333,8 @@ export default class EightpiecesRules extends ChessRules { updateReserve(color, piece, count) { if (V.LANCERS.includes(piece)) - piece = 'c'; //TODO: orientation, or new drawing? + // Show only one lancer orientation, and reorient when drop: + piece = color == 'w' ? 'c' : 'g'; super.updateReserve(color, piece, count); } diff --git a/variants/Eightpieces/style.css b/variants/Eightpieces/style.css index 0ff0c7a..a35f14f 100644 --- a/variants/Eightpieces/style.css +++ b/variants/Eightpieces/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.jailer { background-image: url('/pieces/Eightpieces/white_jailer.svg'); diff --git a/variants/Giveaway/style.css b/variants/Giveaway/style.css index a3550bc..95e35b2 100644 --- a/variants/Giveaway/style.css +++ b/variants/Giveaway/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Madrasi/style.css b/variants/Madrasi/style.css index a3550bc..95e35b2 100644 --- a/variants/Madrasi/style.css +++ b/variants/Madrasi/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Progressive/style.css b/variants/Progressive/style.css index a3550bc..95e35b2 100644 --- a/variants/Progressive/style.css +++ b/variants/Progressive/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Recycle/style.css b/variants/Recycle/style.css index a3550bc..95e35b2 100644 --- a/variants/Recycle/style.css +++ b/variants/Recycle/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Refusal/style.css b/variants/Refusal/style.css index a3550bc..95e35b2 100644 --- a/variants/Refusal/style.css +++ b/variants/Refusal/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Rifle/style.css b/variants/Rifle/style.css index a3550bc..95e35b2 100644 --- a/variants/Rifle/style.css +++ b/variants/Rifle/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Sleepy/style.css b/variants/Sleepy/style.css index d614763..4ff0c39 100644 --- a/variants/Sleepy/style.css +++ b/variants/Sleepy/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.white.sleepy-pawn { background-image: url('/pieces/yellow_pawn.svg'); diff --git a/variants/Suction/style.css b/variants/Suction/style.css index a3550bc..95e35b2 100644 --- a/variants/Suction/style.css +++ b/variants/Suction/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Teleport/style.css b/variants/Teleport/style.css index a3550bc..95e35b2 100644 --- a/variants/Teleport/style.css +++ b/variants/Teleport/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/Zen/style.css b/variants/Zen/style.css index a3550bc..95e35b2 100644 --- a/variants/Zen/style.css +++ b/variants/Zen/style.css @@ -1 +1 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); diff --git a/variants/_Antiking/style.css b/variants/_Antiking/style.css index 6614062..017c413 100644 --- a/variants/_Antiking/style.css +++ b/variants/_Antiking/style.css @@ -1,4 +1,4 @@ -@import url("/base_pieces.css"); +@import url("/css/base_pieces.css"); piece.black.antiking { background-image: url('/pieces/_Antiking/black_antiking.svg'); -- 2.53.0