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...)
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
uid integer,
vid integer,
nbPlayers integer,
+ fen varchar,
+ mainTime integer,
+ addTime integer,
foreign key (uid) references Users(id),
foreign key (vid) references Variants(id)
);
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)
);
gid integer,
uid integer,
color character,
+ rtime integer, --remaining time in milliseconds
foreign key (gid) references Games(id),
foreign key (uid) references Users(id)
);
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
* 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;
* 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;
* 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;
* 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;
* 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;
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);
+ },
},
});
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:
},
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;
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;
},
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>
},
// 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 => {
'/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];
--- /dev/null
+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
--- /dev/null
+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
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);
return notation;
}
}
-
-const VariantRules = AliceRules;
" w 1111 -";
}
}
-
-const VariantRules = AntikingRules;
return color == "w" ? "0-1" : "1-0"; //checkmate
}
}
-
-const VariantRules = AtomicRules;
return notation;
}
}
-
-const VariantRules = BaroqueRules;
return super.getNotation(move); //all other pieces are orthodox
}
}
-
-const VariantRules = BerolinaRules;
}
}
}
-
-const VariantRules = CheckeredRules;
{
// Standard rules
}
-
-const VariantRules = Chess960Rules;
return "@" + V.CoordsToSquare(move.end);
}
}
-
-const VariantRules = CrazyhouseRules;
return moves[_.sample(candidates, 1)];
}
}
-
-const VariantRules = DarkRules;
return super.evalPosition();
}
}
-
-const VariantRules = ExtinctionRules;
" w 1111 - 00000000000000";
}
}
-
-const VariantRules = GrandRules;
" w -"; //no en-passant
}
}
-
-const VariantRules = LosersRules;
return 500; //checkmates evals may be slightly below 1000
}
}
-
-const VariantRules = MagneticRules;
return selected;
}
}
-
-const VariantRules = MarseilleRules;
return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
}
}
-
-const VariantRules = SwitchingRules;
" w"; //no castle, no en-passant
}
}
-
-const VariantRules = UpsidedownRules;
" w 1111 -";
}
}
-
-const VariantRules = WildebeestRules;
}
}
}
-
-const VariantRules = ZenRules;
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"));
-// 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;
#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'")
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")
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.
: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")