Computer mode in rules section almost OK
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 15 Jan 2019 00:25:23 +0000 (01:25 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 15 Jan 2019 00:25:23 +0000 (01:25 +0100)
20 files changed:
_tmp/TODO [moved from TODO with 67% similarity]
_tmp/hexaboard_test.html [moved from hexaboard_test.html with 76% similarity]
models/Problem.js
public/javascripts/components/board.js
public/javascripts/components/game.js
public/javascripts/components/problems.js
public/javascripts/components/rules.js
public/javascripts/utils/squareId.js [new file with mode: 0644]
public/javascripts/variant.js
reflexions [deleted file]
routes/all.js
routes/challenge.js
routes/index.js
routes/messages.js
routes/playing.js
routes/problems.js
routes/users.js
routes/variant.js
views/modalSettings.pug
views/variant.pug

diff --git a/TODO b/_tmp/TODO
similarity index 67%
rename from TODO
rename to _tmp/TODO
index 7ea4813..cb4b8a9 100644 (file)
--- 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
 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
 
 
 // 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)
 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 ?)
 
 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:
 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)
 ==> 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)
 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")
 
 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).
 
 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")
 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"
 
 
 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)
 
 
 Mode contre ordinateur : seulement accessible depuis onglet "Rules" (son principal intérêt)
 
similarity index 76%
rename from hexaboard_test.html
rename to _tmp/hexaboard_test.html
index 800e189..86abc2e 100644 (file)
@@ -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";
 
 var img = new Image();
 img.onload = function() {
            ctx.drawImage(img, 0, 0, 60, 60);
 }
 img.src = "public/images/pieces/wb.svg";
 
-       
 </script>
 
 </body>
 </script>
 
 </body>
index 7858676..7ac92f7 100644 (file)
@@ -11,50 +11,34 @@ var db = require("../utils/database");
  */
 
 // TODO: callback ?
  */
 
 // 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 =
 {
        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 " +
 {
        db.serialize(function() {
                const query =
                        "SELECT * " +
                        "FROM Problems " +
-                       "WHERE id = " + pid;
+                       "WHERE id = " + id;
                db.get(query, callback);
        });
 }
 
                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 = "";
 {
        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 " +
                        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;
                        "  AND added " + directionStr + " " + lastDt + " " + typeLine + " " +
                        "ORDER BY added " + (directionStr=="<" ? "DESC " : "") +
                        "LIMIT " + MaxNbProblems;
index 0399410..5a75f29 100644 (file)
@@ -142,7 +142,7 @@ Vue.component('my-board', {
                                                                        'incheck': showLight && incheckSq[ci][cj],
                                                                },
                                                                attrs: {
                                                                        'incheck': showLight && incheckSq[ci][cj],
                                                                },
                                                                attrs: {
-                                                                       id: this.getSquareId({x:ci,y:cj}),
+                                                                       id: getSquareId({x:ci,y:cj}),
                                                                },
                                                        },
                                                        elems
                                                                },
                                                        },
                                                        elems
@@ -161,7 +161,7 @@ Vue.component('my-board', {
                                myReservePiecesArray.push(h('div',
                                {
                                        'class': {'board':true, ['board'+sizeY]:true},
                                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',
                                },
                                [
                                        h('img',
@@ -185,7 +185,7 @@ Vue.component('my-board', {
                                oppReservePiecesArray.push(h('div',
                                {
                                        'class': {'board':true, ['board'+sizeY]:true},
                                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',
                                },
                                [
                                        h('img',
@@ -251,16 +251,6 @@ Vue.component('my-board', {
                );
        },
        methods: {
                );
        },
        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;
                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;
                                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
                                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
                                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)
                        let moves = this.findMatchingMoves(endSquare);
                        this.possibleMoves = [];
                        if (moves.length > 1)
index edd17c4..c411b09 100644 (file)
@@ -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)
 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?
        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
                        // 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",
 
                        endgameMessage: "",
                        orientation: "w",
 
-                       // if oppid == "computer" then mode = "computer" (otherwise human)
                        oppid: "", //opponent ID in case of HH game
                        score: "*", //'*' means 'unfinished'
                        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...
                        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);
        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();
                },
                },
                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() {
        },
        computed: {
                showChat: function() {
@@ -65,7 +82,9 @@ Vue.component('my-game', {
                        </div>
                        <my-chat v-if="showChat">
                        </my-chat>
                        </div>
                        <my-chat v-if="showChat">
                        </my-chat>
-                       <my-board v-bind:vr="vr" :last-move="lastMove" :mode="mode" :orientation="orientation" :user-color="mycolor" @play-move="play">
+                       <my-board v-bind:vr="vr" :last-move="lastMove" :mode="mode"
+                               :orientation="orientation" :user-color="mycolor" :settings="settings"
+                               @play-move="play">
                        </my-board>
                        <div class="button-group">
                                <button @click="() => play()">Play</button>
                        </my-board>
                        <div class="button-group">
                                <button @click="() => play()">Play</button>
@@ -175,8 +194,11 @@ Vue.component('my-game', {
                        this.conn.addEventListener('message', socketMessageListener);
                        this.conn.addEventListener('close', socketCloseListener);
                };
                        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]);
 
                // 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) {
                        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 =
                        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<squares.length; i++)
                        {
                                let square = squares.item(i);
                        // 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<squares.length; i++)
                        {
                                let square = squares.item(i);
-                               if (square.id != this.getSquareId(move.start))
+                               if (square.id != getSquareId(move.start))
                                        square.style.zIndex = "-1";
                        }
                        movingPiece.style.transform = "translate(" + translation.x + "px," +
                                        square.style.zIndex = "-1";
                        }
                        movingPiece.style.transform = "translate(" + translation.x + "px," +
@@ -368,12 +390,12 @@ Vue.component('my-game', {
                        this.lastMove = move;
                        if (!move.fen)
                                move.fen = this.vr.getFen();
                        this.lastMove = move;
                        if (!move.fen)
                                move.fen = this.vr.getFen();
-                       if (this.sound == 2)
+                       if (this.settings.sound == 2)
                                new Audio("/sounds/move.mp3").play().catch(err => {});
                        if (this.mode == "human")
                        {
                                updateStorage(move); //after our moves and opponent moves
                                new Audio("/sounds/move.mp3").play().catch(err => {});
                        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")
                                        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)
                        }
                                        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)
                                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!?
                        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")
                                new Audio("/sounds/undo.mp3").play().catch(err => {});
                        this.incheck = this.vr.getCheckSquares(this.vr.turn);
                        if (!navigate && this.mode == "analyze")
index cf2cd07..00ea769 100644 (file)
@@ -1,4 +1,5 @@
 Vue.component('my-problems', {
 Vue.component('my-problems', {
+       props: ["queryHash","settings"],
        data: function () {
                return {
                        userId: user.id,
        data: function () {
                return {
                        userId: user.id,
@@ -38,7 +39,7 @@ Vue.component('my-problems', {
                                                {{ curProb.instructions }}
                                        </p>
                                </div>
                                                {{ curProb.instructions }}
                                        </p>
                                </div>
-                               <my-game :fen="curProb.fen" :mode="analyze" :allowMovelist="true">
+                               <my-game :fen="curProb.fen" :mode="analyze" :allowMovelist="true" :settings="settings">
                                </my-board>
                                <div id="solution-div" class="section-content">
                                        <h3 class="clickable" @click="showSolution = !showSolution">
                                </my-board>
                                <div id="solution-div" class="section-content">
                                        <h3 class="clickable" @click="showSolution = !showSolution">
@@ -128,10 +129,24 @@ Vue.component('my-problems', {
                        </div>
                </div>
        `,
                        </div>
                </div>
        `,
+       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() {
        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();
        },
                else
                        this.firstFetch();
        },
@@ -249,7 +264,7 @@ Vue.component('my-problems', {
                                }
                        }
                        ajax(
                                }
                        }
                        ajax(
-                               "/problems/" + variant.name, //TODO: use variant._id ?
+                               "/problems/" + variant.id,
                                "GET",
                                {
                                        type: type,
                                "GET",
                                {
                                        type: type,
@@ -284,7 +299,7 @@ Vue.component('my-problems', {
                },
                deleteProblem: function(pid) {
                        ajax(
                },
                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
                                "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(
                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 => {
                                (this.modalProb.id > 0 ? "PUT" : "POST"),
                                this.modalProb,
                                response => {
index 02d3a0c..e9df1ec 100644 (file)
@@ -1,11 +1,33 @@
 // Load rules on variant page
 Vue.component('my-rules', {
 // Load rules on variant page
 Vue.component('my-rules', {
+       props: ["settings"],
        data: function() {
        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: `
                <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
        template: `
                <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
-                       <div v-html="content" class="section-content"></div>
+                       <div class="button-group">
+                               <button @click="display='rules'">
+                                       Read the rules
+                               </button>
+                               <button @click="startComputerGame()">
+                                       Beat the computer!
+                               </button>
+                       </div>
+                       <div v-show="display=='rules'" v-html="content" class="section-content"></div>
+                       <my-game v-show="display=='computer'" :mycolor="mycolor" :settings="settings"
+                               :allow-movelist="allowMovelist" :mode="mode" :fen="fen">
+                       </my-game>
                </div>
        `,
        mounted: function() {
                </div>
        `,
        mounted: function() {
@@ -28,5 +50,9 @@ Vue.component('my-rules', {
                                shadow: fenParts[3],
                        };
                },
                                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 (file)
index 0000000..a3423f7
--- /dev/null
@@ -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])];
+}
index c1ba857..606c1b9 100644 (file)
@@ -3,23 +3,36 @@ new Vue({
        data: {
                display: "undefined", //default to main hall; see "created()" function
                gameid: undefined, //...yet
        data: {
                display: "undefined", //default to main hall; see "created()" function
                gameid: undefined, //...yet
-       
+               queryHash: "",
                conn: null,
 
                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",
                // TEMPORARY: DEBUG
                mode: "analyze",
                orientation: "w",
                userColor: "w",
-
                allowChat: false,
                allowMovelist: true,
                fen: V.GenRandInitFen(),
        },
        created: function() {
                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;
                window.onhashchange = this.setDisplay;
-
                this.myid = "abcdefghij";
 //console.log(this.myid + " " + variant);
                        //myid: localStorage.getItem("myid"), //our ID, always set
                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: {
                //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() {
                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
                        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;
                },
                        // 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");
        },
 });
                
 //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 (file)
index 82e5980..0000000
+++ /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
index 3e989f7..0989b3f 100644 (file)
@@ -2,10 +2,10 @@ var router = require("express").Router();
 
 router.use("/", require("./index"));
 router.use("/", require("./users"));
 
 router.use("/", require("./index"));
 router.use("/", require("./users"));
-router.use("/", require("./problems"));
 router.use("/", require("./messages"));
 router.use("/", require("./messages"));
-//router.use("/", require("./challenge"));
 //router.use("/", require("./playing"));
 //router.use("/", require("./playing"));
+//router.use("/", require("./challenge"));
+router.use("/", require("./problems"));
 router.use("/", require("./variant"));
 
 module.exports = router;
 router.use("/", require("./variant"));
 
 module.exports = router;
index 47aaad6..1a4d22b 100644 (file)
@@ -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');
 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");
 
 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)
 // variant page
 router.get("/challengesbyvariant", access.logged, access.ajax, (req,res) => {
        if (req.query["uid"] != req.user._id)
index 0ed5b07..d510068 100644 (file)
@@ -1,3 +1,5 @@
+// Main index page
+
 let router = require("express").Router();
 const VariantModel = require("../models/Variant");
 const selectLanguage = require("../utils/language.js");
 let router = require("express").Router();
 const VariantModel = require("../models/Variant");
 const selectLanguage = require("../utils/language.js");
index 9a72f5e..74ec8bd 100644 (file)
@@ -1,3 +1,5 @@
+// Router for contact form sending
+
 let router = require("express").Router();
 const mailer = require(__dirname.replace("/routes", "/utils/mailer"));
 
 let router = require("express").Router();
 const mailer = require(__dirname.replace("/routes", "/utils/mailer"));
 
index 7af0a1b..3bdfa35 100644 (file)
@@ -1,3 +1,5 @@
+// TODO: adapt for correspondance play
+
 var router = require("express").Router();
 var UserModel = require("../models/User");
 var GameModel = require('../models/Game');
 var router = require("express").Router();
 var UserModel = require("../models/User");
 var GameModel = require('../models/Game');
index adb75da..3434f0c 100644 (file)
@@ -6,11 +6,27 @@ const ProblemModel = require("../models/Problem");
 const sanitizeHtml = require('sanitize-html');
 const MaxNbProblems = 20;
 
 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});
                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
 });
 
 // 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;
        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"});
                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);
                (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)
 // 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});
        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({});
 });
 
        res.json({});
 });
 
index 9639ad5..9c88d08 100644 (file)
@@ -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');
 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;
 router.post('/register', access.unlogged, access.ajax, (req,res) => {
        const name = req.body.name;
        const email = req.body.email;
index f45c959..cfb6341 100644 (file)
@@ -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");
 
 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);
        VariantModel.getByName(vname, (err,variant) => {
                if (!!err)
                        return next(err);
index 9398a77..3c22c35 100644 (file)
@@ -1,26 +1,35 @@
 input#modalSettings.modal(type="checkbox")
 div(role="dialog" aria-labelledby="settingsTitle")
 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"]
                label.modal-close(for="modalSettings")
                h3#settingsTitle.section= translations["Preferences"]
-               // taille echiquier : TODO
                fieldset
                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"]
                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"]
                                        = translations["brown"]
-                               option(value="chesscom" selected="this.color=='chesscom'")
+                               option(value="chesscom")
                                        = translations["green"]
                                        = translations["green"]
-                               option(value="chesstempo" selected="this.color=='chesstempo'")
+                               option(value="chesstempo")
                                        = translations["blue"]
                fieldset
                        label(for="selectSound")= translations["Play sounds?"]
                                        = 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"]
index 7d09190..89872ff 100644 (file)
@@ -30,11 +30,11 @@ block content
                .row
                        //my-room(v-show="display=='room'")
                        //my-game-list(v-show="display=='gameList'")
                .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"
                        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")
 
                        //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/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")
        script(src="/javascripts/socket_url.js")
        script(src="/javascripts/base_rules.js")
        script(src="/javascripts/settings.js")