From: Benjamin Auder Date: Tue, 15 Jan 2019 00:25:23 +0000 (+0100) Subject: Computer mode in rules section almost OK X-Git-Url: https://git.auder.net/doc/html/css/scripts/%7B%7B%20asset%28%27mixstore/%3C?a=commitdiff_plain;h=582df3497b0f91dd4b645386a059eac9e98da1bb;p=vchess.git Computer mode in rules section almost OK --- diff --git a/TODO b/_tmp/TODO similarity index 67% rename from TODO rename to _tmp/TODO index 7ea4813d..cb4b8a97 100644 --- a/TODO +++ b/_tmp/TODO @@ -1,4 +1,11 @@ -Sur index, introduction menu remplacé par "mes parties", montrant parties (corr) en cours toutes variantes confondues +tell opponent that I got the move, for him to start timer (and lose...) + --> no, not needed and impossible if everybody is offline + ==> just store this time locally (cheating possible but...) +board2, board3, board4 +VariantRules2, 3 et 4 aussi +fetch challenges and corr games from server at startup (room) +but forbid anonymous to start corr games or accept challenges + Dans variant page, "mes parties" peut toujours contenir corr + importées (deux onglets) En fin de partie (observée ou non), bouton "import game" en + de "download game" ==> directement dans indexedDB --> sursis de 7 jours pour les parties par correspondance, qui sont encore chargées depuis le serveur @@ -8,7 +15,6 @@ mat en 2 échiqueté : brnkr3/pppp1p1p/4ps2/8/2P2P2/P1qP4/2c1s1PP/R1K5 // TODO: decodeURIComponent() for GET/DELETE parameters -1) Finish problems tab 2) Integrate computer play into rules tab 3) Allow correspondance play (no need for P2P: online moves through the server (which also store them)) 4) Write my-games tab (included current/finished/imported) @@ -27,32 +33,20 @@ Increase code line length to 100 or more? Chat button should be more apparent after game ends (color ?) Reinforce security for problems upload (how ?) -The mode switch between human/computer/friend (+ problem) is a mess -(example: finished computer game, ongoing friend game, reload, friend game is unreachable) - Later: -Let choice of time control, allow correspondance play, several games at the same time +Let choice of time control, allow correspondance play, several corr games at the same time ==> need to use indexedDB instead of localStorage. Maybe with Dexie https://dexie.org/ Each user would have a unique identifier stored in the client DB. Allow to cancel games (if opponent doesn't connect again) -Identity would be browser-based: different games on smartphone, home computer, work computer... (why not ?) -Index might still look the same, and variant page would have another tab "Games" -==> running, and finished (which can be deleted from local memory) -(A true analysis mode could be implemented also, to navigate in completed games --> use a button) +Live games storage would be browser-based: different games on smartphone, home computer, work computer... (why not ?) +==> (at most 1) running, and finished (which can be deleted from local memory) Allow challenging a specific player (by his chosen name) -But keep the random pairings as main playing way + always playing in ZEN mode, -except when accepting an individual challenge. +But keep the random pairings as main playing way + always playing in ZEN mode style menu : surligner onglet courant Interface : - newGame: une modalBox à paramètres, timeControl, type d'adversaire ==> "new Game") - - friend-->renommé en 'analyse' et devenant un vrai mode analyse (on garde ces trois modes ?) - -problèmes : récupérer 20 ou 50 depuis le serveur, puis les afficher un par un en les analysant directement, -comme sur le site de ProgramFOX ==> présentation unifiée échiquier avec instructions dessus et soluce cachée dessous - -==> il faut pouvoir faire "new Interface(variables)" pour lancer une analyse de problème sans repasser par le mode jeu... Importer des parties : nécessite de parser le PGN produit (possible, un peu pénible) mais permettrait mode analyse (avec bouton "analyse", comme sur ancien site). @@ -61,13 +55,13 @@ espagnol : jugada ou movimiento ? fin de la partida au lieu de final de partida ? Bouton new game ==> human only. Indiquer adversaire (éventuellement), cadence (ou "infini") -Mode analyse : accessible à tout moment d'une partie (HH, ou computer) terminée. +Mode analyse : accessible à tout moment d'une partie (HH, ou computer) terminée + bouton "analyze from here" (sur parties observées) Coordonnées sur échiquier: sur cases, à gauche (verticale) ou en bas (horizontale) Import game : en local dans indexedDb, affichage dans "Games --> Imported" -Checkered : si intervention d'un 3eme joueur, initialiser son temps à la moyenne des temps restants des deux autres... +Checkered : si intervention d'un 3eme joueur, initialiser son temps à la moyenne des temps restants des deux autres... ? Mode contre ordinateur : seulement accessible depuis onglet "Rules" (son principal intérêt) diff --git a/hexaboard_test.html b/_tmp/hexaboard_test.html similarity index 76% rename from hexaboard_test.html rename to _tmp/hexaboard_test.html index 800e1896..86abc2e0 100644 --- a/hexaboard_test.html +++ b/_tmp/hexaboard_test.html @@ -37,13 +37,21 @@ for(var a=0;a<8;a++) { }} }} +let x=100, y=100, size=40; +ctx.beginPath(); +ctx.moveTo(x + size * Math.cos(0), y + size * Math.sin(0)); +for (let side=0; side < 7; side++) { + ctx.lineTo(x + size * Math.cos(side * 2 * Math.PI / 6), y + size * Math.sin(side * 2 * Math.PI / 6)); +} +ctx.fillStyle = "#333333"; +ctx.fill(); + var img = new Image(); img.onload = function() { ctx.drawImage(img, 0, 0, 60, 60); } img.src = "public/images/pieces/wb.svg"; - diff --git a/models/Problem.js b/models/Problem.js index 78586761..7ac92f78 100644 --- a/models/Problem.js +++ b/models/Problem.js @@ -11,50 +11,34 @@ var db = require("../utils/database"); */ // TODO: callback ? -exports.create = function(vname, fen, instructions, solution) -{ - db.serialize(function() { - const vidQuery = - "SELECT id " + - "FROM Variants " + - "WHERE name = '" + vname + "'"; - db.get(vidQuery, (err,variant) => { - const insertQuery = - "INSERT INTO Problems (added, vid, fen, instructions, solution) VALUES " + - "(" + - Date.now() + "," + - variant.id + "," + - fen + "," + - instructions + "," + - solution + - ")"; - db.run(insertQuery); - }); - }); -} - -exports.getById = function(id, callback) +exports.create = function(vid, fen, instructions, solution) { db.serialize(function() { const query = - "SELECT * FROM Problems " + - "WHERE id ='" + id + "'"; - db.get(query, callback); + "INSERT INTO Problems (added, vid, fen, instructions, solution) VALUES " + + "(" + + Date.now() + "," + + vid + "," + + fen + "," + + instructions + "," + + solution + + ")"; + db.run(query); }); } -exports.getOne = function(vname, pid, callback) +exports.getOne = function(id, callback) { db.serialize(function() { const query = "SELECT * " + "FROM Problems " + - "WHERE id = " + pid; + "WHERE id = " + id; db.get(query, callback); }); } -exports.fetchN = function(vname, uid, type, directionStr, lastDt, MaxNbProblems, callback) +exports.fetchN = function(vid, uid, type, directionStr, lastDt, MaxNbProblems, callback) { db.serialize(function() { let typeLine = ""; @@ -62,7 +46,7 @@ exports.fetchN = function(vname, uid, type, directionStr, lastDt, MaxNbProblems, typeLine = "AND id " + (type=="others" ? "!=" : "=") + " " + uid; const query = "SELECT * FROM Problems " + - "WHERE vid = (SELECT id FROM Variants WHERE name = '" + vname + "') " + + "WHERE vid = " + vid + " AND added " + directionStr + " " + lastDt + " " + typeLine + " " + "ORDER BY added " + (directionStr=="<" ? "DESC " : "") + "LIMIT " + MaxNbProblems; diff --git a/public/javascripts/components/board.js b/public/javascripts/components/board.js index 0399410f..5a75f298 100644 --- a/public/javascripts/components/board.js +++ b/public/javascripts/components/board.js @@ -142,7 +142,7 @@ Vue.component('my-board', { 'incheck': showLight && incheckSq[ci][cj], }, attrs: { - id: this.getSquareId({x:ci,y:cj}), + id: getSquareId({x:ci,y:cj}), }, }, elems @@ -161,7 +161,7 @@ Vue.component('my-board', { myReservePiecesArray.push(h('div', { 'class': {'board':true, ['board'+sizeY]:true}, - attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) } + attrs: { id: getSquareId({x:sizeX+shiftIdx,y:i}) } }, [ h('img', @@ -185,7 +185,7 @@ Vue.component('my-board', { oppReservePiecesArray.push(h('div', { 'class': {'board':true, ['board'+sizeY]:true}, - attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) } + attrs: { id: getSquareId({x:sizeX+(1-shiftIdx),y:i}) } }, [ h('img', @@ -251,16 +251,6 @@ Vue.component('my-board', { ); }, methods: { - // Get the identifier of a HTML square from its numeric coordinates o.x,o.y. - getSquareId: function(o) { - // NOTE: a separator is required to allow any size of board - return "sq-" + o.x + "-" + o.y; - }, - // Inverse function - getSquareFromId: function(id) { - let idParts = id.split('-'); - return [parseInt(idParts[1]), parseInt(idParts[2])]; - }, mousedown: function(e) { e = e || window.event; let ingame = false; @@ -291,7 +281,7 @@ Vue.component('my-board', { this.selectedPiece.style.top = 0; this.selectedPiece.style.display = "inline-block"; this.selectedPiece.style.zIndex = 3000; - const startSquare = this.getSquareFromId(e.target.parentNode.id); + const startSquare = getSquareFromId(e.target.parentNode.id); this.possibleMoves = []; const color = this.mode=="analyze" || this.gameOver ? this.vr.turn @@ -337,7 +327,7 @@ Vue.component('my-board', { return; } // OK: process move attempt - let endSquare = this.getSquareFromId(landing.id); + let endSquare = getSquareFromId(landing.id); let moves = this.findMatchingMoves(endSquare); this.possibleMoves = []; if (moves.length > 1) diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index edd17c43..c411b096 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -4,12 +4,10 @@ Vue.component('my-game', { // gameId: to find the game in storage (assumption: it exists) // fen: to start from a FEN without identifiers (analyze mode) - props: ["conn","gameId","fen","mode","allowChat","allowMovelist"], + props: ["conn","gameId","fen","mode","allowChat","allowMovelist","queryHash","settings"], data: function() { return { oppConnected: false, //TODO? - // sound level: 0 = no sound, 1 = sound only on newgame, 2 = always - sound: parseInt(localStorage["sound"] || "2"), // Web worker to play computer moves without freezing interface: compWorker: new Worker('/javascripts/playCompMove.js'), timeStart: undefined, //time when computer starts thinking @@ -17,10 +15,9 @@ Vue.component('my-game', { endgameMessage: "", orientation: "w", - // if oppid == "computer" then mode = "computer" (otherwise human) oppid: "", //opponent ID in case of HH game score: "*", //'*' means 'unfinished' - // userColor: given by gameId, or fen (if no game Id) + // userColor: given by gameId, or fen in problems mode (if no game Id)... mycolor: "w", fenStart: "", moves: [], //TODO: initialize if gameId is defined... @@ -31,10 +28,30 @@ Vue.component('my-game', { watch: { fen: function(newFen) { this.vr = new VariantRules(newFen); + this.moves = []; + this.cursor = 0; + this.fenStart = newFen; + this.score = "*"; + if (this.mode == "analyze") + { + this.mycolor = V.ParseFen(newFen).turn; + this.orientation = "w"; //convention (TODO?!) + } + else if (this.mode == "computer") //only other alternative (HH with gameId) + { + this.mycolor = (Math.random() < 0.5 ? "w" : "b"); + this.orientation = this.mycolor; + this.compWorker.postMessage(["init",newFen]); + } }, gameId: function() { this.loadGame(); }, + queryHash: function(newQhash) { + // New query hash = "id=42"; get 42 as gameId + this.gameId = parseInt(newQhash.substr(2)); + this.loadGame(); + }, }, computed: { showChat: function() { @@ -65,7 +82,9 @@ Vue.component('my-game', { - +
@@ -175,8 +194,11 @@ Vue.component('my-game', { this.conn.addEventListener('message', socketMessageListener); this.conn.addEventListener('close', socketCloseListener); }; - this.conn.onmessage = socketMessageListener; - this.conn.onclose = socketCloseListener; + if (!!this.conn) + { + this.conn.onmessage = socketMessageListener; + this.conn.onclose = socketCloseListener; + } // Computer moves web worker logic: (TODO: also for observers in HH games) this.compWorker.postMessage(["scripts",variant.name]); @@ -318,20 +340,20 @@ Vue.component('my-game', { this.compWorker.postMessage(["askmove"]); }, animateMove: function(move) { - let startSquare = document.getElementById(this.getSquareId(move.start)); - let endSquare = document.getElementById(this.getSquareId(move.end)); + let startSquare = document.getElementById(getSquareId(move.start)); + let endSquare = document.getElementById(getSquareId(move.end)); let rectStart = startSquare.getBoundingClientRect(); let rectEnd = endSquare.getBoundingClientRect(); let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y}; let movingPiece = - document.querySelector("#" + this.getSquareId(move.start) + " > img.piece"); + document.querySelector("#" + getSquareId(move.start) + " > img.piece"); // HACK for animation (with positive translate, image slides "under background") // Possible improvement: just alter squares on the piece's way... squares = document.getElementsByClassName("board"); for (let i=0; i {}); if (this.mode == "human") { updateStorage(move); //after our moves and opponent moves - if (this.vr.turn == this.userColor) + if (this.vr.turn == this.mycolor) this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid})); } else if (this.mode == "computer") @@ -400,7 +422,7 @@ Vue.component('my-game', { this.showScoreMsg(score); // TODO: notify end of game (give score) } - else if (this.mode == "computer" && this.vr.turn != this.userColor) + else if (this.mode == "computer" && this.vr.turn != this.mycolor) this.playComputerMove(); // https://vuejs.org/v2/guide/list.html#Caveats (also for undo) if (navigate) @@ -419,7 +441,7 @@ Vue.component('my-game', { this.lastMove = (this.cursor > 0 ? this.moves[this.cursor-1] : undefined); if (navigate) this.$children[0].$forceUpdate(); //TODO!? - if (this.sound == 2) + if (this.settings.sound == 2) new Audio("/sounds/undo.mp3").play().catch(err => {}); this.incheck = this.vr.getCheckSquares(this.vr.turn); if (!navigate && this.mode == "analyze") diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js index cf2cd074..00ea7692 100644 --- a/public/javascripts/components/problems.js +++ b/public/javascripts/components/problems.js @@ -1,4 +1,5 @@ Vue.component('my-problems', { + props: ["queryHash","settings"], data: function () { return { userId: user.id, @@ -38,7 +39,7 @@ Vue.component('my-problems', { {{ curProb.instructions }}

- +

@@ -128,10 +129,24 @@ Vue.component('my-problems', {

`, + watch: { + queryHash: function(newQhash) { + if (!!newQhash) + { + // New query hash = "id=42"; get 42 as problem ID + const pid = parseInt(newQhash.substr(2)); + this.showProblem(pid); + } + else + this.curProb = null; //(back to) list display + }, + }, created: function() { - // TODO: adapt this, #problems:28 ? (for example) - if (location.hash.length > 0) - this.showProblem(location.hash.slice(1)); + if (!!this.queryHash) + { + const pid = parseInt(this.queryHash.substr(2)); + this.showProblem(pid); + } else this.firstFetch(); }, @@ -249,7 +264,7 @@ Vue.component('my-problems', { } } ajax( - "/problems/" + variant.name, //TODO: use variant._id ? + "/problems/" + variant.id, "GET", { type: type, @@ -284,7 +299,7 @@ Vue.component('my-problems', { }, deleteProblem: function(pid) { ajax( - "/problems/" + variant.name + "/" + pid, //TODO: with variant.id ? + "/problems/" + variant.id + "/" + pid, "DELETE", response => { // Delete problem from the list on client side @@ -297,7 +312,7 @@ Vue.component('my-problems', { sendProblem: function() { // Send it to the server and close modal ajax( - "/problems/" + variant.name, //TODO: with variant.id ? + "/problems/" + variant.id, (this.modalProb.id > 0 ? "PUT" : "POST"), this.modalProb, response => { diff --git a/public/javascripts/components/rules.js b/public/javascripts/components/rules.js index 02d3a0ca..e9df1ecc 100644 --- a/public/javascripts/components/rules.js +++ b/public/javascripts/components/rules.js @@ -1,11 +1,33 @@ // Load rules on variant page Vue.component('my-rules', { + props: ["settings"], data: function() { - return { content: "" }; + return { + content: "", + display: "rules", + mode: "computer", + mycolor: "w", + allowMovelist: true, + fen: "", + }; }, + + // TODO: third button "see a sample game" (comp VS comp) + template: `
-
+
+ + +
+
+ +
`, mounted: function() { @@ -28,5 +50,9 @@ Vue.component('my-rules', { shadow: fenParts[3], }; }, + startComputerGame: function() { + this.fen = V.GenRandInitFen(); + this.display = "computer"; + }, }, }) diff --git a/public/javascripts/utils/squareId.js b/public/javascripts/utils/squareId.js new file mode 100644 index 00000000..a3423f7b --- /dev/null +++ b/public/javascripts/utils/squareId.js @@ -0,0 +1,12 @@ +// Get the identifier of a HTML square from its numeric coordinates o.x,o.y. +function getSquareId(o) +{ + // NOTE: a separator is required to allow any size of board + return "sq-" + o.x + "-" + o.y; +} + +// Inverse function +function getSquareFromId(id) { + let idParts = id.split('-'); + return [parseInt(idParts[1]), parseInt(idParts[2])]; +} diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js index c1ba8573..606c1b9a 100644 --- a/public/javascripts/variant.js +++ b/public/javascripts/variant.js @@ -3,23 +3,36 @@ new Vue({ data: { display: "undefined", //default to main hall; see "created()" function gameid: undefined, //...yet - + queryHash: "", conn: null, + // Settings initialized with values from localStorage + settings: { + bcolor: localStorage["bcolor"] || "lichess", + sound: parseInt(localStorage["sound"]) || 2, + hints: parseInt(localStorage["hints"]) || 1, + coords: !!eval(localStorage["coords"]), + highlight: !!eval(localStorage["highlight"]), + sqSize: parseInt(localStorage["sqSize"]), + }, + // TEMPORARY: DEBUG mode: "analyze", orientation: "w", userColor: "w", - allowChat: false, allowMovelist: true, fen: V.GenRandInitFen(), }, created: function() { - // TODO: navigation becomes a little more complex - this.setDisplay(); + if (!!localStorage["variant"]) + { + location.hash = "#game?id=" + localStorage["gameId"]; + this.display = location.hash.substr(1); + } + else + this.setDisplay(); window.onhashchange = this.setDisplay; - this.myid = "abcdefghij"; //console.log(this.myid + " " + variant); //myid: localStorage.getItem("myid"), //our ID, always set @@ -33,45 +46,30 @@ new Vue({ //this.vr = new VariantRules( V.GenRandInitFen() ); }, methods: { + updateSettings: function(event) { + const propName = + event.target.id.substr(3).replace(/^\w/, c => c.toLowerCase()) + localStorage[propName] = ["highlight","coords"].includes(propName) + ? event.target.checked + : event.target.value; + }, setDisplay: function() { - -//TODO: prevent set display if there is a running game - + // Prevent set display if there is a running game + if (!!localStorage["variant"]) + return; if (!location.hash) location.hash = "#room"; //default - this.display = location.hash.substr(1); + const hashParts = location.hash.substr(1).split("?"); + this.display = hashParts[0]; + this.queryHash = hashParts[1]; //may be empty, undefined... // Close menu on small screens: let menuToggle = document.getElementById("drawer-control"); if (!!menuToggle) menuToggle.checked = false; }, - - // TEMPORARY: DEBUG (duplicate code) - play: function(move) { - // Not programmatic, or animation is over - if (!move.notation) - move.notation = this.vr.getNotation(move); - this.vr.play(move); - if (!move.fen) - move.fen = this.vr.getFen(); - if (this.sound == 2) - new Audio("/sounds/move.mp3").play().catch(err => {}); - // Is opponent in check? - this.incheck = this.vr.getCheckSquares(this.vr.turn); - const score = this.vr.getCurrentScore(); - }, - undo: function(move) { - this.vr.undo(move); - if (this.sound == 2) - new Audio("/sounds/undo.mp3").play().catch(err => {}); - this.incheck = this.vr.getCheckSquares(this.vr.turn); - }, }, }); //const continuation = (localStorage.getItem("variant") === variant.name); // if (continuation) //game VS human has priority // this.continueGame("human"); - -// TODO: -// si quand on arrive il y a une continuation "humaine" : display="game" et retour à la partie ! diff --git a/reflexions b/reflexions deleted file mode 100644 index 82e59809..00000000 --- a/reflexions +++ /dev/null @@ -1,7 +0,0 @@ -tell opponent that I got the move, for him to start timer (and lose...) - --> no, not needed and impossible if everybody is offline - ==> just store this time locally (cheating possible but...) -board2, board3, board4 -VariantRules2, 3 et 4 aussi -fetch challenges and corr games from server at startup (room) -but forbid anonymous to start corr games or accept challenges diff --git a/routes/all.js b/routes/all.js index 3e989f77..0989b3fb 100644 --- a/routes/all.js +++ b/routes/all.js @@ -2,10 +2,10 @@ var router = require("express").Router(); router.use("/", require("./index")); router.use("/", require("./users")); -router.use("/", require("./problems")); router.use("/", require("./messages")); -//router.use("/", require("./challenge")); //router.use("/", require("./playing")); +//router.use("/", require("./challenge")); +router.use("/", require("./problems")); router.use("/", require("./variant")); module.exports = router; diff --git a/routes/challenge.js b/routes/challenge.js index 47aaad63..1a4d22b8 100644 --- a/routes/challenge.js +++ b/routes/challenge.js @@ -1,3 +1,5 @@ +// TODO: adapt this (from Mongo to SQLite, and challenge format changed) for corr play + var router = require("express").Router(); var ObjectID = require("bson-objectid"); var ChallengeModel = require('../models/Challenge'); @@ -5,8 +7,6 @@ var UserModel = require('../models/User'); var ObjectID = require("bson-objectid"); var access = require("../utils/access"); -// Only AJAX requests here (from variant page and index) - // variant page router.get("/challengesbyvariant", access.logged, access.ajax, (req,res) => { if (req.query["uid"] != req.user._id) diff --git a/routes/index.js b/routes/index.js index 0ed5b07a..d510068e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,3 +1,5 @@ +// Main index page + let router = require("express").Router(); const VariantModel = require("../models/Variant"); const selectLanguage = require("../utils/language.js"); diff --git a/routes/messages.js b/routes/messages.js index 9a72f5e4..74ec8bd4 100644 --- a/routes/messages.js +++ b/routes/messages.js @@ -1,3 +1,5 @@ +// Router for contact form sending + let router = require("express").Router(); const mailer = require(__dirname.replace("/routes", "/utils/mailer")); diff --git a/routes/playing.js b/routes/playing.js index 7af0a1bc..3bdfa35c 100644 --- a/routes/playing.js +++ b/routes/playing.js @@ -1,3 +1,5 @@ +// TODO: adapt for correspondance play + var router = require("express").Router(); var UserModel = require("../models/User"); var GameModel = require('../models/Game'); diff --git a/routes/problems.js b/routes/problems.js index adb75dae..3434f0cc 100644 --- a/routes/problems.js +++ b/routes/problems.js @@ -6,11 +6,27 @@ const ProblemModel = require("../models/Problem"); const sanitizeHtml = require('sanitize-html'); const MaxNbProblems = 20; -// Get one problem -router.get("/problems/:vname([a-zA-Z0-9]+)/:pnum([0-9]+)", access.ajax, (req,res) => { - const vname = req.params["vname"]; - const pnum = req.params["pnum"]; - ProblemModel.getOne(vname, pnum, (err,problem) => { +function sanitizeUserInput(fen, instructions, solution) +{ + if (!fen.match(/^[a-zA-Z0-9, /-]*$/)) + return "Bad characters in FEN string"; + instructions = sanitizeHtml(instructions); + solution = sanitizeHtml(solution); + if (instructions.length == 0) + return "Empty instructions"; + if (solution.length == 0) + return "Empty solution"; + return { + fen: fen, + instructions: instructions, + solution: solution + }; +} + +// Get one problem (TODO: vid unused, here for URL de-ambiguification) +router.get("/problems/:vid([0-9]+)/:id([0-9]+)", access.ajax, (req,res) => { + const pid = req.params["id"]; + ProblemModel.getOne(pid, (err,problem) => { if (!!err) return res.json(err); return res.json({problem: problem}); @@ -18,8 +34,8 @@ router.get("/problems/:vname([a-zA-Z0-9]+)/:pnum([0-9]+)", access.ajax, (req,res }); // Fetch N previous or next problems -router.get("/problems/:vname([a-zA-Z0-9]+)", access.ajax, (req,res) => { - const vname = req.params["vname"]; +router.get("/problems/:vid([0-9]+)", access.ajax, (req,res) => { + const vid = req.params["vid"]; const directionStr = (req.query.direction == "forward" ? ">" : "<"); const lastDt = req.query.last_dt; const type = req.query.type; @@ -27,7 +43,7 @@ router.get("/problems/:vname([a-zA-Z0-9]+)", access.ajax, (req,res) => { return res.json({errmsg: "Bad timestamp"}); if (!["others","mine"].includes(type)) return res.json({errmsg: "Bad type"}); - ProblemModel.fetchN(vname, req.userId, type, directionStr, lastDt, MaxNbProblems, + ProblemModel.fetchN(vid, req.userId, type, directionStr, lastDt, MaxNbProblems, (err,problems) => { if (!!err) return res.json(err); @@ -36,30 +52,13 @@ router.get("/problems/:vname([a-zA-Z0-9]+)", access.ajax, (req,res) => { ); }); -function sanitizeUserInput(fen, instructions, solution) -{ - if (!fen.match(/^[a-zA-Z0-9, /-]*$/)) - return "Bad characters in FEN string"; - instructions = sanitizeHtml(instructions); - solution = sanitizeHtml(solution); - if (instructions.length == 0) - return "Empty instructions"; - if (solution.length == 0) - return "Empty solution"; - return { - fen: fen, - instructions: instructions, - solution: solution - }; -} - // Upload a problem (sanitize inputs) -router.post("/problems/:vname([a-zA-Z0-9]+)", access.logged, access.ajax, (req,res) => { - const vname = req.params["vname"]; +router.post("/problems/:vid([0-9]+)", access.logged, access.ajax, (req,res) => { + const vid = req.params["vid"]; const s = sanitizeUserInput(req.body["fen"], req.body["instructions"], req.body["solution"]); if (typeof s === "string") return res.json({errmsg: s}); - ProblemModel.create(vname, s.fen, s.instructions, s.solution); + ProblemModel.create(vid, s.fen, s.instructions, s.solution); res.json({}); }); diff --git a/routes/users.js b/routes/users.js index 9639ad58..9c88d08c 100644 --- a/routes/users.js +++ b/routes/users.js @@ -1,3 +1,5 @@ +// AJAX methods to get, create, update or delete a user + var router = require("express").Router(); var UserModel = require('../models/User'); var sendEmail = require('../utils/mailer'); @@ -25,8 +27,6 @@ function setAndSendLoginToken(subject, to, res) }); } -// AJAX user life cycle... - router.post('/register', access.unlogged, access.ajax, (req,res) => { const name = req.body.name; const email = req.body.email; diff --git a/routes/variant.js b/routes/variant.js index f45c9594..cfb63414 100644 --- a/routes/variant.js +++ b/routes/variant.js @@ -1,11 +1,13 @@ +// (any) variant page (with room, games, problems ...) + let router = require("express").Router(); const createError = require('http-errors'); const VariantModel = require("../models/Variant"); const selectLanguage = require("../utils/language.js"); const access = require("../utils/access"); -router.get("/:variant([a-zA-Z0-9]+)", (req,res,next) => { - const vname = req.params["variant"]; +router.get("/:vname([a-zA-Z0-9]+)", (req,res,next) => { + const vname = req.params["vname"]; VariantModel.getByName(vname, (err,variant) => { if (!!err) return next(err); diff --git a/views/modalSettings.pug b/views/modalSettings.pug index 9398a773..3c22c359 100644 --- a/views/modalSettings.pug +++ b/views/modalSettings.pug @@ -1,26 +1,35 @@ input#modalSettings.modal(type="checkbox") div(role="dialog" aria-labelledby="settingsTitle") - .card.smallpad(onChange="blabla(event)") + .card.smallpad(@change="updateSettings") label.modal-close(for="modalSettings") h3#settingsTitle.section= translations["Preferences"] - // taille echiquier : TODO fieldset - label(for="setHints")= translations["Show hints?"] - // TODO: this.hints will not work. Idea: query storage in a generic way ? - // --> getValue("hints") par exemple - input#setHints(type="checkbox" checked=this.hints) + label(for="setSqSize")= translations["Square size (in pixels). 0 for 'adaptative'"] + input#setSqSize(type="number" v-model="settings.sqSize") + fieldset + label(for="selectHints")= translations["Show move hints?"] + select#setHints(v-model="settings.hints") + option(value="0")= translations["None"] + option(value="1")= translations["Moves from a square"] + option(value="2")= translations["Pieces which can move"] + fieldset + label(for="setHighlight")= translations["Highlight squares? (Last move & checks)"] + input#setHighlight(type="checkbox" v-model="settings.highlight") + fieldset + label(for="setCoords")= translations["Show board coordinates?"] + input#setCoords(type="checkbox" v-model="settings.coords") fieldset label(for="selectColor")= translations["Board colors"] - select#selectColor - option(value="lichess" selected="this.color=='lichess'") + select#setBcolor(v-model="settings.bcolor") + option(value="lichess") = translations["brown"] - option(value="chesscom" selected="this.color=='chesscom'") + option(value="chesscom") = translations["green"] - option(value="chesstempo" selected="this.color=='chesstempo'") + option(value="chesstempo") = translations["blue"] fieldset label(for="selectSound")= translations["Play sounds?"] - select#selectSound - option(value="0" selected="this.sound==0")= translations["None"] - option(value="1" selected="this.sound==1")= translations["New game"] - option(value="2" selected="this.sound==2")= translations["All"] + select#setSound(v-model="settings.sound") + option(value="0")= translations["None"] + option(value="1")= translations["New game"] + option(value="2")= translations["All"] diff --git a/views/variant.pug b/views/variant.pug index 7d091909..89872ffb 100644 --- a/views/variant.pug +++ b/views/variant.pug @@ -30,11 +30,11 @@ block content .row //my-room(v-show="display=='room'") //my-game-list(v-show="display=='gameList'") - my-rules(v-show="display=='rules'") - //my-problems(v-show="display=='problems'") + my-rules(v-show="display=='rules'" :settings="settings") + //my-problems(v-show="display=='problems'" :query-hash="queryHash") my-game(v-show="display=='game'" :game-id="gameid" :conn="conn" :allow-chat="allowChat" :allow-movelist="allowMovelist" - :mode="mode" :fen="fen") + :mode="mode" :fen="fen" :query-hash="queryHash") //my-board(:vr="vr" :mode="mode" :orientation="orientation" :user-color="userColor" v-on:play-move="play") @@ -42,6 +42,7 @@ block javascripts script(src="/javascripts/utils/array.js") script(src="/javascripts/utils/printDiagram.js") script(src="/javascripts/utils/datetime.js") + script(src="/javascripts/utils/squareId.js") script(src="/javascripts/socket_url.js") script(src="/javascripts/base_rules.js") script(src="/javascripts/settings.js")