Start server implementation for correspondance play (early debug stage)
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 18 Jan 2019 18:24:27 +0000 (19:24 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 18 Jan 2019 18:24:27 +0000 (19:24 +0100)
38 files changed:
_tmp/TODO
db/create.sql
models/Challenge.js
models/Game.js
models/Problem.js
models/User.js
models/Variant.js
public/javascripts/components/correspondance.js
public/javascripts/components/game.js
public/javascripts/components/gameList.js
public/javascripts/components/room.js
public/javascripts/playCompMove.js
public/javascripts/shared/challengeCheck.js [new file with mode: 0644]
public/javascripts/shared/nbPlayers.js [new file with mode: 0644]
public/javascripts/utils/printDiagram.js
public/javascripts/variants/Alice.js
public/javascripts/variants/Antiking.js
public/javascripts/variants/Atomic.js
public/javascripts/variants/Baroque.js
public/javascripts/variants/Berolina.js
public/javascripts/variants/Checkered.js
public/javascripts/variants/Chess960.js
public/javascripts/variants/Crazyhouse.js
public/javascripts/variants/Dark.js
public/javascripts/variants/Extinction.js
public/javascripts/variants/Grand.js
public/javascripts/variants/Losers.js
public/javascripts/variants/Magnetic.js
public/javascripts/variants/Marseille.js
public/javascripts/variants/Switching.js
public/javascripts/variants/Upsidedown.js
public/javascripts/variants/Wildebeest.js
public/javascripts/variants/Zen.js
routes/all.js
routes/challenge.js
views/index.pug
views/layout.pug
views/variant.pug

index 868d4d3..0717e49 100644 (file)
--- a/_tmp/TODO
+++ b/_tmp/TODO
@@ -1,6 +1,17 @@
 Use better-sqlite3 instead of node-sqlite3:
 https://www.npmjs.com/package/better-sqlite3
 
+Canvas for hexagonal board Vue reactivity :
+https://stackoverflow.com/questions/40177493/drawing-onto-a-canvas-with-vue-js
+custom directives ?
+
+Desktop notifications:
+https://developer.mozilla.org/fr/docs/Web/API/notification
+
+Think about this:
+https://alligator.io/vuejs/component-communication/
+https://alligator.io/vuejs/global-event-bus/
+
 CRON task remove unlogged users, finished corr games after 7 days, individual challenges older than 7 days
 
 tell opponent that I got the move, for him to start timer (and lose...)
@@ -73,3 +84,7 @@ Mode contre ordinateur : seulement accessible depuis onglet "Rules" (son princip
 Hexachess: McCooey et Shafran (deux tailles, randomisation OK)
 http://www.math.bas.bg/~iad/tyalie/shegra/shegrax.html
 http://www.quadibloc.com/chess/ch0401.htm
+
+Inspiration for refactor:
+https://github.com/triestpa/Vue-Chess/blob/master/src/components/chessboard/chessboard.js
+https://github.com/gustaYo/vue-chess
index 75e1b1c..936992c 100644 (file)
@@ -37,6 +37,9 @@ create table Challenges (
        uid integer,
        vid integer,
        nbPlayers integer,
+       fen varchar,
+       mainTime integer,
+       addTime integer,
        foreign key (uid) references Users(id),
        foreign key (vid) references Variants(id)
 );
@@ -52,8 +55,11 @@ create table WillPlay (
 create table Games (
        id integer primary key,
        vid integer,
-       fen varchar, --initial position
-       score varchar default '*',
+       fenStart varchar, --initial state
+       fen varchar, --current state
+       score varchar,
+       mainTime integer,
+       addTime integer,
        foreign key (vid) references Variants(id)
 );
 
@@ -62,6 +68,7 @@ create table Players (
        gid integer,
        uid integer,
        color character,
+       rtime integer, --remaining time in milliseconds
        foreign key (gid) references Games(id),
        foreign key (uid) references Users(id)
 );
@@ -69,6 +76,7 @@ create table Players (
 create table Moves (
        gid integer,
        move varchar,
+       message varchar,
        played datetime, --when was this move played?
        idx integer, --index of the move in the game
        color character, --required for e.g. Marseillais Chess
index adb4022..e945daa 100644 (file)
@@ -7,76 +7,113 @@ var db = require("../utils/database");
  *   uid: user id (int)
  *   vid: variant id (int)
  *   nbPlayers: integer
+ *   fen: varchar (optional)
+ *   mainTime: integer
+ *   addTime: integer
  *
  * Structure table WillPlay:
  *   cid: ref challenge id
  *   uid: ref user id
  */
 
-exports.create = function(uid, vid, nbPlayers, cb)
+const ChallengeModel =
 {
-       db.serialize({
-               let query =
-                       "INSERT INTO Challenges (added, uid, vid, nbPlayers) " +
-                       "VALUES (" + Date.now() + "," + uid + "," + vid + "," + nbPlayers + ")";
-               db.run(insertQuery, err => {
-                       if (!!err)
-                               return cb(err);
-                       db.get("SELECT last_insert_rowid() AS rowid", (err2,lastId) => {
+       // fen cannot be undefined; TODO: generate fen on server instead
+       create: function(c, cb)
+       {
+               db.serialize(function() {
+                       let query =
+                               "INSERT INTO Challenges (added, uid, vid, nbPlayers, fen, mainTime, addTime) " +
+                               "VALUES (" + Date.now() + "," + c.uid + "," + c.vid + "," + c.nbPlayers + "," +
+                                       c.fen + "," + c.mainTime + "," + c.increment + ")";
+                       db.run(query, err => {
+                               if (!!err)
+                                       return cb(err);
+                               db.get("SELECT last_insert_rowid() AS rowid", (err2,lastId) => {
+                                       query =
+                                               "INSERT INTO WillPlay VALUES " +
+                                               "(" + lastId["rowid"] + "," + uid + ")";
+                                               db.run(query, (err,ret) => {
+                                                       cb(err, lastId); //all we need is the challenge ID
+                                               });
+                               });
+                       });
+               });
+       },
+
+       getOne: function(id, cb)
+       {
+               db.serialize(function() {
+                       let query =
+                               "SELECT * " +
+                               "FROM Challenges c " +
+                               "JOIN Variants v " +
+                               "  ON c.vid = v.id "
+                               "WHERE id = " + id;
+                       db.get(query, (err,challengeInfo) => {
+                               if (!!err)
+                                       return cb(err);
                                query =
-                                       "INSERT INTO WillPlay VALUES " +
-                                       "(" + lastId["rowid"] + "," + uid + ")";
-                                       db.run(query, cb);
+                                       "SELECT w.uid AS id, u.name " +
+                                       "FROM WillPlay w " +
+                                       "JOIN Users u " +
+                                       "  ON w.uid = u.id " +
+                                       "WHERE w.cid = " + id;
+                               db.run(query, (err2,players) => {
+                                       if (!!err2)
+                                               return cb(err2);
+                                       const challenge = {
+                                               id: id,
+                                               vname: challengeInfo.name,
+                                               added: challengeInfo.added,
+                                               nbPlayers: challengeInfo.nbPlayers,
+                                               players: players, //currently in
+                                               fen: challengeInfo.fen,
+                                               mainTime: challengeInfo.mainTime,
+                                               increment: challengeInfo.addTime,
+                                       };
+                                       return cb(null, challenge);
                                });
                        });
                });
-       });
-}
+       },
 
-exports.getOne = function(id, cb)
-{
-       db.serialize(function() {
-               let query =
-                       "SELECT * " +
-                       "FROM Challenges c " +
-                       "JOIN Variants v " +
-                       "  ON c.vid = v.id "
-                       "WHERE id = " + id;
-               db.get(query, (err,challengeInfo) => {
-                       if (!!err)
-                               return cb(err);
-                       query =
-                               "SELECT w.uid AS id, u.name " +
-                               "FROM WillPlay w " +
-                               "JOIN Users u " +
-                               "  ON w.uid = u.id " +
-                               "WHERE w.cid = " + id;
-                       db.run(query, (err2,players) => {
-                               if (!!err2)
-                                       return cb(err2);
-                               const challenge = {
-                                       id: id,
-                                       vname: challengeInfo.name,
-                                       added: challengeInfo.added,
-                                       nbPlayers: challengeInfo.nbPlayers,
-                                       players: players, //currently in
-                               };
-                               return cb(null, challenge);
+       getByUser: function(uid, cb)
+       {
+               db.serialize(function() {
+                       const query =
+                               "SELECT cid " +
+                               "FROM WillPlay " +
+                               "WHERE uid = " + uid;
+                       db.run(query, (err,challIds) => {
+                               if (!!err)
+                                       return cb(err);
+                               let challenges = [];
+                               challIds.forEach(cidRow => {
+                                       ChallengeModel.getOne(cidRow["cid"], (err2,chall) => {
+                                               if (!!err2)
+                                                       return cb(err2);
+                                               challenges.push(chall);
+                                       });
+                               });
+                               return cb(null, challenges);
                        });
                });
-       });
-}
+       },
 
-exports.remove = function(id)
-{
-       db.parallelize(function() {
-               let query =
-                       "DELETE FROM Challenges " +
-                       "WHERE id = " + id;
-               db.run(query);
-               query =
-                       "DELETE FROM WillPlay " +
-                       "WHERE cid = " + id;
-               db.run(query);
-       });
+       remove: function(id)
+       {
+               db.parallelize(function() {
+                       let query =
+                               "DELETE FROM Challenges " +
+                               "WHERE id = " + id;
+                       db.run(query);
+                       query =
+                               "DELETE FROM WillPlay " +
+                               "WHERE cid = " + id;
+                       db.run(query);
+               });
+       },
 }
+
+module.exports = ChallengeModel;
index 72f1ea8..23a74e8 100644 (file)
@@ -4,102 +4,137 @@ var db = require("../utils/database");
  * Structure table Games:
  *   id: game id (int)
  *   vid: integer (variant id)
- *   fen: varchar (initial position)
+ *   fenStart: varchar (initial position)
+ *   fen: varchar (current position)
  *   mainTime: integer
- *   increment: integer
+ *   addTime: integer (increment)
  *   score: varchar (result)
  *
  * Structure table Players:
  *   gid: ref game id
  *   uid: ref user id
  *   color: character
+ *   rtime: real (remaining time)
  *
  * Structure table Moves:
+ *   gid: ref game id
  *   move: varchar (description)
+ *   message: text
  *   played: datetime
  *   idx: integer
  *   color: character
  */
 
-exports.create = function(vid, fen, mainTime, increment, players, cb)
+const GameModel =
 {
-       db.serialize({
-               let query =
-                       "INSERT INTO Games (vid, fen, mainTime, increment) " +
-                       "VALUES (" + vid + ",'" + fen + "'," + mainTime + "," + increment + ")";
-               db.run(insertQuery, err => {
-                       if (!!err)
-                               return cb(err);
-                       db.get("SELECT last_insert_rowid() AS rowid", (err2,lastId) => {
-                               players.forEach(p => {
-                                       query =
-                                               "INSERT INTO Players VALUES " +
-                                               "(" + lastId["rowid"] + "," + p.id + "," + p.color + ")";
-                                       db.run(query, cb);
+       // mainTime and increment in milliseconds
+       create: function(vid, fen, mainTime, increment, players, cb)
+       {
+               db.serialize({
+                       let query =
+                               "INSERT INTO Games (vid, fen, mainTime, addTime) " +
+                               "VALUES (" + vid + ",'" + fen + "'," + mainTime + "," + increment + ")";
+                       db.run(insertQuery, err => {
+                               if (!!err)
+                                       return cb(err);
+                               db.get("SELECT last_insert_rowid() AS rowid", (err2,lastId) => {
+                                       players.forEach(p => {
+                                               query =
+                                                       "INSERT INTO Players VALUES " +
+                                                       "(" + lastId["rowid"] + "," + p.id + "," + p.color + "," + mainTime + ")";
+                                               db.run(query, cb);
+                                       });
                                });
                        });
                });
-       });
-}
+       },
 
-// TODO: queries here could be async, and wait for all to complete
-exports.getOne = function(id, cb)
-{
-       db.serialize(function() {
-               let query =
-                       "SELECT v.name AS vname, g.fen, g.score " +
-                       "FROM Games g " +
-                       "JOIN Variants v " +
-                       "  ON g.vid = v.id "
-                       "WHERE id = " + id;
-               db.get(query, (err,gameInfo) => {
-                       if (!!err)
-                               return cb(err);
-                       query =
-                               "SELECT p.uid AS id, p.color, u.name " +
-                               "FROM Players p " +
-                               "JOIN Users u " +
-                               "  ON p.uid = u.id " +
-                               "WHERE p.gid = " + id;
-                       db.run(query, (err2,players) => {
-                               if (!!err2)
-                                       return cb(err2);
+       // TODO: queries here could be async, and wait for all to complete
+       getOne: function(id, cb)
+       {
+               db.serialize(function() {
+                       let query =
+                               "SELECT v.name AS vname, g.fen, g.fenStart, g.score " +
+                               "FROM Games g " +
+                               "JOIN Variants v " +
+                               "  ON g.vid = v.id "
+                               "WHERE id = " + id;
+                       db.get(query, (err,gameInfo) => {
+                               if (!!err)
+                                       return cb(err);
                                query =
-                                       "SELECT move AS desc, played, idx, color " +
-                                       "FROM Moves " +
-                                       "WHERE gid = " + id;
-                               db.run(query, (err3,moves) => {
-                                       if (!!err3)
-                                               return cb(err3);
-                                       const game = {
-                                               id: id,
-                                               vname: gameInfo.vname,
-                                               fen: gameInfo.fen,
-                                               score: gameInfo.score,
-                                               players: players,
-                                               moves: moves,
-                                       };
-                                       return cb(null, game);
+                                       "SELECT p.uid AS id, p.color, p.rtime, u.name " +
+                                       "FROM Players p " +
+                                       "JOIN Users u " +
+                                       "  ON p.uid = u.id " +
+                                       "WHERE p.gid = " + id;
+                               db.run(query, (err2,players) => {
+                                       if (!!err2)
+                                               return cb(err2);
+                                       query =
+                                               "SELECT move AS desc, message, played, idx, color " +
+                                               "FROM Moves " +
+                                               "WHERE gid = " + id;
+                                       db.run(query, (err3,moves) => {
+                                               if (!!err3)
+                                                       return cb(err3);
+                                               const game = {
+                                                       id: id,
+                                                       vname: gameInfo.vname,
+                                                       fenStart: gameInfo.fenStart,
+                                                       fen: gameInfo.fen,
+                                                       score: gameInfo.score,
+                                                       players: players,
+                                                       moves: moves,
+                                               };
+                                               return cb(null, game);
+                                       });
                                });
                        });
                });
-       });
-}
+       },
 
-exports.remove = function(id)
-{
-       db.parallelize(function() {
-               let query =
-                       "DELETE FROM Games " +
-                       "WHERE id = " + id;
-               db.run(query);
-               query =
-                       "DELETE FROM Players " +
-                       "WHERE gid = " + id;
-               db.run(query);
-               query =
-                       "DELETE FROM Moves " +
-                       "WHERE gid = " + id;
-               db.run(query);
-       });
+       getByUser: function(uid, cb)
+       {
+               db.serialize(function() {
+                       // Next query is fine because a player appear at most once in a game
+                       const query =
+                               "SELECT gid " +
+                               "FROM Players " +
+                               "WHERE uid = " + uid;
+                       db.run(query, (err,gameIds) => {
+                               if (!!err)
+                                       return cb(err);
+                               let gameArray = [];
+                               gameIds.forEach(gidRow => {
+                                       GameModel.getOne(gidRow["gid"], (err2,game) => {
+                                               if (!!err2)
+                                                       return cb(err2);
+                                               gameArray.push(game);
+                                       });
+                               });
+                               return cb(null, gameArray);
+                       });
+               });
+       },
+
+       remove: function(id)
+       {
+               db.parallelize(function() {
+                       let query =
+                               "DELETE FROM Games " +
+                               "WHERE id = " + id;
+                       db.run(query);
+                       query =
+                               "DELETE FROM Players " +
+                               "WHERE gid = " + id;
+                       db.run(query);
+                       query =
+                               "DELETE FROM Moves " +
+                               "WHERE gid = " + id;
+                       db.run(query);
+               });
+       },
 }
+
+module.exports = GameModel;
index 8f3a302..a1f9903 100644 (file)
@@ -10,67 +10,72 @@ var db = require("../utils/database");
  *   solution: text
  */
 
-exports.create = function(uid, vid, fen, instructions, solution, cb)
+const ProblemModel =
 {
-       db.serialize(function() {
-               const insertQuery =
-                       "INSERT INTO Problems (added, uid, vid, fen, instructions, solution) " +
-                       "VALUES (" + Date.now() + "," + uid + "," + vid + ",'" + fen + "',?,?)";
-               db.run(insertQuery, [instructions, solution], err => {
-                       if (!!err)
-                               return cb(err);
-                       db.get("SELECT last_insert_rowid() AS rowid", cb);
+       create: function(uid, vid, fen, instructions, solution, cb)
+       {
+               db.serialize(function() {
+                       const insertQuery =
+                               "INSERT INTO Problems (added, uid, vid, fen, instructions, solution) " +
+                               "VALUES (" + Date.now() + "," + uid + "," + vid + ",'" + fen + "',?,?)";
+                       db.run(insertQuery, [instructions, solution], err => {
+                               if (!!err)
+                                       return cb(err);
+                               db.get("SELECT last_insert_rowid() AS rowid", cb);
+                       });
                });
-       });
-}
+       },
 
-exports.getOne = function(id, callback)
-{
-       db.serialize(function() {
-               const query =
-                       "SELECT * " +
-                       "FROM Problems " +
-                       "WHERE id = " + id;
-               db.get(query, callback);
-       });
-}
+       getOne: function(id, callback)
+       {
+               db.serialize(function() {
+                       const query =
+                               "SELECT * " +
+                               "FROM Problems " +
+                               "WHERE id = " + id;
+                       db.get(query, callback);
+               });
+       },
 
-exports.fetchN = function(vid, uid, type, directionStr, lastDt, MaxNbProblems, callback)
-{
-       db.serialize(function() {
-               let typeLine = "";
-               if (uid > 0)
-                       typeLine = "AND uid " + (type=="others" ? "!=" : "=") + " " + uid;
-               const query =
-                       "SELECT * FROM Problems " +
-                       "WHERE vid = " + vid +
-                       "  AND added " + directionStr + " " + lastDt + " " + typeLine + " " +
-                       "ORDER BY added " + (directionStr=="<" ? "DESC " : "") +
-                       "LIMIT " + MaxNbProblems;
-               db.all(query, callback);
-       });
-}
+       fetchN: function(vid, uid, type, directionStr, lastDt, MaxNbProblems, callback)
+       {
+               db.serialize(function() {
+                       let typeLine = "";
+                       if (uid > 0)
+                               typeLine = "AND uid " + (type=="others" ? "!=" : "=") + " " + uid;
+                       const query =
+                               "SELECT * FROM Problems " +
+                               "WHERE vid = " + vid +
+                               "  AND added " + directionStr + " " + lastDt + " " + typeLine + " " +
+                               "ORDER BY added " + (directionStr=="<" ? "DESC " : "") +
+                               "LIMIT " + MaxNbProblems;
+                       db.all(query, callback);
+               });
+       },
 
-// TODO: update fails (but insert is OK)
-exports.update = function(id, uid, fen, instructions, solution, cb)
-{
-       db.serialize(function() {
-               const query =
-                       "UPDATE Problems SET " +
-                               "fen = '" + fen + "', " +
-                               "instructions = ?, " +
-                               "solution = ? " +
-                       "WHERE id = " + id + " AND uid = " + uid;
-               db.run(query, [instructions,solution], cb);
-       });
-}
+       // TODO: update fails (but insert is OK)
+       update: function(id, uid, fen, instructions, solution, cb)
+       {
+               db.serialize(function() {
+                       const query =
+                               "UPDATE Problems SET " +
+                                       "fen = '" + fen + "', " +
+                                       "instructions = ?, " +
+                                       "solution = ? " +
+                               "WHERE id = " + id + " AND uid = " + uid;
+                       db.run(query, [instructions,solution], cb);
+               });
+       },
 
-exports.remove = function(id, uid)
-{
-       db.serialize(function() {
-               const query =
-                       "DELETE FROM Problems " +
-                       "WHERE id = " + id + " AND uid = " + uid;
-               db.run(query);
-       });
+       remove: function(id, uid)
+       {
+               db.serialize(function() {
+                       const query =
+                               "DELETE FROM Problems " +
+                               "WHERE id = " + id + " AND uid = " + uid;
+                       db.run(query);
+               });
+       },
 }
+
+module.exports = ProblemModel;
index 4b5c840..a36ab68 100644 (file)
@@ -14,83 +14,88 @@ var params = require("../config/parameters");
  *   notify: boolean (send email notifications for corr games)
  */
 
-// NOTE: parameters are already cleaned (in controller), thus no sanitization here
-exports.create = function(name, email, notify, callback)
+const UserModel =
 {
-       db.serialize(function() {
-               const insertQuery =
-                       "INSERT INTO Users " +
-                       "(name, email, notify) VALUES " +
-                       "('" + name + "', '" + email + "', " + notify + ")";
-               db.run(insertQuery, err => {
-                       if (!!err)
-                               return callback(err);
-                       db.get("SELECT last_insert_rowid() AS rowid", callback);
+       // NOTE: parameters are already cleaned (in controller), thus no sanitization here
+       create: function(name, email, notify, callback)
+       {
+               db.serialize(function() {
+                       const insertQuery =
+                               "INSERT INTO Users " +
+                               "(name, email, notify) VALUES " +
+                               "('" + name + "', '" + email + "', " + notify + ")";
+                       db.run(insertQuery, err => {
+                               if (!!err)
+                                       return callback(err);
+                               db.get("SELECT last_insert_rowid() AS rowid", callback);
+                       });
                });
-       });
-}
-
-// Find one user (by id, name, email, or token)
-exports.getOne = function(by, value, cb)
-{
-       const delimiter = (typeof value === "string" ? "'" : "");
-       db.serialize(function() {
-               const query =
-                       "SELECT * " +
-                       "FROM Users " +
-                       "WHERE " + by + " = " + delimiter + value + delimiter;
-               db.get(query, cb);
-       });
-}
+       },
 
-/////////
-// MODIFY
+       // Find one user (by id, name, email, or token)
+       getOne: function(by, value, cb)
+       {
+               const delimiter = (typeof value === "string" ? "'" : "");
+               db.serialize(function() {
+                       const query =
+                               "SELECT * " +
+                               "FROM Users " +
+                               "WHERE " + by + " = " + delimiter + value + delimiter;
+                       db.get(query, cb);
+               });
+       },
 
-exports.setLoginToken = function(token, uid, cb)
-{
-       db.serialize(function() {
-               const query =
-                       "UPDATE Users " +
-                       "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
-                       "WHERE id = " + uid;
-               db.run(query, cb);
-       });
-}
+       /////////
+       // MODIFY
 
-// Set session token only if empty (first login)
-// TODO: weaker security (but avoid to re-login everywhere after each logout)
-exports.trySetSessionToken = function(uid, cb)
-{
-       // Also empty the login token to invalidate future attempts
-       db.serialize(function() {
-               const querySessionToken =
-                       "SELECT sessionToken " +
-                       "FROM Users " +
-                       "WHERE id = " + uid;
-               db.get(querySessionToken, (err,ret) => {
-                       if (!!err)
-                               return cb(err);
-                       const token = ret.sessionToken || genToken(params.token.length);
-                       const queryUpdate =
+       setLoginToken: function(token, uid, cb)
+       {
+               db.serialize(function() {
+                       const query =
                                "UPDATE Users " +
-                               "SET loginToken = NULL" +
-                               (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
+                               "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
                                "WHERE id = " + uid;
-                       db.run(queryUpdate);
-                       cb(null, token);
+                       db.run(query, cb);
                });
-       });
-}
+       },
 
-exports.updateSettings = function(user, cb)
-{
-       db.serialize(function() {
-               const query =
-                       "UPDATE Users " +
-                       "SET name = '" + user.name + "'" +
-                       ", email = '" + user.email + "'" +
-                       ", notify = " + user.notify + " " +
-                       "WHERE id = " + user.id;
-               db.run(query, cb);
-       });
+       // Set session token only if empty (first login)
+       // TODO: weaker security (but avoid to re-login everywhere after each logout)
+       trySetSessionToken: function(uid, cb)
+       {
+               // Also empty the login token to invalidate future attempts
+               db.serialize(function() {
+                       const querySessionToken =
+                               "SELECT sessionToken " +
+                               "FROM Users " +
+                               "WHERE id = " + uid;
+                       db.get(querySessionToken, (err,ret) => {
+                               if (!!err)
+                                       return cb(err);
+                               const token = ret.sessionToken || genToken(params.token.length);
+                               const queryUpdate =
+                                       "UPDATE Users " +
+                                       "SET loginToken = NULL" +
+                                       (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
+                                       "WHERE id = " + uid;
+                               db.run(queryUpdate);
+                               cb(null, token);
+                       });
+               });
+       },
+
+       updateSettings: function(user, cb)
+       {
+               db.serialize(function() {
+                       const query =
+                               "UPDATE Users " +
+                               "SET name = '" + user.name + "'" +
+                               ", email = '" + user.email + "'" +
+                               ", notify = " + user.notify + " " +
+                               "WHERE id = " + user.id;
+                       db.run(query, cb);
+               });
+       },
 }
+
+module.exports = UserModel;
index 8d7eba2..233d938 100644 (file)
@@ -7,25 +7,30 @@ var db = require("../utils/database");
  *   description: varchar
  */
 
-exports.getByName = function(name, callback)
+const VariantModel =
 {
-       db.serialize(function() {
-               const query =
-                       "SELECT * " +
-                       "FROM Variants " +
-                       "WHERE name='" + name + "'";
-               db.get(query, callback);
-       });
-}
+       getByName: function(name, callback)
+       {
+               db.serialize(function() {
+                       const query =
+                               "SELECT * " +
+                               "FROM Variants " +
+                               "WHERE name='" + name + "'";
+                       db.get(query, callback);
+               });
+       },
 
-exports.getAll = function(callback)
-{
-       db.serialize(function() {
-               const query =
-                       "SELECT * " +
-                       "FROM Variants";
-               db.all(query, callback);
-       });
+       getAll: function(callback)
+       {
+               db.serialize(function() {
+                       const query =
+                               "SELECT * " +
+                               "FROM Variants";
+                       db.all(query, callback);
+               });
+       },
+
+       //create, update, delete: directly in DB
 }
 
-//create, update, delete: directly in DB
+module.exports = VariantModel;
index 87de3eb..35659c3 100644 (file)
 Vue.component("my-correspondance", {
+       data: function() {
+               return {
+                       userId: user.id,
+                       games: [],
+                       challenges: [],
+                       willPlay: [], //IDs of challenges in which I decide to play (>= 3 players)
+                       newgameInfo: {
+                               fen: "",
+                               vid: 0,
+                               nbPlayers: 0,
+                               players: ["","",""],
+                               mainTime: 0,
+                               increment: 0,
+                       },
+               };
+       },
        template: `
                <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
-                       <p>TODO: load from server, show timeControl + players + link "play"</p>
-                       <p>Also tab for current challenges + button "new game"</p>
+                       <input id="modalNewgame" type="checkbox" class="modal"/>
+                       <div role="dialog" aria-labelledby="titleFenedit">
+                               <div class="card smallpad">
+                                       <label id="closeNewgame" for="modalNewgame" class="modal-close">
+                                       </label>
+                                       <fieldset>
+                                               <label for="selectVariant">{{ translate("Variant") }}</label>
+                                               <select id="selectVariant" v-model="newgameInfo.vid">
+                                                       <option v-for="v in variants" :value="v.id">{{ v.name }}</option>
+                                               </select>
+                                       </fieldset>
+                                       <fieldset>
+                                               <label for="selectNbPlayers">{{ translate("Number of players") }}</label>
+                                               <select id="selectNbPlayers" v-model="newgameInfo.nbPlayers">
+                                                       <option v-show="possibleNbplayers(2)" value="2">2</option>
+                                                       <option v-show="possibleNbplayers(3)" value="3">3</option>
+                                                       <option v-show="possibleNbplayers(4)" value="4">4</option>
+                                               </select>
+                                       </fieldset>
+                                       <fieldset>
+                                               <label for="timeControl">Time control (in days)</label>
+                                               <div id="timeControl">
+                                                       <input type="number" v-model="newgameInfo.mainTime" placeholder="Main time"/>
+                                                       <input type="number" v-model="newgameInfo.increment" placeholder="Increment"/>
+                                               </div>
+                                       </fieldset>
+                                       <fieldset>
+                                               <label for="selectPlayers">{{ translate("Play with?") }}</label>
+                                               <div id="selectPlayers">
+                                                       <input type="text" v-model="newgameInfo.players[0]"/>
+                                                       <input v-show="newgameInfo.nbPlayers>=3" type="text"
+                                                               v-model="newgameInfo.players[1]"/>
+                                                       <input v-show="newgameInfo.nbPlayers==4" type="text"
+                                                               v-model="newgameInfo.players[2]"/>
+                                               </div>
+                                       </fieldset>
+                                       <fieldset>
+                                               <label for="inputFen">{{ translate("FEN (ignored if players fields are blank)") }}</label>
+                                               <input id="inputFen" type="text" v-model="newgameInfo.fen"/>
+                                       </fieldset>
+                                       <button @click="newGame">Launch game</button>
+                               </div>
+                       </div>
+                       <p v-if="!userId">Correspondance play is reserved to registered users</p>
+                       <div v-if="!!userId">
+                               <my-challenge-list :challenges="challenges" @click-challenge="clickChallenge">
+                               </my-challenge-list>
+                               <button onClick="doClick('modalNewgame')">New game</button>
+                               <my-game-list :games="games" @show-game="showGame">
+                               </my-game-list>
+                       </div>
                </div>
        `,
+       computed: {
+               // TODO: this is very artificial...
+               variants: function() {
+                       return variantArray;
+               },
+       },
        created: function() {
-               //TODO
+               // use user.id to load challenges + games from server
+       },
+       methods: {
+               translate: translate,
+               clickChallenge: function() {
+                       // TODO: accepter un challenge peut lancer une partie, il
+                       // faut alors supprimer challenge + creer partie + la retourner et l'ajouter ici
+                       // autres actions:
+                       // supprime mon défi
+                       // accepte un défi
+                       // annule l'acceptation d'un défi (si >= 3 joueurs)
+                       //
+                       // si pas le mien et FEN speciale :: (charger code variante et)
+                       // montrer diagramme + couleur (orienté)
+               },
+               showGame: function(g) {
+                       // Redirect to /variant#game?id=...
+                       location.href="/variant#game?id=" + g.id;
+               },
+               newGame: function() {
+                       // NOTE: side-effect = set FEN
+                       // TODO: (to avoid any cheating option) separate the GenRandInitFen() functions
+                       // in separate files, load on server and generate FEN on server.
+                       const error = checkChallenge(this.newgameInfo);
+                       if (!!error)
+                               return alert(error);
+                       // Possible (server) error if filled player does not exist
+                       ajax(
+                               "/challenges/" + this.newgameInfo.vid,
+                               "POST",
+                               this.newgameInfo,
+                               response => {
+                                       this.challenges.push(response.challenge);
+                               }
+                       );
+               },
+               possibleNbplayers: function(nbp) {
+                       if (this.newgameInfo.vid == 0)
+                               return false;
+                       const idxInVariants = variantArray.findIndex(v => v.id == this.newgameInfo.vid);
+                       return NbPlayers[variantArray[idxInVariants].name].includes(nbp);
+               },
        },
 });
index 0d9322e..534d9af 100644 (file)
@@ -120,7 +120,7 @@ Vue.component('my-game', {
                        this.loadGame();
                else if (!!this.fen)
                {
-                       this.vr = new VariantRules(this.fen);
+                       this.vr = new V(this.fen);
                        this.fenStart = this.fen;
                }
                // TODO: if I'm one of the players in game, then:
@@ -296,7 +296,7 @@ Vue.component('my-game', {
                },
                translate: translate,
                newGameFromFen: function() {
-                       this.vr = new VariantRules(this.fen);
+                       this.vr = new V(this.fen);
                        this.moves = [];
                        this.cursor = -1;
                        this.fenStart = this.fen;
@@ -532,12 +532,12 @@ Vue.component('my-game', {
                                this.moves.pop();
                },
                gotoMove: function(index) {
-                       this.vr = new VariantRules(this.moves[index].fen);
+                       this.vr = new V(this.moves[index].fen);
                        this.cursor = index;
                        this.lastMove = this.moves[index];
                },
                gotoBegin: function() {
-                       this.vr = new VariantRules(this.fenStart);
+                       this.vr = new V(this.fenStart);
                        this.cursor = -1;
                        this.lastMove = null;
                },
index 9eb1f7d..9b66982 100644 (file)
@@ -1,18 +1,23 @@
 Vue.component("my-game-list", {
        props: ["games"],
        computed: {
+               showVariant: function() {
+                       return this.games.length > 0 && !!this.games[0].vname;
+               },
                showResult: function() {
-                       this.games.length > 0 && this.games[0].score != "*";
+                       return this.games.length > 0 && this.games[0].score != "*";
                },
        },
        template: `
                <table>
                        <tr>
+                               <th v-if="showVariant">Variant</th>
                                <th>Players names</th>
                                <th>Cadence</th>
                                <th v-if="showResult">Result</th>
                        </tr>
                        <tr v-for="g in games" @click="$emit('show-game',g)">
+                               <td v-if="showVariant">{{ g.vname }}</td>
                                <td>
                                        <span v-for="p in g.players">{{ p.name }}</span>
                                </td>
index 93990ce..fbf02ee 100644 (file)
@@ -144,7 +144,7 @@ Vue.component('my-room', {
                },
                // user: last person to accept the challenge
                newGame: function(chall, user) {
-                       const fen = chall.fen || VariantRules.GenRandInitFen();
+                       const fen = chall.fen || V.GenRandInitFen();
                        const game = {}; //TODO: fen, players, time ...
                        //setStorage(game); //TODO
                        game.players.forEach(p => {
index 8676933..45da511 100644 (file)
@@ -9,7 +9,7 @@ onmessage = function(e)
                                '/javascripts/base_rules.js',
                                '/javascripts/utils/array.js',
                                '/javascripts/variants/' + e.data[1] + '.js');
-                       self.V = VariantRules;
+                       self.V = eval(e.data[1] + "Rules");
                        break;
                case "init":
                        const fen = e.data[1];
diff --git a/public/javascripts/shared/challengeCheck.js b/public/javascripts/shared/challengeCheck.js
new file mode 100644 (file)
index 0000000..a5833f6
--- /dev/null
@@ -0,0 +1,66 @@
+function checkChallenge(c)
+{
+       const vid = parseInt(c.vid);
+       if (isNaN(vid) || vid <= 0)
+               return "Please select a variant";
+
+       const mainTime = parseInt(c.mainTime);
+       const increment = parseInt(c.increment);
+       if (isNaN(mainTime) || mainTime <= 0)
+               return "Main time should be strictly positive";
+       if (isNaN(increment) || increment < 0)
+               return "Increment must be positive";
+
+       // Basic alphanumeric check for players names
+       let playerCount = 0;
+       for (p of c.players)
+       {
+               if (p.length > 0)
+               {
+                       if (!p.match(/^[\w]+$/))
+                               return "Wrong characters in players names";
+                       playerCount++;
+               }
+       }
+
+       if (playerCount > 0 && playerCount != c.nbPlayers)
+               return "None, or all of the opponent names must be filled"
+
+       if (!!document) //client side
+       {
+               const idxInVariants = variantArray.findIndex(v => v.id == c.vid);
+               const vname = variantArray[idxInVariants].name;
+               const scriptId = vname + "RulesScript";
+               const afterRulesAreLoaded = () => {
+                       const V = eval(vname + "Rules");
+                       // Allow custom FEN (and check it) only for individual challenges
+                       if (c.fen.length > 0 && playerCount > 0)
+                       {
+                               if (!V.IsGoodFen(c.fen))
+                                       return "Bad FEN string";
+                       }
+                       else
+                       {
+                               // Generate a FEN
+                               c.fen = V.GenRandInitFen();
+                       }
+               };
+               if (!document.getElementById(scriptId))
+               {
+                       // Load variant rules (only once)
+                       var script = document.createElement("script");
+                       script.id = scriptId;
+                       script.src = "/javascripts/variants/" + vname + ".js";
+                       document.body.appendChild(script);
+                       script.onload = afterRulesAreLoaded;
+               }
+       }
+       else
+       {
+               // Just characters check on server:
+               if (!c.fen.match(/^[a-zA-Z0-9, /-]*$/))
+                       return "Bad FEN string";
+       }
+}
+
+try { module.exports = checkChallenge; } catch(e) { } //for server
diff --git a/public/javascripts/shared/nbPlayers.js b/public/javascripts/shared/nbPlayers.js
new file mode 100644 (file)
index 0000000..8c0cc86
--- /dev/null
@@ -0,0 +1,23 @@
+const NbPlayers =
+{
+       "Alice": [2,3,4],
+       "Antiking": [2,3,4],
+       "Atomic": [2,3,4],
+       "Baroque": [2,3,4],
+       "Berolina": [2,4],
+       "Checkered": [2,3,4],
+       "Chess960": [2,3,4],
+       "Crazyhouse": [2,3,4],
+       "Dark": [2,3,4],
+       "Extinction": [2,3,4],
+       "Grand": [2],
+       "Losers": [2,3,4],
+       "Magnetic": [2],
+       "Marseille": [2],
+       "Switching": [2,3,4],
+       "Upsidedown": [2],
+       "Wildebeest": [2],
+       "Zen": [2,3,4],
+};
+
+try { module.exports = NbPlayers; } catch (e) { } //for server
index 9f7a020..ba7b1e9 100644 (file)
@@ -76,7 +76,7 @@ function getShadowArray(shadow)
 function getDiagram(args)
 {
        // Obtain the array of pieces images names:
-       const board = VariantRules.GetBoard(args.position);
+       const board = V.GetBoard(args.position);
        const orientation = args.orientation || "w";
        const markArray = getMarkArray(args.marks);
        const shadowArray = getShadowArray(args.shadow);
index 3a61358..04b4a34 100644 (file)
@@ -350,5 +350,3 @@ class AliceRules extends ChessRules
                return notation;
        }
 }
-
-const VariantRules = AliceRules;
index 45daa62..66eb9f0 100644 (file)
@@ -198,5 +198,3 @@ class AntikingRules extends ChessRules
                        " w 1111 -";
        }
 }
-
-const VariantRules = AntikingRules;
index 255444f..87c9809 100644 (file)
@@ -140,5 +140,3 @@ class AtomicRules extends ChessRules
                return color == "w" ? "0-1" : "1-0"; //checkmate
        }
 }
-
-const VariantRules = AtomicRules;
index 38d4012..d06b99c 100644 (file)
@@ -614,5 +614,3 @@ class BaroqueRules extends ChessRules
                return notation;
        }
 }
-
-const VariantRules = BaroqueRules;
index 31630ab..517a93e 100644 (file)
@@ -133,5 +133,3 @@ class BerolinaRules extends ChessRules
                return super.getNotation(move); //all other pieces are orthodox
        }
 }
-
-const VariantRules = BerolinaRules;
index 6864673..9de46ff 100644 (file)
@@ -299,5 +299,3 @@ class CheckeredRules extends ChessRules
                }
        }
 }
-
-const VariantRules = CheckeredRules;
index 529ad99..1c8292c 100644 (file)
@@ -2,5 +2,3 @@ class Chess960Rules extends ChessRules
 {
        // Standard rules
 }
-
-const VariantRules = Chess960Rules;
index 3d9c743..179fffa 100644 (file)
@@ -281,5 +281,3 @@ class CrazyhouseRules extends ChessRules
                return "@" + V.CoordsToSquare(move.end);
        }
 }
-
-const VariantRules = CrazyhouseRules;
index e1ab30d..3879d9d 100644 (file)
@@ -285,5 +285,3 @@ class DarkRules extends ChessRules
                return moves[_.sample(candidates, 1)];
        }
 }
-
-const VariantRules = DarkRules;
index 2f57260..aab359f 100644 (file)
@@ -135,5 +135,3 @@ class ExtinctionRules extends ChessRules
                return super.evalPosition();
        }
 }
-
-const VariantRules = ExtinctionRules;
index 8d06f71..6408847 100644 (file)
@@ -387,5 +387,3 @@ class GrandRules extends ChessRules
                        " w 1111 - 00000000000000";
        }
 }
-
-const VariantRules = GrandRules;
index 9af6776..33c8109 100644 (file)
@@ -183,5 +183,3 @@ class LosersRules extends ChessRules
                        " w -"; //no en-passant
        }
 }
-
-const VariantRules = LosersRules;
index 8adb1ba..3227990 100644 (file)
@@ -202,5 +202,3 @@ class MagneticRules extends ChessRules
                return 500; //checkmates evals may be slightly below 1000
        }
 }
-
-const VariantRules = MagneticRules;
index 7aa73c0..7d83bd5 100644 (file)
@@ -293,5 +293,3 @@ class MarseilleRules extends ChessRules
                return selected;
        }
 }
-
-const VariantRules = MarseilleRules;
index d898ff6..04bb110 100644 (file)
@@ -133,5 +133,3 @@ class SwitchingRules extends ChessRules
                return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
        }
 }
-
-const VariantRules = SwitchingRules;
index 3e389d0..ecd1ff5 100644 (file)
@@ -68,5 +68,3 @@ class UpsidedownRules extends ChessRules
                        " w"; //no castle, no en-passant
        }
 }
-
-const VariantRules = UpsidedownRules;
index 293b3b1..cb95318 100644 (file)
@@ -285,5 +285,3 @@ class WildebeestRules extends ChessRules
                        " w 1111 -";
        }
 }
-
-const VariantRules = WildebeestRules;
index 7713b84..6a568db 100644 (file)
@@ -225,5 +225,3 @@ class ZenRules extends ChessRules
                }
        }
 }
-
-const VariantRules = ZenRules;
index 85815a3..f901a02 100644 (file)
@@ -4,7 +4,7 @@ router.use("/", require("./index"));
 router.use("/", require("./users"));
 router.use("/", require("./messages"));
 //router.use("/", require("./games"));
-//router.use("/", require("./challenge"));
+router.use("/", require("./challenge"));
 router.use("/", require("./problems"));
 router.use("/", require("./variant"));
 
index 1a4d22b..2131870 100644 (file)
@@ -1,77 +1,81 @@
-// TODO: adapt this (from Mongo to SQLite, and challenge format changed) for corr play
+// AJAX methods to get, create, update or delete a challenge
 
-var router = require("express").Router();
-var ObjectID = require("bson-objectid");
-var ChallengeModel = require('../models/Challenge');
-var UserModel = require('../models/User');
-var ObjectID = require("bson-objectid");
-var access = require("../utils/access");
+let router = require("express").Router();
+const access = require("../utils/access");
+const ChallengeModel = require("../models/Challenge");
+const checkChallenge = require("../public/javascripts/shared/challengeCheck.js");
 
-// variant page
-router.get("/challengesbyvariant", access.logged, access.ajax, (req,res) => {
-       if (req.query["uid"] != req.user._id)
-               return res.json({errmsg: "Not your challenges"});
-       let uid = ObjectID(req.query["uid"]);
-       let vid = ObjectID(req.query["vid"]);
-       ChallengeModel.getByVariant(uid, vid, (err, challengeArray) => {
-               res.json(err || {challenges: challengeArray});
+router.post("/challenges/:vid([0-9]+)", access.logged, access.ajax, (req,res) => {
+       const vid = req.params["vid"];
+       const chall = {
+               uid: req.userId,
+               vid: vid,
+               fen: req.body["fen"],
+               mainTime: req.body["mainTime"],
+               increment: req.body["increment"],
+               nbPlayers: req.body["nbPlayers"],
+               players: req.body["players"],
+       };
+       const error = checkChallenge(chall);
+       ChallengeModel.create(chall, (err,lastId) => {
+               res.json(err || {challenge: Object.assign(chall, {id: lastId["rowid"]})});
        });
 });
 
-// index
-router.get("/challengesbyplayer", access.logged, access.ajax, (req,res) => {
-       if (req.query["uid"] != req.user._id)
-               return res.json({errmsg: "Not your challenges"});
-       let uid = ObjectID(req.query["uid"]);
-       ChallengeModel.getByPlayer(uid, (err, challengeArray) => {
-               res.json(err || {challenges: challengeArray});
-       });
-});
-
-function createChallenge(vid, from, to, res)
-{
-       ChallengeModel.create(vid, from, to, (err, chall) => {
-               res.json(err || {
-                       // A challenge can be sent using only name, thus 'to' is returned
-                       to: chall.to,
-                       cid: chall._id
-               });
-       });
-}
-
-// from[, to][,nameTo]
-router.post("/challenges", access.logged, access.ajax, (req,res) => {
-       if (req.body.from != req.user._id)
-               return res.json({errmsg: "Identity usurpation"});
-       let from = ObjectID(req.body.from);
-       let to = !!req.body.to ? ObjectID(req.body.to) : undefined;
-       let nameTo = !!req.body.nameTo ? req.body.nameTo : undefined;
-       let vid = ObjectID(req.body.vid);
-       if (!to && !!nameTo)
-       {
-               UserModel.getByName(nameTo, (err,user) => {
-                       access.checkRequest(res, err, user, "Opponent not found", () => {
-                               createChallenge(vid, from, user._id, res);
-                       });
-               });
-       }
-       else if (!!to)
-               createChallenge(vid, from, to, res);
-       else
-               createChallenge(vid, from, undefined, res); //automatch
-});
-
-router.delete("/challenges", access.logged, access.ajax, (req,res) => {
-       let cid = ObjectID(req.query.cid);
-       ChallengeModel.getById(cid, (err,chall) => {
-               access.checkRequest(res, err, chall, "Challenge not found", () => {
-                       if (!chall.from.equals(req.user._id) && !!chall.to && !chall.to.equals(req.user._id))
-                               return res.json({errmsg: "Not your challenge"});
-                       ChallengeModel.remove(cid, err => {
-                               res.json(err || {});
-                       });
-               });
-       });
-});
+//// index
+//router.get("/challengesbyplayer", access.logged, access.ajax, (req,res) => {
+//     if (req.query["uid"] != req.user._id)
+//             return res.json({errmsg: "Not your challenges"});
+//     let uid = ObjectID(req.query["uid"]);
+//     ChallengeModel.getByPlayer(uid, (err, challengeArray) => {
+//             res.json(err || {challenges: challengeArray});
+//     });
+//});
+//
+//function createChallenge(vid, from, to, res)
+//{
+//     ChallengeModel.create(vid, from, to, (err, chall) => {
+//             res.json(err || {
+//                     // A challenge can be sent using only name, thus 'to' is returned
+//                     to: chall.to,
+//                     cid: chall._id
+//             });
+//     });
+//}
+//
+//// from[, to][,nameTo]
+//router.post("/challenges", access.logged, access.ajax, (req,res) => {
+//     if (req.body.from != req.user._id)
+//             return res.json({errmsg: "Identity usurpation"});
+//     let from = ObjectID(req.body.from);
+//     let to = !!req.body.to ? ObjectID(req.body.to) : undefined;
+//     let nameTo = !!req.body.nameTo ? req.body.nameTo : undefined;
+//     let vid = ObjectID(req.body.vid);
+//     if (!to && !!nameTo)
+//     {
+//             UserModel.getByName(nameTo, (err,user) => {
+//                     access.checkRequest(res, err, user, "Opponent not found", () => {
+//                             createChallenge(vid, from, user._id, res);
+//                     });
+//             });
+//     }
+//     else if (!!to)
+//             createChallenge(vid, from, to, res);
+//     else
+//             createChallenge(vid, from, undefined, res); //automatch
+//});
+//
+//router.delete("/challenges", access.logged, access.ajax, (req,res) => {
+//     let cid = ObjectID(req.query.cid);
+//     ChallengeModel.getById(cid, (err,chall) => {
+//             access.checkRequest(res, err, chall, "Challenge not found", () => {
+//                     if (!chall.from.equals(req.user._id) && !!chall.to && !chall.to.equals(req.user._id))
+//                             return res.json({errmsg: "Not your challenge"});
+//                     ChallengeModel.remove(cid, err => {
+//                             res.json(err || {});
+//                     });
+//             });
+//     });
+//});
 
 module.exports = router;
index cecca5c..6ea4516 100644 (file)
@@ -22,10 +22,10 @@ block content
                                #flagMenu.clickable(onClick="doClick('modalLang')")
                                        img(src="/images/flags/" + lang + ".svg")
                                include userMenu
-                               a.right-menu(v-show="display=='variants'" href="#games")
+                               a.right-menu(v-show="display=='variants'" href="#correspondance")
                                        .info-container
                                                p Correspondance
-                               a.right-menu(v-show="display=='games'" href="#variants")
+                               a.right-menu(v-show="display=='correspondance'" href="#variants")
                                        .info-container
                                                p Variants
                .row(v-show="display=='variants'")
@@ -43,5 +43,6 @@ block javascripts
        script(src="/javascripts/socket_url.js")
        script(src="/javascripts/components/variantSummary.js")
        script(src="/javascripts/components/gameList.js")
+       script(src="/javascripts/components/challengeList.js")
        script(src="/javascripts/components/correspondance.js")
        script(src="/javascripts/index.js")
index 1e0f175..b2b1d52 100644 (file)
@@ -46,12 +46,16 @@ html
                script(src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js")
                script(src="/javascripts/utils/misc.js")
                script(src="/javascripts/utils/ajax.js")
+               script(src="/javascripts/utils/array.js")
+               script(src="/javascripts/base_rules.js")
                script(src="/javascripts/layout.js")
                script(src="/javascripts/contactForm.js")
                if development
                        script(src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js")
                else
                        script(src="https://cdn.jsdelivr.net/npm/vue")
+               script(src="/javascripts/shared/nbPlayers.js")
+               script(src="/javascripts/shared/challengeCheck.js")
                script(src="/javascripts/shared/userCheck.js")
                script(src="/javascripts/components/upsertUser.js")
                script.
index c6303f3..7e57bbf 100644 (file)
@@ -37,16 +37,15 @@ block content
                                :mode="mode" :settings="settings" @game-over="archiveGame")
 
 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/variants/" + variant.name + ".js")
        script.
-               const V = VariantRules; //because this variable is often used
                const variant = !{JSON.stringify(variant)};
+               // Just 'V' because this variable is often used:
+               const V = eval(variant.name + "Rules");
        script(src="/javascripts/components/board.js")
        script(src="/javascripts/components/chat.js")
        script(src="/javascripts/components/gameList.js")