## 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```)
<meta name="viewport"
content="width=device-width, initial-scale=1"/>
<link id="_common_css"
- rel="stylesheet" href="/common.css"/>
+ rel="stylesheet" href="/css/common.css"/>
<script type="text/javascript">
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
// Apple brightness in SVG workaround
// TODO: remove that.
let link = document.createElement("link");
- link.href = "iOS_fix.css";
+ link.href = "/css/iOS_fix.css";
link.type = "text/css";
link.rel = "stylesheet";
link.media = "screen,print";
</div>
</main>
- <script src="/parameters.js"></script>
- <script src="/variants.js"></script>
- <script src="/app.js"></script>
+ <script src="/js/parameters.js"></script>
+ <script src="/js/sanitize.js"></script>
+ <script src="/js/variants.js"></script>
+ <script src="/js/app.js"></script>
</body>
</html>
///////////////////
// 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
/////////
// 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
});
}
}
-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 = "";
});
}
+
+
+
+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())
<span>vs. ${gameInfos.players[oppIndex].name}</span>
</p>
</div>`;
- const options = Object.entries(gameInfos.options);
- if (options.length > 0) {
+ const _options = Object.entries(gameInfos.options);
+ if (_options.length > 0) {
htmlContent += '<div class="options-info">';
let i = 0;
- while (i < options.length) {
+ while (i < _options.length) {
htmlContent += '<div class="row">';
for (let j=i; j<i+4; j++) {
- if (j == options.length)
+ if (j == _options.length)
break;
- const opt = options[j];
+ const opt = _options[j];
if (!opt[1]) //includes 0 and false (lighter display)
continue;
htmlContent +=
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");
}
};
+
+
+
+
+
+
+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 || {};
// 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)
--- /dev/null
+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;
--- /dev/null
+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;
-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,
// 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
};
// 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);
}
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": {
// 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)
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);
}
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))
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;
#!/bin/sh
-nodemon -w server.js &
+nodemon -w js/server.js &
echo $! > .pid
php -S localhost:8000 &
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.black.amazon {
background-image: url('/pieces/black_amazon.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.alice-pawn {
background-image: url('/pieces/yellow_pawn.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.target {
background-image: url('/pieces/Ambiguous/yellow_target.svg');
+++ /dev/null
-../_Antiking/style.css
\ No newline at end of file
--- /dev/null
+@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');
+}
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
div.illegal-text {
position: relative;
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.mammoth {
background-image: url('/pieces/Balaklava/white_mammoth.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.undefined {
background-image: url('/pieces/white_mystery.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
@import url("/variants/_SpecialCaptures/style.css");
piece.white.immobilizer {
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.black.cleopatra {
background-image: url('/pieces/Benedict/black_cleopatra.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
@import url("/variants/_Berolina/style.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.black.empress {
background-image: url('/pieces/black_empress.svg');
-@import url("/base_pieces.css")
+@import url("/css/base_pieces.css")
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.egg {
background-image: url('/pieces/Chakart/egg.svg');
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)),
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.checkered.pawn {
background-image: url('/pieces/Checkered/checkered_pawn.svg');
-@import url("/base_pieces.css")
+@import url("/css/base_pieces.css")
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css")
+@import url("/css/base_pieces.css")
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.nv-pawn {
background-image: url('/pieces/yellow_pawn.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.black.royal_queen {
background-image: url('/pieces/Coregal/black_royal_queen.svg');
-@import url("/base_pieces.css")
+@import url("/css/base_pieces.css")
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.c_rook {
background-image: url('/pieces/Cwda/c_white_rook.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
@font-face {
font-family: chess-font;
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.black.commoner {
background-image: url('/pieces/black_commoner.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
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({
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);
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})],
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;
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);
}
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.jailer {
background-image: url('/pieces/Eightpieces/white_jailer.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.white.sleepy-pawn {
background-image: url('/pieces/yellow_pawn.svg');
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
-@import url("/base_pieces.css");
+@import url("/css/base_pieces.css");
piece.black.antiking {
background-image: url('/pieces/_Antiking/black_antiking.svg');