const parsedFen = V.ParseFen(game.fenStart);
const firstMoveColor = parsedFen.turn;
this.firstMoveNumber = Math.floor(parsedFen.movesCount / 2);
+ let L = this.moves.length;
this.moves.forEach(move => {
// Strategy working also for multi-moves:
if (!Array.isArray(move)) move = [move];
- move.forEach(m => {
+ move.forEach((m,idx) => {
m.notation = this.vr.getNotation(m);
this.vr.play(m);
+ if (idx < L - 1 && this.vr.getCheckSquares(this.vr.turn).length > 0)
+ m.notation += "+";
});
});
if (firstMoveColor == "b") {
end: { x: -1, y: -1 },
fen: game.fenStart
});
+ L++;
}
this.positionCursorTo(this.moves.length - 1);
this.incheck = this.vr.getCheckSquares(this.vr.turn);
+ const score = this.vr.getCurrentScore();
+ if (["1-0","0-1"].includes(score))
+ this.moves[L - 1].notation += "#";
+ else if (this.vr.getCheckSquares(this.vr.turn).length > 0)
+ this.moves[L - 1].notation += "+";
},
positionCursorTo: function(index) {
this.cursor = index;
};
const computeScore = () => {
const score = this.vr.getCurrentScore();
+ if (!navigate) {
+ if (["1-0","0-1"].includes(score))
+ this.lastMove.notation += "#";
+ else if (this.vr.getCheckSquares(this.vr.turn).length > 0)
+ this.lastMove.notation += "+";
+ }
if (score != "*" && this.game.mode == "analyze") {
const message = getScoreMessage(score);
// Just show score on screen (allow undo)
this.incheck = this.vr.getCheckSquares(this.vr.turn);
this.emitFenIfAnalyze();
this.inMultimove = false;
- if (!noemit) var score = computeScore();
+ this.score = computeScore();
if (this.game.mode != "analyze") {
- const L = this.moves.length;
- if (!noemit)
+ if (!noemit) {
// Post-processing (e.g. computer play).
+ const L = this.moves.length;
// NOTE: always emit the score, even in unfinished,
// to tell Game::processMove() that it's not a received move.
- this.$emit("newmove", this.moves[L-1], { score: score });
- else {
+ this.$emit("newmove", this.moves[L-1], { score: this.score });
+ } else {
this.inPlay = false;
if (this.stackToPlay.length > 0)
// Move(s) arrived in-between
if (!light) {
this.lastMove = move[move.length-1];
this.incheck = this.vr.getCheckSquares(this.vr.turn);
- computeScore();
+ this.score = computeScore();
this.emitFenIfAnalyze();
}
this.cursor++;
data: function() {
return {
st: store.state,
- gameRef: {
- // rid = remote (socket) ID
- id: "",
- rid: ""
- },
+ // gameRef can point to a corr game, local game or remote live game
+ gameRef: "",
nextIds: [],
game: {}, //passed to BaseGame
// virtualClocks will be initialized from true game.clocks
this.atCreation();
} else {
// Same game ID
- this.gameRef.id = to.params["id"];
- this.gameRef.rid = to.query["rid"];
this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
- this.fetchGame();
+ this.loadGame(this.game);
}
}
},
},
atCreation: function() {
// 0] (Re)Set variables
- this.gameRef.id = this.$route.params["id"];
- // rid = remote ID to find an observed live game,
- // next = next corr games IDs to navigate faster
- // (Both might be undefined)
- this.gameRef.rid = this.$route.query["rid"];
+ this.gameRef = this.$route.params["id"];
+ // next = next corr games IDs to navigate faster (if applicable)
this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
// Always add myself to players' list
const my = this.st.user;
callback();
else
// Socket not ready yet (initial loading)
- // NOTE: it's important to call callback without arguments,
- // otherwise first arg is Websocket object and fetchGame fails.
+ // NOTE: first arg is Websocket object, unused here:
this.conn.onopen = () => callback();
};
- if (!this.gameRef.rid)
- // Game stored locally or on server
- this.fetchGame(null, () => socketInit(this.roomInit));
- else
- // Game stored remotely: need socket to retrieve it
- // NOTE: the callback "roomInit" will be lost, so we don't provide it.
- // --> It will be given when receiving "fullgame" socket event.
- socketInit(this.fetchGame);
+ this.fetchGame((game) => {
+ if (!!game)
+ this.loadVariantThenGame(game, () => socketInit(this.roomInit));
+ else
+ // Live game stored remotely: need socket to retrieve it
+ // NOTE: the callback "roomInit" will be lost, so we don't provide it.
+ // --> It will be given when receiving "fullgame" socket event.
+ socketInit(() => { this.send("askfullgame"); });
+ });
},
cleanBeforeDestroy: function() {
if (!!this.askLastate)
const currentUrl = document.location.href;
const doAskGame = () => {
if (document.location.href != currentUrl) return; //page change
- if (!this.gameRef.rid)
- // This is my game: just reload.
- this.fetchGame();
- else
- // Just ask fullgame again (once!), this is much simpler.
- // If this fails, the user could just reload page :/
- this.send("askfullgame", { target: this.gameRef.rid });
+ this.fetchGame((game) => {
+ if (!!game)
+ // This is my game: just reload.
+ this.loadGame(game);
+ else
+ // Just ask fullgame again (once!), this is much simpler.
+ // If this fails, the user could just reload page :/
+ this.send("askfullgame");
+ });
};
// Delay of at least 2s between two game requests
const now = Date.now();
players: this.game.players,
vid: this.game.vid,
cadence: this.game.cadence,
- score: this.game.score,
- rid: this.st.user.sid //useful in Hall if I'm an observer
+ score: this.game.score
};
this.send("game", { data: myGame, target: data.from });
}
break;
case "fullgame":
// Callback "roomInit" to poll clients only after game is loaded
- this.fetchGame(data.data, this.roomInit);
+ this.loadVariantThenGame(data.data, this.roomInit);
break;
case "asklastate":
// Sending informative last state if I played a move or score != "*"
!!this.game.mycolor &&
!receiveMyMove
) {
- GameStorage.update(this.gameRef.id, { drawOffer: "" });
+ GameStorage.update(this.gameRef, { drawOffer: "" });
}
}
this.$refs["basegame"].play(movePlus.move, "received", null, true);
case "drawoffer":
// NOTE: observers don't know who offered draw
this.drawOffer = "received";
+ if (this.game.type == "live") {
+ GameStorage.update(
+ this.gameRef,
+ { drawOffer: V.GetOppCol(this.game.mycolor) }
+ );
+ }
break;
case "rematchoffer":
// NOTE: observers don't know who offered rematch
this.rematchOffer = data.data ? "received" : "";
+ if (this.game.type == "live") {
+ GameStorage.update(
+ this.gameRef,
+ { rematchOffer: V.GetOppCol(this.game.mycolor) }
+ );
+ }
break;
case "newgame": {
// A game started, redirect if I'm playing in
) {
this.$router.push("/game/" + gameInfo.id);
} else {
- let urlRid = "";
- if (gameInfo.cadence.indexOf('d') === -1) {
- urlRid = "/?rid=";
- // Select sid of any of the online players:
- let onlineSid = [];
- gameInfo.players.forEach(p => {
- if (!!this.people[p.sid]) onlineSid.push(p.sid);
- });
- urlRid += onlineSid[Math.floor(Math.random() * onlineSid.length)];
- }
- this.rematchId = gameInfo.id + urlRid;
+ this.rematchId = gameInfo.id;
document.getElementById("modalInfo").checked = true;
}
break;
"PUT",
{
data: {
- gid: this.gameRef.id,
+ gid: this.gameRef,
newObj: obj
},
success: () => {
this.send("drawoffer");
if (this.game.type == "live") {
GameStorage.update(
- this.gameRef.id,
+ this.gameRef,
{ drawOffer: this.game.mycolor }
);
} else this.updateCorrGame({ drawOffer: this.game.mycolor });
this.send("rematchoffer", { data: true });
if (this.game.type == "live") {
GameStorage.update(
- this.gameRef.id,
+ this.gameRef,
{ rematchOffer: this.game.mycolor }
);
} else this.updateCorrGame({ rematchOffer: this.game.mycolor });
this.send("rematchoffer", { data: false });
if (this.game.type == "live") {
GameStorage.update(
- this.gameRef.id,
+ this.gameRef,
{ rematchOffer: '' }
);
} else this.updateCorrGame({ rematchOffer: 'n' });
const side = (this.game.mycolor == "w" ? "White" : "Black");
this.gameOver(score, side + " surrender");
},
- // 3 cases for loading a game:
- // - from indexedDB (running or completed live game I play)
- // - from server (one correspondance game I play[ed] or not)
- // - from remote peer (one live game I don't play, finished or not)
loadGame: function(game, callback) {
this.vr = new V(game.fen);
const gtype = this.getGameType(game);
}
if (!!callback) callback();
},
- fetchGame: function(game, callback) {
- const afterRetrieval = async (game) => {
- await import("@/variants/" + game.vname + ".js")
- .then((vModule) => {
- window.V = vModule[game.vname + "Rules"];
- this.loadGame(game, callback);
- });
- };
- if (!!game) {
- afterRetrieval(game);
- return;
- }
- if (this.gameRef.rid)
- // Remote live game: forgetting about callback func... (TODO: design)
- this.send("askfullgame", { target: this.gameRef.rid });
- else {
- // Local or corr game on server.
- // NOTE: afterRetrieval() is never called if game not found
- const gid = this.gameRef.id;
- if (Number.isInteger(gid) || !isNaN(parseInt(gid))) {
- // corr games identifiers are integers
- ajax(
- "/games",
- "GET",
- {
- data: { gid: gid },
- success: (res) => {
- res.game.moves.forEach(m => {
- m.squares = JSON.parse(m.squares);
- });
- afterRetrieval(res.game);
- }
+ loadVariantThenGame: async function(game, callback) {
+ await import("@/variants/" + game.vname + ".js")
+ .then((vModule) => {
+ window.V = vModule[game.vname + "Rules"];
+ this.loadGame(game, callback);
+ });
+ },
+ // 3 cases for loading a game:
+ // - from indexedDB (running or completed live game I play)
+ // - from server (one correspondance game I play[ed] or not)
+ // - from remote peer (one live game I don't play, finished or not)
+ fetchGame: function(callback) {
+ if (Number.isInteger(this.gameRef) || !isNaN(parseInt(this.gameRef))) {
+ // corr games identifiers are integers
+ ajax(
+ "/games",
+ "GET",
+ {
+ data: { gid: this.gameRef },
+ success: (res) => {
+ res.game.moves.forEach(m => {
+ m.squares = JSON.parse(m.squares);
+ });
+ callback(res.game);
}
- );
- }
- else
- // Local game
- GameStorage.get(this.gameRef.id, afterRetrieval);
- }
+ }
+ );
+ } else
+ // Local game (or live remote)
+ GameStorage.get(this.gameRef, callback);
},
re_setClocks: function() {
if (this.game.moves.length < 2 || this.game.score != "*") {
playMove(move, this.vr);
// The move is played: stop clock
clearInterval(this.clockUpdate);
- if (!data.score) {
- // Received move, score has not been computed in BaseGame (!!noemit)
- const score = this.vr.getCurrentScore();
- if (score != "*") this.gameOver(score);
- }
+ if (!data.score)
+ // Received move, score is computed in BaseGame, but maybe not yet.
+ // ==> Compute it here, although this is redundant (TODO)
+ data.score = this.vr.getCurrentScore();
+ if (data.score != "*") this.gameOver(data.score);
this.game.moves.push(move);
this.game.fen = this.vr.getFen();
if (this.game.type == "live") {
this.notifyMyGames(
"turn",
{
- gid: this.gameRef.id,
+ gid: this.gameRef,
turn: this.vr.turn
}
);
}
else {
const updateStorage = () => {
- GameStorage.update(this.gameRef.id, {
+ GameStorage.update(this.gameRef, {
fen: this.game.fen,
move: filtered_move,
moveIdx: origMovescount,
scoreMsg: scoreMsg
};
if (this.game.type == "live") {
- GameStorage.update(this.gameRef.id, scoreObj);
+ GameStorage.update(this.gameRef, scoreObj);
if (!!callback) callback();
}
else this.updateCorrGame(scoreObj, callback);
this.notifyMyGames(
"score",
{
- gid: this.gameRef.id,
+ gid: this.gameRef,
score: score
}
);
showGame: function(g) {
// NOTE: we are an observer, since only games I don't play are shown here
// ==> Moves sent by connected remote player(s) if live game
- let url = "/game/" + g.id;
- if (g.type == "live")
- url += "?rid=" + g.rids[Math.floor(Math.random() * g.rids.length)];
- this.$router.push(url);
+ this.$router.push("/game/" + g.id);
},
resetSocialColor: function() {
// TODO: this is called twice, once on opening an once on closing
case "connect":
case "gconnect": {
const page = data.page || "/";
- // Only ask game / challenges if first connexion:
- if (!this.people[data.from]) {
- this.people[data.from] = { pages: [{ path: page, focus: true }] };
- if (data.code == "connect")
+ if (data.code == "connect") {
+ // Ask challenges only on first connexion:
+ if (!this.people[data.from])
this.send("askchallenges", { target: data.from });
- // Ask game only if live:
- else if (!page.match(/\/[0-9]+$/))
- this.send("askgame", { target: data.from, page: page });
- } else {
+ }
+ // Ask game only if live:
+ else if (!page.match(/\/[0-9]+$/))
+ this.send("askgame", { target: data.from, page: page });
+ if (!this.people[data.from])
+ this.people[data.from] = { pages: [{ path: page, focus: true }] };
+ else {
// Append page if not already in list
if (!(this.people[data.from].pages.find(p => p.path == page)))
this.people[data.from].pages.push({ path: page, focus: true });
// the first reload won't have time to connect but will trigger a "close" event anyway.
// ==> Next check is required.
if (!this.people[data.from]) return;
+ const page = data.page || "/";
+ ArrayFun.remove(this.people[data.from].pages, p => p.path == page);
+ if (this.people[data.from].pages.length == 0)
+ this.$delete(this.people, data.from);
// Disconnect means no more tmpIds:
if (data.code == "disconnect") {
// Remove the live challenges sent by this player:
);
} else {
// Remove the matching live game if now unreachable
- const gid = data.page.match(/[a-zA-Z0-9]+$/)[0];
+ const gid = page.match(/[a-zA-Z0-9]+$/)[0];
// Corr games are always reachable:
if (!gid.match(/^[0-9]+$/)) {
- const gidx = this.games.findIndex(g => g.id == gid);
- // NOTE: gidx should always be >= 0 (TODO?)
- if (gidx >= 0) {
- const game = this.games[gidx];
- ArrayFun.remove(game.rids, rid => rid == data.from);
- if (game.rids.length == 0) this.games.splice(gidx, 1);
+ // Live games are reachable as long as someone is on the game page
+ if (Object.values(this.people).every(p =>
+ p.pages.every(pg => pg.path != page))) {
+ ArrayFun.remove(this.games, g => g.id == gid);
}
}
}
- const page = data.page || "/";
- ArrayFun.remove(this.people[data.from].pages, p => p.path == page);
- if (this.people[data.from].pages.length == 0)
- this.$delete(this.people, data.from);
break;
}
case "getfocus":
case "game": // Individual request
case "newgame": {
const game = data.data;
- // Ignore games where I play (will go in MyGames page)
- if (game.players.every(p =>
- p.sid != this.st.user.sid && p.id != this.st.user.id))
- {
- let locGame = this.games.find(g => g.id == game.id);
- if (!locGame) {
- let newGame = game;
- newGame.type = this.classifyObject(game);
- newGame.vname = this.getVname(game.vid);
- if (!game.score)
- // New game from Hall
- newGame.score = "*";
- newGame.rids = [game.rid];
- delete newGame["rid"];
- this.games.push(newGame);
- if (
- (newGame.type == "live" && this.gdisplay == "corr") ||
- (newGame.type == "corr" && this.gdisplay == "live")
- ) {
- document
- .getElementById("btnG" + newGame.type)
- .classList.add("somethingnew");
- }
- } else {
- // Append rid (if not already in list)
- if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid);
+ // Ignore games where I play (will go in MyGames page),
+ // and also games that I already received.
+ if (
+ game.players.every(p =>
+ p.sid != this.st.user.sid && p.id != this.st.user.id) &&
+ this.games.findIndex(g => g.id == game.id) == -1
+ ) {
+ let newGame = game;
+ newGame.type = this.classifyObject(game);
+ newGame.vname = this.getVname(game.vid);
+ if (!game.score)
+ // New game from Hall
+ newGame.score = "*";
+ this.games.push(newGame);
+ if (
+ (newGame.type == "live" && this.gdisplay == "corr") ||
+ (newGame.type == "corr" && this.gdisplay == "live")
+ ) {
+ document
+ .getElementById("btnG" + newGame.type)
+ .classList.add("somethingnew");
}
}
break;