<template lang="pug">
main
+ input#modalInfo.modal(type="checkbox")
+ div#infoDiv(
+ role="dialog"
+ data-checkbox="modalInfo"
+ )
+ .card.text-center
+ label.modal-close(for="modalInfo")
+ p
+ span {{ st.tr["Rematch in progress:"] }}
+ a(
+ :href="'#/game/' + rematchId"
+ onClick="document.getElementById('modalInfo').checked=false"
+ )
+ | {{ "#/game/" + rematchId }}
input#modalChat.modal(
type="checkbox"
@click="resetChatColor()"
.card
label.modal-close(for="modalChat")
#participants
- span {{ Object.keys(people).length + " " + st.tr["participant(s):"] }}
+ span {{ st.tr["Participant(s):"] }}
span(
v-for="p in Object.values(people)"
v-if="p.focus && !!p.name"
)
img(src="/images/icons/resign.svg")
button.tooltip(
- v-else-if="!!game.mycolor"
- @click="rematch()"
+ v-else
+ @click="clickRematch()"
+ :class="{['rematch-' + rematchOffer]: true}"
:aria-label="st.tr['Rematch']"
)
img(src="/images/icons/rematch.svg")
virtualClocks: [],
vr: null, //"variant rules" object initialized from FEN
drawOffer: "",
+ rematchId: "",
+ rematchOffer: "",
+ lastateAsked: false,
people: {}, //players + observers
- onMygames: [], //opponents (or me) on "MyGames" page
lastate: undefined, //used if opponent send lastate before game is ready
repeat: {}, //detect position repetition
curDiag: "", //for corr moves confirmation
},
mounted: function() {
document.addEventListener('visibilitychange', this.visibilityChange);
- document
- .getElementById("chatWrap")
- .addEventListener("click", processModalClick);
+ ["chatWrap", "infoDiv"].forEach(eltName => {
+ document.getElementById(eltName)
+ .addEventListener("click", processModalClick);
+ });
if ("ontouchstart" in window) {
// Disable tooltips on smartphones:
- document.getElementsByClassName("tooltip").forEach(elt => {
+ document.querySelectorAll("#aboveBoard .tooltip").forEach(elt => {
elt.classList.remove("tooltip");
});
}
this.virtualClocks = [[0,0], [0,0]];
this.vr = null;
this.drawOffer = "";
- this.onMygames = [];
+ this.lastateAsked = false;
+ this.rematchOffer = "";
this.lastate = undefined;
this.newChat = "";
this.roomInitialized = false;
params.socketUrl +
"/?sid=" +
this.st.user.sid +
+ "&id=" +
+ this.st.user.id +
"&tmpId=" +
getRandString() +
"&page=" +
)
);
},
+ getOppsid: function() {
+ let oppsid = this.game.oppsid;
+ if (!oppsid) {
+ oppsid = Object.keys(this.people).find(
+ sid => this.people[sid].id == this.game.oppid
+ );
+ }
+ // oppsid is useful only if opponent is online:
+ if (!!oppsid && !!this.people[oppsid]) return oppsid;
+ return null;
+ },
resetChatColor: function() {
// TODO: this is called twice, once on opening an once on closing
document.getElementById("chatBtn").classList.remove("somethingnew");
this.$set(this.game, "chats", []);
}
},
- // Notify turn after a new move (to opponent and me on MyGames page)
- notifyTurn: function(sid) {
- const player = this.people[sid];
- const colorIdx = this.game.players.findIndex(
- p => p.sid == sid || p.uid == player.id);
- const color = ["w","b"][colorIdx];
- const movesCount = this.game.moves.length;
- const yourTurn =
- (color == "w" && movesCount % 2 == 0) ||
- (color == "b" && movesCount % 2 == 1);
- this.send("turnchange", { target: sid, yourTurn: yourTurn });
+ getGameType: function(game) {
+ return game.cadence.indexOf("d") >= 0 ? "corr" : "live";
+ },
+ // Notify something after a new move (to opponent and me on MyGames page)
+ notifyMyGames: function(thing, data) {
+ this.send(
+ "notify" + thing,
+ {
+ data: data,
+ targets: this.game.players.map(p => {
+ return { sid: p.sid, uid: p.uid };
+ })
+ }
+ );
},
showNextGame: function() {
// Did I play in current game? If not, add it to nextIds list
this.$router.push(
"/game/" + nextGid + "/?next=" + JSON.stringify(this.nextIds));
},
- rematch: function() {
- alert("Unimplemented yet (soon :) )");
- // TODO: same logic as for draw, but re-click remove rematch offer (toggle)
- },
askGameAgain: function() {
this.gameIsLoading = true;
const currentUrl = document.location.href;
case "disconnect":
this.$delete(this.people, data.from);
break;
- case "mconnect": {
- // TODO: from MyGames page : send mconnect message with the list of gid (live and corr)
- // Either me (another tab) or opponent
- const sid = data.from;
- if (!this.onMygames.some(s => s == sid))
- {
- this.onMygames.push(sid);
- this.notifyTurn(sid); //TODO: this may require server ID (so, notify after receiving identity)
- }
- break;
- if (!this.people[sid])
- this.send("askidentity", { target: sid });
- }
- case "mdisconnect":
- ArrayFun.remove(this.onMygames, sid => sid == data.from);
- break;
case "getfocus": {
let player = this.people[data.from];
if (!!player) {
.filter(k =>
[
"id","fen","players","vid","cadence","fenStart","vname",
- "moves","clocks","initime","score","drawOffer"
+ "moves","clocks","initime","score","drawOffer","rematchOffer"
].includes(k))
.reduce(
(obj, k) => {
break;
case "asklastate":
// Sending informative last state if I played a move or score != "*"
- if (
- (this.game.moves.length > 0 && this.vr.turn != this.game.mycolor) ||
- this.game.score != "*" ||
- this.drawOffer == "sent"
- ) {
- // Send our "last state" informations to opponent
- const L = this.game.moves.length;
- const myIdx = ["w", "b"].indexOf(this.game.mycolor);
- const myLastate = {
- lastMove: L > 0 ? this.game.moves[L - 1] : undefined,
- clock: this.game.clocks[myIdx],
- // Since we played a move (or abort or resign),
- // only drawOffer=="sent" is possible
- drawSent: this.drawOffer == "sent",
- score: this.game.score,
- score: this.game.scoreMsg,
- movesCount: L,
- initime: this.game.initime[1 - myIdx] //relevant only if I played
- };
- this.send("lastate", { data: myLastate, target: data.from });
- } else {
- this.send("lastate", { data: {nothing: true}, target: data.from });
- }
+ // If the game or moves aren't loaded yet, delay the sending:
+ if (!this.game || !this.game.moves) this.lastateAsked = true;
+ else this.sendLastate(data.from);
break;
case "lastate": {
// Got opponent infos about last move
// NOTE: observers don't know who offered draw
this.drawOffer = "received";
break;
+ case "rematchoffer":
+ // NOTE: observers don't know who offered rematch
+ this.rematchOffer = data.data ? "received" : "";
+ break;
+ case "newgame": {
+ // A game started, redirect if I'm playing in
+ const gameInfo = data.data;
+ const gameType = this.getGameType(gameInfo);
+ if (
+ gameType == "live" &&
+ gameInfo.players.some(p => p.sid == this.st.user.sid)
+ ) {
+ this.addAndGotoLiveGame(gameInfo);
+ } else if (
+ gameType == "corr" &&
+ gameInfo.players.some(p => p.uid == this.st.user.id)
+ ) {
+ 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;
+ document.getElementById("modalInfo").checked = true;
+ }
+ break;
+ }
case "newchat":
this.newChat = data.data;
if (!document.getElementById("modalChat").checked)
}
);
},
+ sendLastate: function(target) {
+ if (
+ (this.game.moves.length > 0 && this.vr.turn != this.game.mycolor) ||
+ this.game.score != "*" ||
+ this.drawOffer == "sent" ||
+ this.rematchOffer == "sent"
+ ) {
+ // Send our "last state" informations to opponent
+ const L = this.game.moves.length;
+ const myIdx = ["w", "b"].indexOf(this.game.mycolor);
+ const myLastate = {
+ lastMove: L > 0 ? this.game.moves[L - 1] : undefined,
+ clock: this.game.clocks[myIdx],
+ // Since we played a move (or abort or resign),
+ // only drawOffer=="sent" is possible
+ drawSent: this.drawOffer == "sent",
+ rematchSent: this.rematchOffer == "sent",
+ score: this.game.score,
+ scoreMsg: this.game.scoreMsg,
+ movesCount: L,
+ initime: this.game.initime[1 - myIdx] //relevant only if I played
+ };
+ this.send("lastate", { data: myLastate, target: target });
+ } else {
+ this.send("lastate", { data: {nothing: true}, target: target });
+ }
+ },
// lastate was received, but maybe game wasn't ready yet:
processLastate: function() {
const data = this.lastate;
this.processMove(data.lastMove, { clock: data.clock });
}
if (data.drawSent) this.drawOffer = "received";
+ if (data.rematchSent) this.rematchOffer = "received";
if (data.score != "*") {
this.drawOffer = "";
if (this.game.score == "*")
} else this.updateCorrGame({ drawOffer: this.game.mycolor });
}
},
+ addAndGotoLiveGame: function(gameInfo, callback) {
+ const game = Object.assign(
+ {},
+ gameInfo,
+ {
+ // (other) Game infos: constant
+ fenStart: gameInfo.fen,
+ vname: this.game.vname,
+ created: Date.now(),
+ // Game state (including FEN): will be updated
+ moves: [],
+ clocks: [-1, -1], //-1 = unstarted
+ initime: [0, 0], //initialized later
+ score: "*"
+ }
+ );
+ GameStorage.add(game, (err) => {
+ // No error expected.
+ if (!err) {
+ if (this.st.settings.sound)
+ new Audio("/sounds/newgame.flac").play().catch(() => {});
+ if (!!callback) callback();
+ this.$router.push("/game/" + gameInfo.id);
+ }
+ });
+ },
+ clickRematch: function() {
+ if (!this.game.mycolor) return; //I'm just spectator
+ if (this.rematchOffer == "received") {
+ // Start a new game!
+ let gameInfo = {
+ id: getRandString(), //ignored if corr
+ fen: V.GenRandInitFen(this.game.randomness),
+ players: this.game.players.reverse(),
+ vid: this.game.vid,
+ cadence: this.game.cadence
+ };
+ const notifyNewGame = () => {
+ let oppsid = this.getOppsid(); //may be null
+ this.send("rnewgame", { data: gameInfo, oppsid: oppsid });
+ // Also to MyGames page:
+ this.notifyMyGames("newgame", gameInfo);
+ };
+ if (this.game.type == "live")
+ this.addAndGotoLiveGame(gameInfo, notifyNewGame);
+ else {
+ // corr game
+ ajax(
+ "/games",
+ "POST",
+ {
+ // cid is useful to delete the challenge:
+ data: { gameInfo: gameInfo },
+ success: (response) => {
+ gameInfo.id = response.gameId;
+ notifyNewGame();
+ this.$router.push("/game/" + response.gameId);
+ }
+ }
+ );
+ }
+ } else if (this.rematchOffer == "") {
+ this.rematchOffer = "sent";
+ this.send("rematchoffer", { data: true });
+ if (this.game.type == "live") {
+ GameStorage.update(
+ this.gameRef.id,
+ { rematchOffer: this.game.mycolor }
+ );
+ } else this.updateCorrGame({ rematchOffer: this.game.mycolor });
+ } else if (this.rematchOffer == "sent") {
+ // Toggle rematch offer (on --> off)
+ this.rematchOffer = "";
+ this.send("rematchoffer", { data: false });
+ if (this.game.type == "live") {
+ GameStorage.update(
+ this.gameRef.id,
+ { rematchOffer: '' }
+ );
+ } else this.updateCorrGame({ rematchOffer: 'n' });
+ }
+ },
abortGame: function() {
if (!this.game.mycolor || !confirm(this.st.tr["Terminate game?"])) return;
this.gameOver("?", "Stop");
// - 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) {
- const afterRetrieval = async game => {
+ const afterRetrieval = async (game) => {
const vModule = await import("@/variants/" + game.vname + ".js");
window.V = vModule.VariantRules;
this.vr = new V(game.fen);
- const gtype = game.cadence.indexOf("d") >= 0 ? "corr" : "live";
+ const gtype = this.getGameType(game);
const tc = extractTime(game.cadence);
const myIdx = game.players.findIndex(p => {
return p.sid == this.st.user.sid || p.uid == this.st.user.id;
if (game.score == "*") {
// Set clocks + initime
game.initime = [0, 0];
- if (L >= 1) {
- const gameLastupdate = game.moves[L-1].played;
- game.initime[L % 2] = gameLastupdate;
- if (L >= 2) {
- game.clocks[L % 2] =
- tc.mainTime - (Date.now() - gameLastupdate) / 1000;
- }
- }
+ if (L >= 1) game.initime[L % 2] = game.moves[L-1].played;
+ // NOTE: game.clocks shouldn't be computed right now:
+ // job will be done in re_setClocks() called soon below.
}
// Sort chat messages from newest to oldest
game.chats.sort((c1, c2) => {
}
}
}
+ // TODO: merge next 2 "if" conditions
if (!!game.drawOffer) {
if (game.drawOffer == "t")
// Three repetitions
}
}
}
+ if (!!game.rematchOffer) {
+ if (myIdx < 0) this.rematchOffer = "received";
+ else {
+ // I play in this game:
+ if (
+ (game.rematchOffer == "w" && myIdx == 0) ||
+ (game.rematchOffer == "b" && myIdx == 1)
+ )
+ this.rematchOffer = "sent";
+ else this.rematchOffer = "received";
+ }
+ }
this.repeat = {}; //reset: scan past moves' FEN:
let repIdx = 0;
let vr_tmp = new V(game.fenStart);
},
game,
);
- if (this.gameIsLoading)
- // Re-load game because we missed some moves:
- // artificially reset BaseGame (required if moves arrived in wrong order)
- this.$refs["basegame"].re_setVariables();
- else {
+ this.$refs["basegame"].re_setVariables(this.game);
+ if (!this.gameIsLoading) {
// Initial loading:
this.gotMoveIdx = game.moves.length - 1;
// If we arrive here after 'nextGame' action, the board might be hidden
// Did lastate arrive before game was rendered?
if (this.lastate) this.processLastate();
});
+ if (this.lastateAsked) {
+ this.lastateAsked = false;
+ this.sendLastate(game.oppsid);
+ }
if (this.gameIsLoading) {
this.gameIsLoading = false;
if (this.gotMoveIdx >= game.moves.length)
const score = this.vr.getCurrentScore();
if (score != "*") this.gameOver(score);
}
-// TODO: notifyTurn: "changeturn" message
this.game.moves.push(move);
this.game.fen = this.vr.getFen();
if (this.game.type == "live") {
// NOTE: 'var' to see that variable outside this block
var filtered_move = getFilteredMove(move);
}
+ if (moveCol == this.game.mycolor && !data.receiveMyMove) {
+ // Notify turn on MyGames page:
+ this.notifyMyGames(
+ "turn",
+ {
+ gid: this.gameRef.id,
+ turn: this.vr.turn
+ }
+ );
+ }
// Since corr games are stored at only one location, update should be
// done only by one player for each move:
if (
clearInterval(this.retrySendmove);
return;
}
- let oppsid = this.game.players[nextIdx].sid;
- if (!oppsid) {
- oppsid = Object.keys(this.people).find(
- sid => this.people[sid].id == this.game.players[nextIdx].uid
- );
- }
- if (!oppsid || !this.people[oppsid])
+ const oppsid = this.getOppsid();
+ if (!oppsid)
// Opponent is disconnected: he'll ask last state
clearInterval(this.retrySendmove);
else {
else this.updateCorrGame(scoreObj, callback);
// Notify the score to main Hall. TODO: only one player (currently double send)
this.send("result", { gid: this.game.id, score: score });
+ // Also to MyGames page (TODO: doubled as well...)
+ this.notifyMyGames(
+ "score",
+ {
+ gid: this.gameRef.id,
+ score: score
+ }
+ );
}
else if (!!callback) callback();
}
</script>
<style lang="sass" scoped>
+#infoDiv > .card
+ padding: 15px 0
+ max-width: 430px
+
.connected
background-color: lightgreen
.draw-threerep, .draw-threerep:hover
background-color: #e4d1fc
+.rematch-sent, .rematch-sent:hover
+ background-color: lightyellow
+
+.rematch-received, .rematch-received:hover
+ background-color: lightgreen
+
.somethingnew
background-color: #c5fefe