If a pawn taken: direction of the capturer.
+ Maxima, Interweave, Roccoco, Dynamo, Synchrone
+Synchrone Chess: allow to anticipate en-passant capture as well :)
S-chess https://en.wikipedia.org/wiki/Seirawan_chess
};
},
watch: {
- // game initial FEN changes when a new game starts
- "game.fenStart": function() {
- this.re_setVariables();
+ // game initial FEN changes when a new game starts.
+ // NOTE: when game ID change on Game page, fenStart may be temporarily undefined
+ "game.fenStart": function(fenStart) {
+ if (!!fenStart) this.re_setVariables();
},
},
computed: {
},
showEndgameMsg: function(message) {
this.endgameMessage = message;
- let modalBox = document.getElementById("modalEog");
- modalBox.checked = true;
- setTimeout(() => {
- modalBox.checked = false;
- }, 2000);
+ document.getElementById("modalEog").checked = true;
},
// Animate an elementary move
animateMove: function(move, callback) {
<script>
import { store } from "@/store";
import { GameStorage } from "@/utils/gameStorage";
+import { ajax } from "@/utils/ajax";
export default {
name: "my-game-list",
props: ["games", "showBoth"],
},
deleteGame: function(game, e) {
if (
- game.score != "*" &&
+ // My game ?
game.players.some(p =>
p.sid == this.st.user.sid ||
p.uid == this.st.user.id
)
) {
- if (confirm(this.st.tr["Remove game?"])) {
- GameStorage.remove(
- game.id,
- () => {
- this.$set(this.deleted, game.id, true);
- }
- );
+ const message =
+ game.score != "*"
+ ? "Remove game?"
+ : "Abort and remove game?";
+ if (confirm(this.st.tr[message])) {
+ const afterDelete = () => {
+ if (game.score == "*") this.$emit("abort", game);
+ this.$set(this.deleted, game.id, true);
+ };
+ if (game.type == "live")
+ // Effectively remove game:
+ GameStorage.remove(game.id, afterDelete);
+ else {
+ const mySide =
+ game.players[0].uid == this.st.user.id
+ ? "White"
+ : "Black";
+ game["deletedBy" + mySide] = true;
+ // Mark the game for deletion on server
+ // If both people mark it, it is deleted
+ ajax(
+ "/games",
+ "PUT",
+ {
+ gid: game.id,
+ newObj: { removeFlag: true }
+ },
+ afterDelete
+ );
+ }
}
e.stopPropagation();
}
type="checkbox"
v-model="st.settings.sound"
)
+ fieldset
+ label(for="setGotonext")
+ | {{ st.tr["Show next game after move?"] }}
+ input#setGotonext(
+ type="checkbox"
+ v-model="st.settings.gotonext"
+ )
fieldset
label(for="setRandomness") {{ st.tr["Randomness against computer"] }}
select#setRandomness(v-model="st.settings.randomness")
sound: getItemDefaultTrue("sound"),
hints: getItemDefaultTrue("hints"),
highlight: getItemDefaultTrue("highlight"),
+ gotonext: getItemDefaultTrue("gotonext"),
randomness: parseInt(localStorage.getItem("randomness"))
};
if (isNaN(this.state.settings.randomness))
export const translations = {
Abort: "Abort",
+ "Abort and remove game?": "Abort and Remove game?",
About: "About",
"Accept draw?": "Accept draw?",
"Accept challenge?": "Accept challenge?",
"Are you sure?": "Are you sure?",
"Asymmetric random": "Asymmetric random",
"Authentication successful!": "Authentication successful!",
- "Back to Hall in 3 seconds...": "Back to Hall in 3 seconds...",
"Back to list": "Back to list",
"Black to move": "Black to move",
"Black surrender": "Black surrender",
"Self-challenge is forbidden": "Self-challenge is forbidden",
"Send challenge": "Send challenge",
Settings: "Settings",
+ "Show next game after move?": "Show next game after move?",
"Show possible moves?": "Show possible moves?",
"Show solution": "Show solution",
Solution: "Solution",
export const translations = {
Abort: "Terminar",
+ "Abort and remove game?": "¿Terminar y eliminar la partida?",
About: "Acerca de",
"Accept draw?": "¿Acceptar tablas?",
"Accept challenge?": "¿Acceptar el desafÃo?",
"Are you sure?": "¿Está usted seguro?",
"Asymmetric random": "Aleatorio asimétrico",
"Authentication successful!": "¡Autenticación exitosa!",
- "Back to Hall in 3 seconds...": "Regreso al salón en 3 segundos...",
"Back to list": "Volver a la lista",
"Black to move": "Juegan las negras",
"Black surrender": "Las negras abandonan",
"Self-challenge is forbidden": "Auto desafÃo está prohibido",
"Send challenge": "Enviar desafÃo",
Settings: "Configuraciones",
+ "Show next game after move?": "¿Mostrar la siguiente partida después de una jugada?",
"Show possible moves?": "¿Mostrar posibles movimientos?",
"Show solution": "Mostrar la solución",
Solution: "Solución",
export const translations = {
Abort: "Arrêter",
+ "Abort and remove game?": "Arrêter et supprimer la partie ?",
About: "À propos",
"Accept draw?": "Accepter la nulle ?",
"Accept challenge?": "Accepter le défi ?",
"Authentication successful!": "Authentification réussie !",
"Are you sure?": "Étes vous sûr?",
"Asymmetric random": "Aléatoire asymétrique",
- "Back to Hall in 3 seconds...": "Retour au Hall dans 3 secondes...",
"Back to list": "Retour à la liste",
"Black to move": "Trait aux noirs",
"Black surrender": "Les noirs abandonnent",
"Self-challenge is forbidden": "Interdit de s'auto-défier",
"Send challenge": "Envoyer défi",
Settings: "Réglages",
+ "Show next game after move?": "Montrer la partie suivante après un coup ?",
"Show possible moves?": "Montrer les coups possibles ?",
"Show solution": "Montrer la solution",
Solution: "Solution",
- "Sound alert when game starts?": "Alerte sonore quand une partie démarre?",
+ "Sound alert when game starts?": "Alerte sonore quand une partie démarre ?",
Stop: "Arrêt",
"Stop game": "Arrêter la partie",
Subject: "Sujet",
// score: string (several options; '*' == running),
// }
-import { ajax } from "@/utils/ajax";
import { store } from "@/store";
function dbOperation(callback) {
});
},
- // TODO: also option to takeback a move ?
// obj: chat, move, fen, clocks, score[Msg], initime, ...
update: function(gameId, obj) {
- if (Number.isInteger(gameId) || !isNaN(parseInt(gameId))) {
- // corr: only move, fen and score
- ajax("/games", "PUT", {
- gid: gameId,
- newObj: {
- // Some fields may be undefined:
- chat: obj.chat,
- move: obj.move,
- fen: obj.fen,
- score: obj.score,
- scoreMsg: obj.scoreMsg,
- drawOffer: obj.drawOffer
+ // live
+ dbOperation((err,db) => {
+ let objectStore = db
+ .transaction("games", "readwrite")
+ .objectStore("games");
+ objectStore.get(gameId).onsuccess = function(event) {
+ // Ignoring error silently: shouldn't happen now. TODO?
+ if (event.target.result) {
+ let game = event.target.result;
+ // Hidden tabs are delayed, to prevent multi-updates:
+ if (obj.moveIdx < game.moves.length) return;
+ Object.keys(obj).forEach(k => {
+ if (k == "move") game.moves.push(obj[k]);
+ else game[k] = obj[k];
+ });
+ objectStore.put(game); //save updated data
}
- });
- } else {
- // live
- dbOperation((err,db) => {
- let objectStore = db
- .transaction("games", "readwrite")
- .objectStore("games");
- objectStore.get(gameId).onsuccess = function(event) {
- // Ignoring error silently: shouldn't happen now. TODO?
- if (event.target.result) {
- let game = event.target.result;
- // Hidden tabs are delayed, to prevent multi-updates:
- if (obj.moveIdx < game.moves.length) return;
- Object.keys(obj).forEach(k => {
- if (k == "move") game.moves.push(obj[k]);
- else game[k] = obj[k];
- });
- objectStore.put(game); //save updated data
- }
- };
- });
- }
+ };
+ });
},
// Retrieve all local games (running, completed, imported...)
// Retrieve any game from its identifiers (locally or on server)
// NOTE: need callback because result is obtained asynchronously
get: function(gameId, callback) {
- // corr games identifiers are integers
- if (Number.isInteger(gameId) || !isNaN(parseInt(gameId))) {
- ajax("/games", "GET", { gid: gameId }, res => {
- let game = res.game;
- game.moves.forEach(m => {
- m.squares = JSON.parse(m.squares);
- });
- callback(game);
- });
- }
- else {
- // Local game
- dbOperation((err,db) => {
- let objectStore = db.transaction("games").objectStore("games");
- objectStore.get(gameId).onsuccess = function(event) {
- if (event.target.result)
- callback(event.target.result);
- };
- });
- }
+ // Local game
+ dbOperation((err,db) => {
+ let objectStore = db.transaction("games").objectStore("games");
+ objectStore.get(gameId).onsuccess = function(event) {
+ if (event.target.result)
+ callback(event.target.result);
+ };
+ });
},
// Delete a game in indexedDB
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
div(v-if="authOk")
p {{ st.tr["Authentication successful!"] }}
- p {{ st.tr["Back to Hall in 3 seconds..."] }}
</template>
<script>
this.st.user.notify = res.notify;
localStorage["myname"] = res.name;
localStorage["myid"] = res.id;
- setTimeout(() => {
- this.$router.replace("/");
- }, 3000);
}
);
}
span.anonymous(v-if="Object.values(people).some(p => !p.name && p.id === 0)")
| + @nonymous
Chat(
+ ref="chatcomp"
:players="game.players"
:pastChats="game.chats"
:newChat="newChat"
)
span.time-left {{ virtualClocks[0][0] }}
span.time-separator(v-if="!!virtualClocks[0][1]") :
- span.time-right(v-if="!!virtualClocks[0][1]") {{ virtualClocks[0][1] }}
+ span.time-right(v-if="!!virtualClocks[0][1]")
+ | {{ virtualClocks[0][1] }}
span.split-names -
span.name(:class="{connected: isConnected(1)}")
| {{ game.players[1].name || "@nonymous" }}
)
span.time-left {{ virtualClocks[1][0] }}
span.time-separator(v-if="!!virtualClocks[1][1]") :
- span.time-right(v-if="!!virtualClocks[1][1]") {{ virtualClocks[1][1] }}
+ span.time-right(v-if="!!virtualClocks[1][1]")
+ | {{ virtualClocks[1][1] }}
BaseGame(
ref="basegame"
:game="game"
BaseGame,
Chat
},
- // gameRef: to find the game in (potentially remote) storage
data: function() {
return {
st: store.state,
id: "",
rid: ""
},
- game: {
- // Passed to BaseGame
- players: [{ name: "" }, { name: "" }],
- chats: [],
- rendered: false
- },
nextIds: [],
- virtualClocks: [[0,0], [0,0]], //initialized with true game.clocks
+ game: {}, //passed to BaseGame
+ // virtualClocks will be initialized from true game.clocks
+ virtualClocks: [],
vr: null, //"variant rules" object initialized from FEN
drawOffer: "",
people: {}, //players + observers
// If newmove got no pingback, send again:
opponentGotMove: false,
connexionString: "",
+ // Intervals from setInterval():
+ // TODO: limit them to e.g. 3 retries ?!
+ askIfPeerConnected: null,
+ askLastate: null,
+ retrySendmove: null,
+ clockUpdate: null,
// Related to (killing of) self multi-connects:
newConnect: {},
killed: {}
};
},
watch: {
- $route: function(to) {
- this.gameRef.id = to.params["id"];
- this.gameRef.rid = to.query["rid"];
- this.loadGame();
+ $route: function(to, from) {
+ if (from.params["id"] != to.params["id"]) {
+ // Change everything:
+ this.cleanBeforeDestroy();
+ 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.loadGame();
+ }
}
},
// NOTE: some redundant code with Hall.vue (mostly related to people array)
created: function() {
- // Always add myself to players' list
- const my = this.st.user;
- this.$set(this.people, my.sid, { id: my.id, name: my.name });
- 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.nextIds = JSON.parse(this.$route.query["next"] || "[]");
- // Initialize connection
- this.connexionString =
- params.socketUrl +
- "/?sid=" +
- this.st.user.sid +
- "&tmpId=" +
- getRandString() +
- "&page=" +
- // Discard potential "/?next=[...]" for page indication:
- encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]);
- this.conn = new WebSocket(this.connexionString);
- this.conn.onmessage = this.socketMessageListener;
- this.conn.onclose = this.socketCloseListener;
- // Socket init required before loading remote game:
- const socketInit = callback => {
- if (!!this.conn && this.conn.readyState == 1)
- // 1 == OPEN state
- callback();
- else
- // Socket not ready yet (initial loading)
- // NOTE: it's important to call callback without arguments,
- // otherwise first arg is Websocket object and loadGame fails.
- this.conn.onopen = () => callback();
- };
- if (!this.gameRef.rid)
- // Game stored locally or on server
- this.loadGame(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.loadGame);
+ this.atCreation();
},
mounted: function() {
document
}
},
beforeDestroy: function() {
- this.send("disconnect");
+ this.cleanBeforeDestroy();
},
methods: {
+ 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.nextIds = JSON.parse(this.$route.query["next"] || "[]");
+ // Always add myself to players' list
+ const my = this.st.user;
+ this.$set(this.people, my.sid, { id: my.id, name: my.name });
+ this.game = {
+ players: [{ name: "" }, { name: "" }],
+ chats: [],
+ rendered: false
+ };
+ let chatComp = this.$refs["chatcomp"];
+ if (!!chatComp) chatComp.chats = [];
+ this.virtualClocks = [[0,0], [0,0]];
+ this.vr = null;
+ this.drawOffer = "";
+ this.onMygames = [];
+ this.lastate = undefined;
+ this.newChat = "";
+ this.roomInitialized = false;
+ this.askGameTime = 0;
+ this.gameIsLoading = false;
+ this.gotLastate = false;
+ this.gotMoveIdx = -1;
+ this.opponentGotMove = false;
+ this.askIfPeerConnected = null;
+ this.askLastate = null;
+ this.retrySendmove = null;
+ this.clockUpdate = null;
+ this.newConnect = {};
+ this.killed = {};
+ // 1] Initialize connection
+ this.connexionString =
+ params.socketUrl +
+ "/?sid=" +
+ this.st.user.sid +
+ "&tmpId=" +
+ getRandString() +
+ "&page=" +
+ // Discard potential "/?next=[...]" for page indication:
+ encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]);
+ this.conn = new WebSocket(this.connexionString);
+ this.conn.onmessage = this.socketMessageListener;
+ this.conn.onclose = this.socketCloseListener;
+ // Socket init required before loading remote game:
+ const socketInit = callback => {
+ if (!!this.conn && this.conn.readyState == 1)
+ // 1 == OPEN state
+ callback();
+ else
+ // Socket not ready yet (initial loading)
+ // NOTE: it's important to call callback without arguments,
+ // otherwise first arg is Websocket object and loadGame fails.
+ this.conn.onopen = () => callback();
+ };
+ if (!this.gameRef.rid)
+ // Game stored locally or on server
+ this.loadGame(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.loadGame);
+ },
+ cleanBeforeDestroy: function() {
+ if (!!this.askIfPeerConnected)
+ clearInterval(this.askIfPeerConnected);
+ if (!!this.askLastate)
+ clearInterval(this.askLastate);
+ if (!!this.retrySendmove)
+ clearInterval(this.retrySendmove);
+ if (!!this.clockUpdate)
+ clearInterval(this.clockUpdate);
+ this.send("disconnect");
+ },
roomInit: function() {
if (!this.roomInitialized) {
// Notify the room only now that I connected, because
this.send("newchat", { data: chat });
// NOTE: anonymous chats in corr games are not stored on server (TODO?)
if (this.game.type == "corr" && this.st.user.id > 0)
- GameStorage.update(this.gameRef.id, { chat: chat });
+ this.updateCorrGame({ chat: chat });
},
clearChat: function() {
// Nothing more to do if game is live (chats not recorded)
this.send("turnchange", { target: sid, yourTurn: yourTurn });
},
showNextGame: function() {
+ if (this.nextIds.length == 0) return;
// Did I play in current game? If not, add it to nextIds list
if (this.game.score == "*" && this.vr.turn == this.game.mycolor)
this.nextIds.unshift(this.game.id);
},
askGameAgain: function() {
this.gameIsLoading = true;
+ const currentUrl = document.location.href;
const doAskGame = () => {
+ if (currentUrl != document.location.href) return; //page change
if (!this.gameRef.rid)
// This is my game: just reload.
this.loadGame();
else {
// Just ask fullgame again (once!), this is much simpler.
// If this fails, the user could just reload page :/
- let self = this;
- (function askIfPeerConnected() {
- if (!!self.people[self.gameRef.rid])
- self.send("askfullgame", { target: self.gameRef.rid });
- else setTimeout(askIfPeerConnected, 1000);
- })();
+ this.send("askfullgame", { target: this.gameRef.rid });
+ this.askIfPeerConnected = setInterval(
+ () => {
+ if (
+ !!this.people[this.gameRef.rid] &&
+ currentUrl != document.location.href
+ ) {
+ this.send("askfullgame", { target: this.gameRef.rid });
+ clearInterval(this.askIfPeerConnected);
+ }
+ },
+ 1000
+ );
}
};
// Delay of at least 2s between two game requests
this.game.score == "*" &&
this.game.players.some(p => p.sid == user.sid)
) {
- let self = this;
- (function askLastate() {
- self.send("asklastate", { target: user.sid });
- setTimeout(
- () => {
- // Ask until we got a reply (or opponent disconnect):
- if (!self.gotLastate && !!self.people[user.sid])
- askLastate();
- },
- 1000
- );
- })();
+ this.send("asklastate", { target: user.sid });
+ this.askLastate = setInterval(
+ () => {
+ // Ask until we got a reply (or opponent disconnect):
+ if (!this.gotLastate && !!this.people[user.sid])
+ this.send("asklastate", { target: user.sid });
+ else
+ clearInterval(this.askLastate);
+ },
+ 1000
+ );
}
}
break;
this.conn.addEventListener("message", this.socketMessageListener);
this.conn.addEventListener("close", this.socketCloseListener);
},
+ updateCorrGame: function(obj) {
+ ajax(
+ "/games",
+ "PUT",
+ {
+ gid: this.gameRef.id,
+ newObj: obj
+ }
+ );
+ },
// lastate was received, but maybe game wasn't ready yet:
processLastate: function() {
const data = this.lastate;
if (!confirm(this.st.tr["Offer draw?"])) return;
this.drawOffer = "sent";
this.send("drawoffer");
- GameStorage.update(this.gameRef.id, { drawOffer: this.game.mycolor });
+ if (this.game.type == "live") {
+ GameStorage.update(
+ this.gameRef.id,
+ { drawOffer: this.game.mycolor }
+ );
+ } else this.updateCorrGame({ drawOffer: this.game.mycolor });
}
},
abortGame: function() {
// Re-load game because we missed some moves:
// artificially reset BaseGame (required if moves arrived in wrong order)
this.$refs["basegame"].re_setVariables();
+ else
+ // Initial loading:
+ this.gotMoveIdx = game.moves.length - 1;
this.re_setClocks();
this.$nextTick(() => {
this.game.rendered = true;
// Remote live game: forgetting about callback func... (TODO: design)
this.send("askfullgame", { target: this.gameRef.rid });
} else {
- // Local or corr game
+ // Local or corr game on server.
// NOTE: afterRetrieval() is never called if game not found
- GameStorage.get(this.gameRef.id, afterRetrieval);
+ const gid = this.gameRef.id;
+ if (Number.isInteger(gid) || !isNaN(parseInt(gid))) {
+ // corr games identifiers are integers
+ ajax("/games", "GET", { gid: gid }, res => {
+ let g = res.game;
+ g.moves.forEach(m => {
+ m.squares = JSON.parse(m.squares);
+ });
+ afterRetrieval(g);
+ });
+ }
+ else
+ // Local game
+ GameStorage.get(this.gameRef.id, afterRetrieval);
}
},
re_setClocks: function() {
i == colorIdx ? (Date.now() - this.game.initime[colorIdx]) / 1000 : 0;
return ppt(this.game.clocks[i] - removeTime).split(':');
});
- let clockUpdate = setInterval(() => {
+ this.clockUpdate = setInterval(() => {
if (
countdown < 0 ||
this.game.moves.length > currentMovesCount ||
this.game.score != "*"
) {
- clearInterval(clockUpdate);
+ clearInterval(this.clockUpdate);
if (countdown < 0)
this.gameOver(
currentTurn == "w" ? "0-1" : "1-0",
else this.game.clocks[colorIdx] = extractTime(this.game.cadence).mainTime;
// data.initime is set only when I receive a "lastate" move from opponent
this.game.initime[nextIdx] = data.initime || Date.now();
- this.re_setClocks();
// If repetition detected, consider that a draw offer was received:
const fenObj = this.vr.getFenForRepeat();
this.repeat[fenObj] = this.repeat[fenObj] ? this.repeat[fenObj] + 1 : 1;
if (this.repeat[fenObj] >= 3) this.drawOffer = "threerep";
else if (this.drawOffer == "threerep") this.drawOffer = "";
- // Since corr games are stored at only one location, update should be
- // done only by one player for each move:
if (!!this.game.mycolor && !data.receiveMyMove) {
// NOTE: 'var' to see that variable outside this block
var filtered_move = getFilteredMove(move);
}
+ // Since corr games are stored at only one location, update should be
+ // done only by one player for each move:
if (
!!this.game.mycolor &&
!data.receiveMyMove &&
break;
}
if (this.game.type == "corr") {
- GameStorage.update(this.gameRef.id, {
+ // corr: only move, fen and score
+ this.updateCorrGame({
fen: this.game.fen,
move: {
squares: filtered_move,
this.opponentGotMove = false;
this.send("newmove", {data: sendMove});
// If the opponent doesn't reply gotmove soon enough, re-send move:
- let retrySendmove = setInterval(
+ this.retrySendmove = setInterval(
() => {
if (this.opponentGotMove) {
- clearInterval(retrySendmove);
+ clearInterval(this.retrySendmove);
return;
}
let oppsid = this.game.players[nextIdx].sid;
}
if (!oppsid || !this.people[oppsid])
// Opponent is disconnected: he'll ask last state
- clearInterval(retrySendmove);
+ clearInterval(this.retrySendmove);
else this.send("newmove", {data: sendMove, target: oppsid});
},
1000
() => {
document.getElementById("modalConfirm").checked = false;
doProcessMove();
+ if (this.st.settings.gotonext) this.showNextGame();
+ else this.re_setClocks();
}
);
- this.vr.play(move);
- const parsedFen = V.ParseFen(this.vr.getFen());
- this.vr.undo(move);
+ // PlayOnBoard is enough, and more appropriate for Synchrone Chess
+ V.PlayOnBoard(this.vr.board, move);
+ const position = this.vr.getBaseFen();
+ V.UndoOnBoard(this.vr.board, move);
this.curDiag = getDiagram({
- position: parsedFen.position,
- orientation: this.game.mycolor
+ position: position,
+ orientation: V.CanFlip ? this.game.mycolor : "w"
});
document.getElementById("modalConfirm").checked = true;
}
- else doProcessMove();
+ else {
+ doProcessMove();
+ this.re_setClocks();
+ }
},
cancelMove: function() {
document.getElementById("modalConfirm").checked = false;
});
if (myIdx >= 0) {
// OK, I play in this game
- GameStorage.update(this.gameRef.id, {
+ const scoreObj = {
score: score,
scoreMsg: scoreMsg
- });
+ };
+ if (this.Game.type == "live")
+ GameStorage.update(this.gameRef.id, scoreObj);
+ else this.updateCorrGame(scoreObj);
// Notify the score to main Hall. TODO: only one player (currently double send)
this.send("result", { gid: this.game.id, score: score });
}
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
div(v-if="logoutOk")
p {{ st.tr["Logout successful!"] }}
- p {{ st.tr["Back to Hall in 3 seconds..."] }}
</template>
<script>
this.st.user.notify = false;
localStorage.removeItem("myid");
localStorage.removeItem("myname");
- setTimeout(() => {
- this.$router.replace("/");
- }, 3000);
}
);
}
v-show="display=='corr'"
:games="corrGames"
@show-game="showGame"
+ @abort="abortGame"
)
</template>
import { store } from "@/store";
import { GameStorage } from "@/utils/gameStorage";
import { ajax } from "@/utils/ajax";
+import { getScoreMessage } from "@/utils/scoring";
import params from "@/parameters";
import { getRandString } from "@/utils/alea";
import GameList from "@/components/GameList.vue";
},
created: function() {
GameStorage.getAll(true, localGames => {
- localGames.forEach(g => (g.type = this.classifyObject(g)));
+ localGames.forEach(g => g.type = "live");
this.liveGames = localGames;
});
if (this.st.user.id > 0) {
- ajax("/games", "GET", { uid: this.st.user.id }, res => {
- res.games.forEach(g => (g.type = this.classifyObject(g)));
- this.corrGames = res.games;
+ ajax(
+ "/games",
+ "GET",
+ { uid: this.st.user.id },
+ res => {
+ let serverGames = res.games.filter(g => {
+ const mySide =
+ g.players[0].uid == this.st.user.id
+ ? "White"
+ : "Black";
+ return !g["deletedBy" + mySide];
+ });
+ serverGames.forEach(g => g.type = "corr");
+ this.corrGames = serverGames;
});
}
// Initialize connection
}
this.$router.push("/game/" + game.id + nextIds);
},
+ abortGame: function(game) {
+ // Special "trans-pages" case: from MyGames to Game
+ // TODO: also for corr games? (It's less important)
+ if (game.type == "live") {
+ const oppsid =
+ game.players[0].sid == this.st.user.sid
+ ? game.players[1].sid
+ : game.players[0].sid;
+ this.conn.send(
+ JSON.stringify(
+ {
+ code: "mabort",
+ gid: game.id,
+ // NOTE: target might not be online
+ target: oppsid
+ }
+ )
+ );
+ }
+ else if (!game.deletedByWhite || !game.deletedByBlack) {
+ // Set score if game isn't deleted on server:
+ ajax(
+ "/games",
+ "PUT",
+ {
+ gid: game.id,
+ newObj: {
+ score: "?",
+ scoreMsg: getScoreMessage("?")
+ }
+ }
+ );
+ }
+ },
socketMessageListener: function(msg) {
const data = JSON.parse(msg.data);
if (data.code == "changeturn") {
vid integer,
fenStart varchar, --initial state
fen varchar, --current state
- score varchar,
+ score varchar default '*',
scoreMsg varchar,
cadence varchar,
created datetime,
- drawOffer character,
+ drawOffer character default '',
+ rematchOffer character default '',
+ deletedByWhite boolean,
+ deletedByBlack boolean,
foreign key (vid) references Variants(id)
);
db.serialize(function() {
let query =
"INSERT INTO Games " +
- "(vid, fenStart, fen, score, cadence, created, drawOffer) " +
+ "(vid, fenStart, fen, cadence, created) " +
"VALUES " +
- "(" + vid + ",'" + fen + "','" + fen + "','*','" + cadence + "'," + Date.now() + ",'')";
+ "(" + vid + ",'" + fen + "','" + fen + "','" + cadence + "'," + Date.now() + ")";
db.run(query, function(err) {
if (err)
cb(err)
},
// TODO: some queries here could be async
- getOne: function(id, light, cb)
+ getOne: function(id, cb)
{
// NOTE: ignoring errors (shouldn't happen at this stage)
db.serialize(function() {
let query =
"SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, g.score, " +
- "g.scoreMsg, g.drawOffer, v.name AS vname " +
+ "g.scoreMsg, g.drawOffer, g.rematchOffer, v.name AS vname " +
"FROM Games g " +
"JOIN Variants v " +
" ON g.vid = v.id " +
"WHERE g.id = " + id;
- db.get(query, (err,gameInfo) => {
+ db.get(query, (err, gameInfo) => {
query =
"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 (light)
- {
+ db.all(query, (err2, players) => {
+ query =
+ "SELECT squares, played, idx " +
+ "FROM Moves " +
+ "WHERE gid = " + id;
+ db.all(query, (err3, moves) => {
query =
- "SELECT COUNT(*) AS nbMoves " +
- "FROM Moves " +
+ "SELECT msg, name, added " +
+ "FROM Chats " +
"WHERE gid = " + id;
- db.get(query, (err,ret) => {
+ db.all(query, (err4, chats) => {
const game = Object.assign({},
gameInfo,
- {players: players},
- {movesCount: ret.nbMoves}
+ {
+ players: players,
+ moves: moves,
+ chats: chats,
+ }
);
cb(null, game);
});
- }
- else
- {
- // Full game requested:
- query =
- "SELECT squares, played, idx " +
- "FROM Moves " +
- "WHERE gid = " + id;
- db.all(query, (err3,moves) => {
- query =
- "SELECT msg, name, added " +
- "FROM Chats " +
- "WHERE gid = " + id;
- db.all(query, (err4,chats) => {
- const game = Object.assign({},
- gameInfo,
- {
- players: players,
- moves: moves,
- chats: chats,
- }
- );
- cb(null, game);
- });
- });
- }
+ });
});
});
});
// For display on MyGames or Hall: no need for moves or chats
getByUser: function(uid, excluded, cb)
{
+ // Some fields are not required when showing a games list:
+ const getOneLight = (id, cb2) => {
+ let query =
+ "SELECT g.id, g.vid, g.fen, g.cadence, g.created, g.score, " +
+ "g.scoreMsg, g.deletedByWhite, g.deletedByBlack, v.name AS vname " +
+ "FROM Games g " +
+ "JOIN Variants v " +
+ " ON g.vid = v.id " +
+ "WHERE g.id = " + id;
+ db.get(query, (err, gameInfo) => {
+ query =
+ "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) => {
+ query =
+ "SELECT COUNT(*) AS nbMoves " +
+ "FROM Moves " +
+ "WHERE gid = " + id;
+ db.get(query, (err,ret) => {
+ const game = Object.assign({},
+ gameInfo,
+ {
+ players: players,
+ movesCount: ret.nbMoves
+ }
+ );
+ cb2(game);
+ });
+ });
+ });
+ };
db.serialize(function() {
let query = "";
- if (uid == 0)
- {
+ if (uid == 0) {
// Special case anonymous user: show all games
query =
"SELECT id AS gid " +
"FROM Games";
}
- else
- {
+ else {
// Registered user:
query =
"SELECT gid " +
(excluded ? " = 0" : " > 0");
}
db.all(query, (err,gameIds) => {
- if (err || gameIds.length == 0)
- cb(err, []);
- else
- {
+ if (err || gameIds.length == 0) cb(err, []);
+ else {
let gameArray = [];
let gCounter = 0;
- for (let i=0; i<gameIds.length; i++)
- {
- GameModel.getOne(gameIds[i]["gid"], true, (err2,game) => {
+ for (let i=0; i<gameIds.length; i++) {
+ getOneLight(gameIds[i]["gid"], (game) => {
gameArray.push(game);
gCounter++; //TODO: let's hope this is atomic?!
// Call callback function only when gameArray is complete:
obj.drawOffer = "";
modifs += "drawOffer = '" + obj.drawOffer + "',";
}
- if (obj.fen)
+ if (!!obj.fen)
modifs += "fen = '" + obj.fen + "',";
- if (obj.score)
+ if (!!obj.score)
modifs += "score = '" + obj.score + "',";
- if (obj.scoreMsg)
+ if (!!obj.scoreMsg)
modifs += "scoreMsg = '" + obj.scoreMsg + "',";
+ if (!!obj.deletedBy) {
+ const myColor = obj.deletedBy == 'w' ? "White" : "Black";
+ modifs += "deletedBy" + myColor + " = true,";
+ }
modifs = modifs.slice(0,-1); //remove last comma
if (modifs.length > 0)
{
});
}
else cb(null);
- if (obj.chat)
+ if (!!obj.chat)
{
query =
"INSERT INTO Chats (gid, msg, name, added) VALUES ("
"WHERE gid = " + id;
db.run(query);
}
+ if (!!obj.deletedBy) {
+ // Did my opponent delete it too?
+ let selection =
+ "deletedBy" +
+ (obj.deletedBy == 'w' ? "Black" : "White") +
+ " AS deletedByOpp";
+ query =
+ "SELECT " + selection + " " +
+ "FROM Games " +
+ "WHERE id = " + id;
+ db.get(query, (err,ret) => {
+ // If yes: just remove game
+ if (!!ret.deletedByOpp) GameModel.remove(id);
+ });
+ }
});
},
{
if (gameId.match(/^[0-9]+$/))
{
- GameModel.getOne(gameId, false, (err,game) => {
+ GameModel.getOne(gameId, (err,game) => {
res.json({game: game});
});
}
// FEN update + score(Msg) + draw status / and new move + chats
router.put("/games", access.logged, access.ajax, (req,res) => {
const gid = req.body.gid;
- const obj = req.body.newObj;
+ let obj = req.body.newObj;
if (gid.toString().match(/^[0-9]+$/) && GameModel.checkGameUpdate(obj))
{
GameModel.getPlayers(gid, (err,players) => {
- if (players.some(p => p.uid == req.userId))
- {
+ const myIdx = players.findIndex(p => p.uid == req.userId)
+ if (myIdx >= 0) {
+ // Did I mark the game for deletion?
+ if (!!obj.removeFlag) {
+ obj.deletedBy = ["w","b"][myIdx];
+ delete obj["removeFlag"];
+ }
GameModel.update(gid, obj, (err) => {
- if (!err && (obj.move || obj.score))
+ if (!err && (!!obj.move || !!obj.score))
{
// Notify opponent if he enabled notifications:
const oppid = players[0].uid == req.userId
if (k == sid && x == tmpId) return;
send(
clients[page][k][x],
- Object.assign({code: code, from: sid}, obj)
+ Object.assign({ code: code, from: sid }, obj)
);
});
});
// I effectively disconnected from this page:
notifyRoom(page, "disconnect");
if (page.indexOf("/game/") >= 0)
- notifyRoom("/", "gdisconnect", {page:page});
+ notifyRoom("/", "gdisconnect", { page:page });
}
};
const messageListener = (objtxt) => {
case "connect": {
notifyRoom(page, "connect");
if (page.indexOf("/game/") >= 0)
- notifyRoom("/", "gconnect", {page:page});
+ notifyRoom("/", "gconnect", { page:page });
break;
}
case "disconnect":
Object.keys(clients[pg][k]).forEach(x => {
send(
clients[pg][k][x],
- Object.assign({code: code, from: obj.sid}, o)
+ Object.assign({ code: code, from: obj.sid }, o)
);
});
}
doKill(pg);
disconnectFromOtherConnexion(pg, "disconnect");
if (pg.indexOf("/game/") >= 0 && clients["/"])
- disconnectFromOtherConnexion("/", "gdisconnect", {page: pg});
+ disconnectFromOtherConnexion("/", "gdisconnect", { page: pg });
}
});
break;
// Avoid polling myself: no new information to get
if (k != sid) sockIds.push(k);
});
- send(socket, {code: "pollclients", sockIds: sockIds});
+ send(socket, { code: "pollclients", sockIds: sockIds });
break;
}
case "pollclientsandgamers": {
if (p != "/") {
Object.keys(clients[p]).forEach(k => {
// 'page' indicator is needed for gamers
- if (k != sid) sockIds.push({sid:k, page:p});
+ if (k != sid) sockIds.push({ sid:k, page:p });
});
}
});
- send(socket, {code: "pollclientsandgamers", sockIds: sockIds});
+ send(socket, { code: "pollclientsandgamers", sockIds: sockIds });
break;
}
const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
send(
clients[pg][obj.target][tmpIds[tmpId_idx]],
- {code: obj.code, from: [sid,tmpId,page]}
+ { code: obj.code, from: [sid,tmpId,page] }
);
}
break;
if (obj.target != sid || x != tmpId)
send(
clients[page][obj.target][x],
- {code: obj.code, data: obj.data}
+ { code: obj.code, data: obj.data }
);
});
break;
break;
case "newmove": {
- const dataWithFrom = {from: [sid,tmpId], data: obj.data};
+ const dataWithFrom = { from: [sid,tmpId], data: obj.data };
// Special case re-send newmove only to opponent:
if (!!obj.target && !!clients[page][obj.target]) {
Object.keys(clients[page][obj.target]).forEach(x => {
send(
clients[page][obj.target][x],
- Object.assign({code: "newmove"}, dataWithFrom)
+ Object.assign({ code: "newmove" }, dataWithFrom)
);
});
} else {
) {
send(
clients[page][obj.target[0]][obj.target[1]],
- {code: "gotmove", data: obj.data}
+ { code: "gotmove", data: obj.data }
);
}
break;
case "result":
// Special case: notify all, 'transroom': Game --> Hall
- notifyRoom("/", "result", {gid: obj.gid, score: obj.score});
+ notifyRoom("/", "result", { gid: obj.gid, score: obj.score });
break;
case "mconnect":
Object.keys(clients[pg][s]).forEach(x => {
send(
clients[pg][s][x],
- {code: "mconnect", data: obj.data}
+ { code: "mconnect", data: obj.data }
);
});
});
// TODO
// Also TODO: pass newgame to MyGames, and gameover (result)
break;
+ case "mabort": {
+ const gamePg = "/game/" + obj.gid;
+ if (!!clients[gamePg] && !!clients[gamePg][obj.target]) {
+ Object.keys(clients[gamePg][target]).forEach(x => {
+ send(
+ clients[gamePg][obj.target][x],
+ { code: "abort" }
+ );
+ });
+ }
+ break;
+ }
// Passing, relaying something: from isn't needed,
// but target is fully identified (sid + tmpId)
const pg = obj.target[2] || page; //required for identity and game
// NOTE: if in game we ask identity to opponent still in Hall,
// but leaving Hall, clients[pg] or clients[pg][target] could be undefined
- if (!!clients[pg] && !!clients[pg][obj.target[0]])
- send(clients[pg][obj.target[0]][obj.target[1]], {code:obj.code, data:obj.data});
+ if (!!clients[pg] && !!clients[pg][obj.target[0]]) {
+ send(
+ clients[pg][obj.target[0]][obj.target[1]],
+ { code:obj.code, data:obj.data }
+ );
+ }
break;
}
}
};
// Update clients object: add new connexion
if (!clients[page])
- clients[page] = {[sid]: {[tmpId]: socket}};
+ clients[page] = { [sid]: {[tmpId]: socket } };
else if (!clients[page][sid])
- clients[page][sid] = {[tmpId]: socket};
+ clients[page][sid] = { [tmpId]: socket };
else
clients[page][sid][tmpId] = socket;
socket.on("message", messageListener);