this.lastMove = move;
if (this.st.settings.sound == 2)
new Audio("/sounds/move.mp3").play().catch(err => {});
- if (!this.analyze)
- this.$emit("newmove", move); //post-processing (e.g. computer play)
if (!navigate)
{
move.fen = this.vr.getFen();
this.moves = this.moves.slice(0,this.cursor).concat([move]);
}
}
+ if (!this.analyze)
+ this.$emit("newmove", move); //post-processing (e.g. computer play)
// Is opponent in check?
this.incheck = this.vr.getCheckSquares(this.vr.turn);
const score = this.vr.getCurrentScore();
this.state.user.notify = res.notify;
});
}
- this.state.conn = new WebSocket(
- params.socketUrl + "/?sid=" + mysid + "&page=" + page);
+ this.state.conn = new WebSocket(params.socketUrl + "/?sid=" + mysid +
+ "&page=" + encodeURIComponent(page));
// Settings initialized with values from localStorage
this.state.settings = {
bcolor: localStorage["bcolor"] || "lichess",
gid: gameId,
newObj:
{
- // TODO: I think stringify isn't requuired here (see ajax() )
- move: JSON.stringify(obj.move), //may be undefined...
+ move: obj.move, //may be undefined...
fen: obj.fen,
score: obj.score,
}
button(@click="abortGame") {{ st.tr["Game is too boring"] }}
BaseGame(:game="game" :vr="vr" ref="basegame"
@newmove="processMove" @gameover="gameOver")
- // TODO: also show players names
+ div Names: {{ game.players[0].name }} - {{ game.players[1].name }}
div Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }}
.button-group(v-if="game.mode!='analyze' && game.score=='*'")
button(@click="offerDraw") Draw
import { GameStorage } from "@/utils/gameStorage";
import { ppt } from "@/utils/datetime";
import { extractTime } from "@/utils/timeControl";
+import { ArrayFun } from "@/utils/array";
export default {
name: 'my-game',
id: "",
rid: ""
},
- game: {}, //passed to BaseGame
+ game: {players:[{name:""},{name:""}]}, //passed to BaseGame
corrMsg: "", //to send offline messages in corr games
virtualClocks: [0, 0], //initialized with true game.clocks
vr: null, //"variant rules" object initialized from FEN
}
}, 1000);
},
- // In case variants array was't loaded when game was retrieved
- "st.variants": function(variantArray) {
- if (!!this.game.vname && this.game.vname == "")
- {
- this.game.vname = variantArray.filter(v =>
- v.id == this.game.vid)[0].name;
- }
- },
},
// TODO: redundant code with Hall.vue (related to people array)
created: function() {
}
case "identity":
{
- const pIdx = this.people.findIndex(p => p.sid == data.user.sid);
- this.people[pIdx].id = data.user.id;
- this.people[pIdx].name = data.user.name;
- break;
- }
- case "newmove":
- // NOTE: next call will trigger processMove()
- this.$refs["basegame"].play(data.move,
- "receive", this.game.vname!="Dark" ? "animate" : null);
- break;
- case "pong": //received if we sent a ping (game still alive on our side)
- {
- if (this.game.type == "live") //corr games are always complete
+ let player = this.people.find(p => p.sid == data.user.sid);
+ player.id = data.user.id;
+ player.name = data.user.name;
+ // Sending last state only for live games: corr games are complete
+ if (this.game.type == "live" && this.game.oppsid == player.sid)
{
- // Send our "last state" informations to opponent(s)
+ // Send our "last state" informations to opponent
const L = this.game.moves.length;
this.st.conn.send(JSON.stringify({
code: "lastate",
- target: this.getOppSid(), //he's connected for sure
- gameId: this.gameRef.id,
- lastMove: (L>0 ? this.game.moves[L-1] : undefined),
- score: this.game.score,
- movesCount: L,
- drawOffer: this.drawOffer,
- clocks: this.game.clocks,
+ target: player.sid,
+ state:
+ {
+ lastMove: (L>0 ? this.game.moves[L-1] : undefined),
+ score: this.game.score,
+ movesCount: L,
+ drawOffer: this.drawOffer,
+ clocks: this.game.clocks,
+ }
}));
}
break;
}
+ case "newmove":
+ // NOTE: this call to play() will trigger processMove()
+ this.$refs["basegame"].play(data.move,
+ "receive", this.game.vname!="Dark" ? "animate" : null);
+ break;
case "lastate": //got opponent infos about last move
{
const L = this.game.moves.length;
- if (this.gameRef.id != data.gameId)
- break; //games IDs don't match: nothing we can do...
- // OK, opponent still in game (which might be over)
if (data.movesCount > L)
{
// Just got last move from him
- this.$refs["basegame"].play(data.lastMove, "receive");
+ this.$refs["basegame"].play(data.lastMove,
+ "receive", this.game.vname!="Dark" ? "animate" : null);
if (data.score != "*" && this.game.score == "*")
{
// Opponent resigned or aborted game, or accepted draw offer
// (this is not a stalemate or checkmate)
this.$refs["basegame"].endGame(data.score, "Opponent action");
}
- this.game.clocks = data.clocks;
- this.drawOffer = data.drawOffer;
- }
- else if (data.movesCount < L)
- {
- // We must tell last move to opponent
- this.st.conn.send(JSON.stringify({
- code: "lastate",
- target: this.getOppSid(), //we know he is connected
- gameId: this.gameRef.id,
- lastMove: (L>0 ? this.game.moves[L-1] : undefined),
- score: this.game.score,
- movesCount: L,
- drawOffer: this.drawOffer,
- clocks: this.game.clocks,
- }));
+ this.game.clocks = data.clocks; //TODO: check this?
+ this.drawOffer = data.drawOffer; //does opponent offer draw?
}
break;
}
// ==> on "newmove", check "drawOffer" field
case "connect":
{
- // TODO: if opponent connect, trigger lastate chain of events...
this.people.push({name:"", id:0, sid:data.sid});
this.st.conn.send(JSON.stringify({code:"askidentity", target:data.sid}));
break;
// - from remote peer (one live game I don't play, finished or not)
loadGame: function(game) {
const afterRetrieval = async (game) => {
- // NOTE: variants array might not be available yet, thus the two next lines
- const variantCell = this.st.variants.filter(v => v.id == game.vid);
- const vname = (variantCell.length > 0 ? variantCell[0].name : "");
- if (!game.fen)
- game.fen = game.fenStart; //game wasn't started
+ const vModule = await import("@/variants/" + game.vname + ".js");
+ window.V = vModule.VariantRules;
+ this.vr = new V(game.fen);
const gtype = (game.timeControl.indexOf('d') >= 0 ? "corr" : "live");
- // TODO: this is not really beautiful (uid on corr players...)
- if (gtype == "corr" && game.players[0].color == "b")
- [ game.players[0], game.players[1] ] = [ game.players[1], game.players[0] ];
- const myIdx = game.players.findIndex(p => {
- return p.sid == this.st.user.sid || p.uid == this.st.user.id;
- });
const tc = extractTime(game.timeControl);
if (gtype == "corr")
{
+ if (game.players[0].color == "b")
+ {
+ // Adopt the same convention for live and corr games: [0] = white
+ [ game.players[0], game.players[1] ] =
+ [ game.players[1], game.players[0] ];
+ }
// corr game: needs to compute the clocks + initime
game.clocks = [tc.mainTime, tc.mainTime];
game.initime = [0, 0];
if (L >= 1)
game.initime[L%2] = game.moves[L-1].played;
}
+ const myIdx = game.players.findIndex(p => {
+ return p.sid == this.st.user.sid || p.uid == this.st.user.id;
+ });
if (gtype == "live" && game.clocks[0] < 0) //game unstarted
{
game.clocks = [tc.mainTime, tc.mainTime];
});
}
}
- const vModule = await import("@/variants/" + vname + ".js");
- window.V = vModule.VariantRules;
- this.vr = new V(game.fen);
this.game = Object.assign({},
game,
// NOTE: assign mycolor here, since BaseGame could also be VS computer
{
type: gtype,
increment: tc.increment,
- vname: vname,
mycolor: [undefined,"w","b"][myIdx+1],
// opponent sid not strictly required (or available), but easier
// at least oppsid or oppid is available anyway:
oppid: (myIdx < 0 ? undefined : game.players[1-myIdx].uid),
}
);
- if (!!this.game.oppid)
- {
- // Send ping to server (answer pong if players[s] are connected)
- this.st.conn.send(JSON.stringify({code:"ping", target:this.game.oppid}));
- }
};
if (!!game)
return afterRetrival(game);
if (!!this.gameRef.rid)
{
+ // Remote live game
this.st.conn.send(JSON.stringify(
{code:"askfullgame", target:this.gameRef.rid}));
// (send moves updates + resign/abort/draw actions)
}
else
{
- GameStorage.get(this.gameRef.id, async (game) => {
+ // Local or corr game
+ GameStorage.get(this.gameRef.id, (game) => {
afterRetrieval(game);
});
}
const nextIdx = ["w","b"].indexOf(this.vr.turn);
GameStorage.update(this.gameRef.id,
{
- move: filtered_move,
fen: move.fen,
+ move:
+ {
+ squares: filtered_move,
+ message: this.corrMsg, //TODO
+ played: Date.now(), //TODO: on server?
+ idx: this.game.moves.length - 1,
+ color: move.color,
+ },
clocks: this.game.clocks.map((t,i) => i==colorIdx
? this.game.clocks[i] + addTime
: this.game.clocks[i]),
});
this.games.forEach(g => {
if (g.vname == "")
- g.vname = this.getVname(g.vid)
+ g.vname = this.getVname(g.vid);
});
},
},
}
this.$router.push(url);
},
- // TODO: ...filter(...)[0].name, one-line, just remove this function
getVname: function(vid) {
- const vIdx = this.st.variants.findIndex(v => v.id == vid);
- return vIdx >= 0 ? this.st.variants[vIdx].name : "";
+ const variant = this.st.variants.find(v => v.id == vid);
+ // this.st.variants might be uninitialized (variant == null)
+ return (!!variant ? variant.name : "");
},
getSid: function(pname) {
const pIdx = this.people.findIndex(pl => pl.name == pname);
chall.vname = vname;
chall.from = this.people[0]; //avoid sending email
this.challenges.push(chall);
- localStorage.setItem("challenge", JSON.stringify(chall));
+ if (ctype == "live")
+ localStorage.setItem("challenge", JSON.stringify(chall));
document.getElementById("modalNewgame").checked = false;
};
const cIdx = this.challenges.findIndex(
}
else //my challenge
{
- localStorage.removeItem("challenge");
if (c.type == "corr")
{
ajax(
{id: c.id}
);
}
+ else //live
+ localStorage.removeItem("challenge");
}
// In (almost) all cases, the challenge is consumed:
ArrayFun.remove(this.challenges, ch => ch.id == c.id);
fen: c.fen || V.GenRandInitFen(),
players: shuffle([c.from, c.seat]), //white then black
vid: c.vid,
+ vname: c.vname, //theoretically vid is enough, but much easier with vname
timeControl: c.timeControl,
};
let target = c.from.sid; //may not be defined if corr + offline opp
create table Moves (
gid integer,
- move varchar,
+ squares varchar, --description, appear/vanish/from/to
message varchar,
played datetime, --when was this move played?
idx integer, --index of the move in the game
* gid: ref game id
* uid: ref user id
* color: character
- * rtime: real (remaining time)
*
* Structure table Moves:
* gid: ref game id
- * move: varchar (description)
+ * squares: varchar (description)
* message: text
* played: datetime
* idx: integer
{
db.serialize(function() {
let query =
- "INSERT INTO Games (vid, fenStart, score, timeControl) " +
- "VALUES (" + vid + ",'" + fen + "','*','" + timeControl + "')";
+ "INSERT INTO Games (vid, fenStart, fen, score, timeControl) VALUES " +
+ "(" + vid + ",'" + fen + "','" + fen + "','*','" + timeControl + "')";
db.run(query, function(err) {
if (!!err)
return cb(err);
const color = (idx==0 ? "w" : "b");
query =
"INSERT INTO Players VALUES " +
- // Remaining time = -1 means "unstarted"
- "(" + this.lastID + "," + p.id + ",'" + color + "', -1)";
+ "(" + this.lastID + "," + p.id + ",'" + color + "')";
db.run(query);
});
cb(null, {gid: this.lastID});
getOne: function(id, cb)
{
db.serialize(function() {
+ // TODO: optimize queries?
let query =
- "SELECT * " +
- "FROM Games " +
- "WHERE id = " + id;
+ "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
+ "v.name AS vname " +
+ "FROM Games g " +
+ "JOIN Variants v " +
+ " ON g.vid = v.id " +
+ "WHERE g.id = " + id;
db.get(query, (err,gameInfo) => {
if (!!err)
return cb(err);
query =
- "SELECT uid, color, rtime " +
- "FROM Players " +
- "WHERE gid = " + id;
+ "SELECT p.uid, p.color, u.name " +
+ "FROM Players p " +
+ "JOIN Users u " +
+ " ON p.uid = u.id " +
+ "WHERE p.gid = " + id;
db.all(query, (err2,players) => {
if (!!err2)
return cb(err2);
query =
- "SELECT move, message, played, idx, color " +
+ "SELECT squares, message, played, idx, color " +
"FROM Moves " +
"WHERE gid = " + id;
db.all(query, (err3,moves) => {
},
// obj can have fields move, fen and/or score
- update: function(id, obj, cb)
+ update: function(id, obj)
{
- db.serialize(function() {
+
+
+
+console.log(id);
+ console.log(obj);
+
+
+ db.parallelize(function() {
let query =
"UPDATE Games " +
"SET ";
- if (!!obj.move)
- {
- move.played = Date.now();
- query += "move = " + obj.move + ","; //TODO: already stringified?!
- }
if (!!obj.fen)
query += "fen = " + obj.fen + ",";
if (!!obj.score)
query += "score = " + obj.score + ",";
query = query.slice(0,-1); //remove last comma
query += " WHERE gameId = " + id;
- db.run(query, (err) => {
- cb(err);
- });
+ db.run(query);
+ if (!!obj.move)
+ {
+ const m =obj.move;
+ query =
+ "INSERT INTO Moves (gid,squares,message,played,idx,color) VALUES " +
+ "(" + id + ",'" + JSON.stringify(m.squares) + "','" + m.message +
+ "'" + m.played + "," + m.idx + ",'" + m.color + "')";
+ db.run(query);
+ }
});
},
// New move + fen update + score, potentially
// TODO: if newmove fail, takeback in GUI
router.put("/games", access.logged, access.ajax, (req,res) => {
- const gid = req.body.gid;
- const oppId = req.body.oppId;
+ const gid = req.body.gid;
const obj = req.body.newObj;
GameModel.update(gid, obj, (err) => {
if (!!err)
switch (obj.code)
{
case "pollclients":
+ const curPage = clients[sid].page;
socket.send(JSON.stringify({code:"pollclients",
sockIds: Object.keys(clients).filter(k =>
- k != sid && clients[k].page == obj.page)}));
+ k != sid && clients[k].page == curPage)}));
break;
case "pagechange":
notifyRoom(clients[sid].page, "disconnect");
notifyRoom(obj.page, "connect");
break;
case "askidentity":
- clients[obj.target].sock.send(
- JSON.stringify({code:"askidentity",from:sid}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"askidentity",from:sid}));
break;
case "askchallenge":
- clients[obj.target].sock.send(
- JSON.stringify({code:"askchallenge",from:sid}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"askchallenge",from:sid}));
break;
case "askgame":
- clients[obj.target].sock.send(
- JSON.stringify({code:"askgame",from:sid}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"askgame",from:sid}));
break;
case "identity":
- clients[obj.target].sock.send(
- JSON.stringify({code:"identity",user:obj.user}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"identity",user:obj.user}));
break;
case "refusechallenge":
- clients[obj.target].sock.send(
- JSON.stringify({code:"refusechallenge", cid:obj.cid, from:sid}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"refusechallenge", cid:obj.cid, from:sid}));
break;
case "deletechallenge":
- clients[obj.target].sock.send(
- JSON.stringify({code:"deletechallenge", cid:obj.cid, from:sid}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"deletechallenge", cid:obj.cid, from:sid}));
break;
case "newgame":
clients[obj.target].sock.send(JSON.stringify(
{code:"game", game:obj.game, from:sid}));
break;
case "newchat":
- clients[obj.target].sock.send(JSON.stringify({code:"newchat",msg:obj.msg}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"newchat",msg:obj.msg}));
break;
// TODO: WebRTC instead in this case (most demanding?)
case "newmove":
- clients[obj.target].sock.send(JSON.stringify({code:"newmove",move:obj.move}));
- break;
- case "ping":
- // If this code is reached, then obj.target is connected
- socket.send(JSON.stringify({code:"pong"}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"newmove",move:obj.move}));
break;
case "lastate":
- const oppId = obj.target;
- obj.oppid = sid; //I'm the opponent of my opponent
- clients[oppId].sock.send(JSON.stringify(obj));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"lastate", state:obj.state}));
break;
case "resign":
- clients[obj.target].sock.send(JSON.stringify({code:"resign"}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"resign"}));
break;
case "abort":
- clients[obj.target].sock.send(JSON.stringify({code:"abort",msg:obj.msg}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"abort",msg:obj.msg}));
break;
case "drawoffer":
- clients[obj.target].sock.send(JSON.stringify({code:"drawoffer"}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"drawoffer"}));
break;
case "draw":
- clients[obj.target].sock.send(JSON.stringify({code:"draw"}));
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"draw"}));
break;
}
});