}
// Some variants cannot have analyse mode
- static get CanAnalyse() {
+ static get CanAnalyze() {
return true;
}
"game.fenStart": function() {
this.re_setVariables();
},
- // Received a new move to play:
- "game.moveToPlay": function(move) {
- if (move) this.play(move, "receive");
- },
- // ...Or to undo (corr game, move not validated)
- "game.moveToUndo": function(move) {
- if (move) this.undo(move);
- }
},
computed: {
showMoves: function() {
},
re_setVariables: function() {
this.endgameMessage = "";
- this.orientation = this.game.mycolor || "w"; //default orientation for observed games
+ // "w": default orientation for observed games
+ this.orientation = this.game.mycolor || "w";
this.moves = JSON.parse(JSON.stringify(this.game.moves || []));
- // Post-processing: decorate each move with color + current FEN:
- // (to be able to jump to any position quickly)
- let vr_tmp = new V(this.game.fenStart); //vr is already at end of game
- this.firstMoveNumber = Math.floor(
- V.ParseFen(this.game.fenStart).movesCount / 2
- );
+ // Post-processing: decorate each move with color, notation and FEN
+ let vr_tmp = new V(this.game.fenStart);
+ const parsedFen = V.ParseFen(this.game.fenStart);
+ const firstMoveColor = parsedFen.turn;
+ this.firstMoveNumber = Math.floor(parsedFen.movesCount / 2);
this.moves.forEach(move => {
- // NOTE: this is doing manually what play() function below achieve,
- // but in a lighter "fast-forward" way
move.color = vr_tmp.turn;
move.notation = vr_tmp.getNotation(move);
vr_tmp.play(move);
move.fen = vr_tmp.getFen();
});
- if (
- (this.moves.length > 0 && this.moves[0].color == "b") ||
- (this.moves.length == 0 && vr_tmp.turn == "b")
- ) {
+ if (firstMoveColor == "b") {
// 'end' is required for Board component to check lastMove for e.p.
this.moves.unshift({
color: "w",
<template lang="pug">
BaseGame(
+ ref="basegame"
:game="game"
:vr="vr"
@newmove="processMove"
}
}
},
- // Modal end of game, and then sub-components
created: function() {
// Computer moves web worker logic:
this.compWorker = new Worker();
let moveIdx = 0;
let self = this;
(function executeMove() {
- self.$set(self.game, "moveToPlay", compMove[moveIdx++]);
+ self.$refs["basegame"].play(compMove[moveIdx++]);
if (moveIdx >= compMove.length) {
self.compThink = false;
if (self.game.score != "*")
if (mycolor != "w" || this.gameInfo.mode == "auto")
this.playComputerMove();
},
+ // NOTE: a "goto" action could lead to an error when comp is thinking,
+ // but it's OK because from the user viewpoint the game just stops.
playComputerMove: function() {
this.timeStart = Date.now();
this.compThink = true;
data: function() {
return {
st: store.state,
- showCadence: true
+ deleted: {}, //mark deleted games
+ showCadence: window.innerWidth >= 425 //TODO: arbitrary value
};
},
mounted: function() {
if (!timeoutLaunched) {
timeoutLaunched = true;
setTimeout(() => {
- this.showCadence = window.innerWidth >= 425; //TODO: arbitrary
+ this.showCadence = window.innerWidth >= 425;
timeoutLaunched = false;
}, 500);
}
// Show in order: games where it's my turn, my running games, my games, other games
let minCreated = Number.MAX_SAFE_INTEGER;
let maxCreated = 0;
- let augmentedGames = this.games.map(g => {
- let priority = 0;
- let myColor = undefined;
- if (
- g.players.some(
- p => p.uid == this.st.user.id || p.sid == this.st.user.sid
- )
- ) {
- priority++;
- myColor =
- g.players[0].uid == this.st.user.id ||
- g.players[0].sid == this.st.user.sid
- ? "w"
- : "b";
- if (g.score == "*") {
+ let augmentedGames = this.games
+ .filter(g => !this.deleted[g.id])
+ .map(g => {
+ let priority = 0;
+ let myColor = undefined;
+ if (
+ g.players.some(
+ p => p.uid == this.st.user.id || p.sid == this.st.user.sid
+ )
+ ) {
priority++;
- // I play in this game, so g.fen will be defined
- // NOTE: this is a fragile way to detect turn,
- // but since V isn't defined let's do that for now. (TODO:)
- //if (V.ParseFen(g.fen).turn == myColor)
- if (g.fen.match(" " + myColor + " ")) priority++;
+ myColor =
+ g.players[0].uid == this.st.user.id ||
+ g.players[0].sid == this.st.user.sid
+ ? "w"
+ : "b";
+ if (g.score == "*") {
+ priority++;
+ // I play in this game, so g.fen will be defined
+ // NOTE: this is a fragile way to detect turn,
+ // but since V isn't defined let's do that for now. (TODO:)
+ //if (V.ParseFen(g.fen).turn == myColor)
+ if (g.fen.match(" " + myColor + " ")) priority++;
+ }
}
- }
- if (g.created < minCreated) minCreated = g.created;
- if (g.created > maxCreated) maxCreated = g.created;
- return Object.assign({}, g, {
- priority: priority,
- myTurn: priority == 3,
- myColor: myColor
+ if (g.created < minCreated) minCreated = g.created;
+ if (g.created > maxCreated) maxCreated = g.created;
+ return Object.assign({}, g, {
+ priority: priority,
+ myTurn: priority == 3,
+ myColor: myColor
+ });
});
- });
const deltaCreated = maxCreated - minCreated;
return augmentedGames.sort((g1, g2) => {
return (
},
deleteGame: function(game, e) {
if (game.score != "*") {
- if (confirm(this.st.tr["Remove game?"])) GameStorage.remove(game.id);
+ if (confirm(this.st.tr["Remove game?"])) {
+ GameStorage.remove(
+ game.id,
+ () => {
+ this.$set(this.deleted, game.id, true);
+ }
+ );
+ }
e.stopPropagation();
}
}
name: "my-move-list",
props: ["moves", "cursor", "score", "message", "firstNum"],
render(h) {
- if (this.moves.length == 0) return h("div");
- let tableContent = [];
- let moveCounter = 0;
- let tableRow = undefined;
- let moveCells = undefined;
- let curCellContent = "";
- let firstIndex = 0;
- for (let i = 0; i < this.moves.length; i++) {
- if (this.moves[i].color == "w") {
- if (i == 0 || (i > 0 && this.moves[i - 1].color == "b")) {
- if (tableRow) {
- tableRow.children = moveCells;
- tableContent.push(tableRow);
- }
- moveCells = [
- h("td", { domProps: { innerHTML: ++moveCounter + "." } })
- ];
- tableRow = h("tr", {});
- curCellContent = "";
- firstIndex = i;
- }
- }
- // Next condition is fine because even if the first move is black,
- // there will be the "..." which count as white move.
- else if (this.moves[i].color == "b" && this.moves[i - 1].color == "w")
- firstIndex = i;
- curCellContent += this.moves[i].notation;
- if (
- i < this.moves.length - 1 &&
- this.moves[i + 1].color == this.moves[i].color
- )
- curCellContent += ",";
- //color change
- else {
- moveCells.push(
- h("td", {
- domProps: { innerHTML: curCellContent },
- on: { click: () => this.gotoMove(i) },
- class: {
- "highlight-lm": this.cursor >= firstIndex && this.cursor <= i
- }
- })
- );
- curCellContent = "";
- }
- }
- // Complete last row, which might not be full:
- if (moveCells.length - 1 == 1) {
- moveCells.push(h("td", { domProps: { innerHTML: "" } }));
- }
- tableRow.children = moveCells;
- tableContent.push(tableRow);
let rootElements = [];
if (!!this.score && this.score != "*") {
const scoreDiv = h(
);
rootElements.push(scoreDiv);
}
- rootElements.push(
- h(
- "table",
- {
- class: {
- "moves-list": true
+ if (this.moves.length > 0) {
+ let tableContent = [];
+ let moveCounter = 0;
+ let tableRow = undefined;
+ let moveCells = undefined;
+ let curCellContent = "";
+ let firstIndex = 0;
+ for (let i = 0; i < this.moves.length; i++) {
+ if (this.moves[i].color == "w") {
+ if (i == 0 || (i > 0 && this.moves[i - 1].color == "b")) {
+ if (tableRow) {
+ tableRow.children = moveCells;
+ tableContent.push(tableRow);
+ }
+ moveCells = [
+ h(
+ "div",
+ {
+ "class": {td: true},
+ domProps: { innerHTML: ++moveCounter + "." }
+ }
+ )
+ ];
+ tableRow = h("div", {"class": {tr: true}});
+ curCellContent = "";
+ firstIndex = i;
}
- },
- tableContent
- )
- );
+ }
+ // Next condition is fine because even if the first move is black,
+ // there will be the "..." which count as white move.
+ else if (this.moves[i].color == "b" && this.moves[i - 1].color == "w")
+ firstIndex = i;
+ curCellContent += this.moves[i].notation;
+ if (
+ i < this.moves.length - 1 &&
+ this.moves[i + 1].color == this.moves[i].color
+ )
+ curCellContent += ",";
+ else {
+ // Color change
+ moveCells.push(
+ h(
+ "div",
+ {
+ "class": {
+ td: true,
+ "highlight-lm": this.cursor >= firstIndex && this.cursor <= i
+ },
+ domProps: { innerHTML: curCellContent },
+ on: { click: () => this.gotoMove(i) }
+ }
+ )
+ );
+ curCellContent = "";
+ }
+ }
+ // Complete last row, which might not be full:
+ if (moveCells.length - 1 == 1) {
+ moveCells.push(h("div", {"class": {td: true}}));
+ }
+ tableRow.children = moveCells;
+ tableContent.push(tableRow);
+ rootElements.push(
+ h(
+ "div",
+ {
+ class: {
+ "moves-list": true
+ }
+ },
+ tableContent
+ )
+ );
+ }
return h("div", {}, rootElements);
},
watch: {
cursor: function(newCursor) {
if (window.innerWidth <= 767) return; //scrolling would hide chessboard
- // Count grouped moves until the cursor (if multi-moves):
- let groupsCount = 0;
- let curCol = undefined;
- for (let i = 0; i < newCursor; i++) {
- const m = this.moves[i];
- if (m.color != curCol) {
- groupsCount++;
- curCol = m.color;
- }
- }
// $nextTick to wait for table > tr to be rendered
this.$nextTick(() => {
- let rows = document.querySelectorAll("#movesList tr");
- if (rows.length > 0) {
- rows[Math.floor(groupsCount / 2)].scrollIntoView({
+ let curMove = document.querySelector(".td.highlight-lm");
+ if (curMove) {
+ curMove.scrollIntoView({
behavior: "auto",
block: "nearest"
});
<style lang="sass" scoped>
.moves-list
- min-width: 250px
+ cursor: pointer
+ min-height: 1px
+ max-height: 500px
+ overflow: auto
+ background-color: white
+ width: 280px
+ & > .tr
+ clear: both
+ border-bottom: 1px solid lightgrey
+ & > .td
+ float: left
+ padding: 2% 0 2% 1%
+ &:first-child
+ color: grey
+ width: 15%
+ &:not(first-child)
+ width: 41%
+
+@media screen and (max-width: 767px)
+ .moves-list
+ width: 100%
-td.highlight-lm
+.td.highlight-lm
background-color: plum
</style>
-
-<!-- TODO: use template function + multi-moves: much easier
-<template lang="pug">
-div
- #scoreInfo(v-if="score!='*'")
- p {{ score }}
- p {{ message }}
- table.moves-list
- tbody
- tr(v-for="moveIdx in evenNumbers")
- td {{ firstNum + moveIdx / 2 + 1 }}
- td(:class="{'highlight-lm': cursor == moveIdx}"
- @click="() => gotoMove(moveIdx)")
- | {{ moves[moveIdx].notation }}
- td(v-if="moveIdx < moves.length-1"
- :class="{'highlight-lm': cursor == moveIdx+1}"
- @click="() => gotoMove(moveIdx+1)")
- | {{ moves[moveIdx+1].notation }}
- // Else: just add an empty cell
- td(v-else)
-</template>
-
-<script>
-// Component for moves list on the right
-export default {
- name: 'my-move-list',
- props: ["moves","cursor","score","message","firstNum"],
- watch: {
- cursor: function(newValue) {
- if (window.innerWidth <= 767)
- return; //moves list is below: scrolling would hide chessboard
- if (newValue < 0)
- newValue = 0; //avoid rows[-1] => error
- // $nextTick to wait for table > tr to be rendered
- this.$nextTick( () => {
- let rows = document.querySelectorAll('#movesList tr');
- if (rows.length > 0)
- {
- rows[Math.floor(newValue/2)].scrollIntoView({
- behavior: "auto",
- block: "nearest",
- });
- }
- });
- },
- },
- computed: {
- evenNumbers: function() {
- return [...Array(this.moves.length).keys()].filter(i => i%2==0);
- },
- },
- methods: {
- gotoMove: function(index) {
- this.$emit("goto-move", index);
- },
- },
-};
-</script>
--->
Apply: "Apply",
"Back to list": "Back to list",
"Black to move": "Black to move",
+ "Black surrender": "Black surrender",
"Black win": "Black win",
"Board colors": "Board colors",
"Board size": "Board size",
Contact: "Contact",
"Correspondance challenges": "Correspondance challenges",
"Correspondance games": "Correspondance games",
- "Database error:": "Database error:",
+ "Database error: stop private browsing, or update your browser":
+ "Database error: stop private browsing, or update your browser",
Delete: "Delete",
Download: "Download",
Draw: "Draw",
Email: "Email",
"Email sent!": "Email sent!",
"Empty message": "Empty message",
- "Error while loading database:": "Error while loading database:",
"Example game": "Example game",
- "Game retrieval failed:": "Game retrieval failed:",
- "Game removal failed:": "Game removal failed:",
Go: "Go",
green: "green",
Hall: "Hall",
"Highlight last move and checks?": "Highlight last move and checks?",
Instructions: "Instructions",
+ "is not online": "is not online",
Language: "Language",
"Live challenges": "Live challenges",
"Live games": "Live games",
"Show possible moves?": "Show possible moves?",
"Show solution": "Show solution",
Solution: "Solution",
+ Stop: "Stop",
"Stop game": "Stop game",
Subject: "Subject",
"Terminate game?": "Terminate game?",
Variants: "Variants",
Versus: "Versus",
"White to move": "White to move",
+ "White surrender": "White surrender",
"White win": "White win",
"Who's there?": "Who's there?",
With: "With",
"Back to list": "Volver a la lista",
Black: "Negras",
"Black to move": "Juegan las negras",
+ "Black surrender": "Las negras abandonan",
"Black win": "Las negras gagnan",
"Board colors": "Colores del tablero",
"Board size": "Tamaño del tablero",
Contact: "Contacto",
"Correspondance challenges": "Desafíos por correspondencia",
"Correspondance games": "Partidas por correspondencia",
- "Database error:": "Error de la base de datos:",
+ "Database error: stop private browsing, or update your browser":
+ "Error de la base de datos: detener la navegación privada, o actualizar su navegador",
Delete: "Borrar",
Download: "Descargar",
Draw: "Tablas",
Email: "Email",
"Email sent!": "¡Email enviado!",
"Empty message": "Mensaje vacio",
- "Error while loading database:": "Error al cargar la base de datos:",
"Example game": "Ejemplo de partida",
- "Game retrieval failed:": "La recuperación de la partida falló:",
- "Game removal failed:": "La eliminación de la partida falló:",
Go: "Go",
green: "verde",
Hall: "Salón",
"Highlight last move and checks?": "¿Resaltar el último movimiento y jaques?",
Instructions: "Instrucciones",
+ "is not online": "no está en línea",
Language: "Idioma",
"Live challenges": "Desafíos en vivo",
"Live games": "Partidas en vivo",
"Show possible moves?": "¿Mostrar posibles movimientos?",
"Show solution": "Mostrar la solución",
Solution: "Solución",
+ Stop: "Interrupción",
"Stop game": "Terminar la partida",
Subject: "Asunto",
"Terminate game?": "¿Terminar la partida?",
Versus: "Contra",
White: "Blancas",
"White to move": "Juegan las blancas",
+ "White surrender": "Las blancas abandonan",
"White win": "Las blancas gagnan",
"Who's there?": "¿Quién está ahí?",
With: "Con",
"Back to list": "Retour à la liste",
Black: "Noirs",
"Black to move": "Trait aux noirs",
+ "Black surrender": "Les noirs abandonnent",
"Black win": "Les noirs gagnent",
"Board colors": "Couleurs de l'échiquier",
"Board size": "Taille de l'échiquier",
Contact: "Contact",
"Correspondance challenges": "Défis par correspondance",
"Correspondance games": "Parties par correspondance",
- "Database error:": "Erreur de base de données :",
+ "Database error: stop private browsing, or update your browser":
+ "Erreur de base de données : arrêtez la navigation privée, ou mettez à jour votre navigateur",
Delete: "Supprimer",
Download: "Télécharger",
Draw: "Nulle",
Email: "Email",
"Email sent!": "Email envoyé !",
"Empty message": "Message vide",
- "Error while loading database:":
- "Erreur lors du chargement de la base de données :",
"Example game": "Partie exemple",
- "Game retrieval failed:": "Échec de la récupération de la partie :",
- "Game removal failed:": "Échec de la suppresion de la partie :",
Go: "Go",
green: "vert",
Hall: "Salon",
"Highlight last move and checks?":
"Mettre en valeur le dernier coup et les échecs ?",
Instructions: "Instructions",
+ "is not online": "n'est pas en ligne",
Language: "Langue",
"Live challenges": "Défis en direct",
"Live games": "Parties en direct",
"Show possible moves?": "Montrer les coups possibles ?",
"Show solution": "Montrer la solution",
Solution: "Solution",
+ Stop: "Arrêt",
"Stop game": "Arrêter la partie",
Subject: "Sujet",
"Terminate game?": "Stopper la partie ?",
Versus: "Contre",
White: "Blancs",
"White to move": "Trait aux blancs",
+ "White surrender": "Les blancs abandonnent",
"White win": "Les blancs gagnent",
"Who's there?": "Qui est là ?",
With: "Avec",
let DBOpenRequest = window.indexedDB.open("vchess", 4);
DBOpenRequest.onerror = function(event) {
- alert(store.state.tr["Database error:"] + " " + event.target.errorCode);
+ alert(store.state.tr["Database error: stop private browsing, or update your browser"]);
+ callback("error",null);
};
DBOpenRequest.onsuccess = function() {
db = DBOpenRequest.result;
- callback(db);
+ callback(null,db);
db.close();
};
DBOpenRequest.onupgradeneeded = function(event) {
let db = event.target.result;
- db.onerror = function(event) {
- alert(
- store.state.tr["Error while loading database:"] +
- " " +
- event.target.errorCode
- );
- };
- // Create objectStore for vchess->games
let objectStore = db.createObjectStore("games", { keyPath: "id" });
objectStore.createIndex("score", "score"); //to search by game result
};
export const GameStorage = {
// Optional callback to get error status
add: function(game, callback) {
- dbOperation(db => {
- let transaction = db.transaction("games", "readwrite");
- if (callback) {
- transaction.oncomplete = function() {
- callback({}); //everything's fine
- };
- transaction.onerror = function() {
- callback({
- errmsg:
- store.state.tr["Game retrieval failed:"] + " " + transaction.error
- });
- };
+ dbOperation((err,db) => {
+ if (err) {
+ callback("error");
+ return;
}
+ let transaction = db.transaction("games", "readwrite");
+ transaction.oncomplete = function() {
+ callback(); //everything's fine
+ };
let objectStore = transaction.objectStore("games");
objectStore.add(game);
});
});
} else {
// live
- dbOperation(db => {
+ dbOperation((err,db) => {
let objectStore = db
.transaction("games", "readwrite")
.objectStore("games");
objectStore.get(gameId).onsuccess = function(event) {
- const game = event.target.result;
- Object.keys(obj).forEach(k => {
- if (k == "move") game.moves.push(obj[k]);
- else game[k] = obj[k];
- });
- objectStore.put(game); //save updated data
+ // Ignoring error silently: shouldn't happen now. TODO?
+ if (event.target.result) {
+ const game = event.target.result;
+ 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...)
getAll: function(callback) {
- dbOperation(db => {
+ dbOperation((err,db) => {
let objectStore = db.transaction("games").objectStore("games");
let games = [];
objectStore.openCursor().onsuccess = function(event) {
});
callback(game);
});
- } //local game
+ }
else {
- dbOperation(db => {
+ // Local game
+ dbOperation((err,db) => {
let objectStore = db.transaction("games").objectStore("games");
objectStore.get(gameId).onsuccess = function(event) {
- callback(event.target.result);
+ if (event.target.result)
+ callback(event.target.result);
};
});
}
},
- getCurrent: function(callback) {
- dbOperation(db => {
- let objectStore = db.transaction("games").objectStore("games");
- objectStore.get("*").onsuccess = function(event) {
- callback(event.target.result);
- };
- });
- },
-
// Delete a game in indexedDB
remove: function(gameId, callback) {
- dbOperation(db => {
- let transaction = db.transaction(["games"], "readwrite");
- if (callback) {
+ dbOperation((err,db) => {
+ if (!err) {
+ let transaction = db.transaction(["games"], "readwrite");
transaction.oncomplete = function() {
callback({}); //everything's fine
};
- transaction.onerror = function() {
- callback({
- errmsg:
- store.state.tr["Game removal failed:"] + " " + transaction.error
- });
- };
+ transaction.objectStore("games").delete(gameId);
}
- transaction.objectStore("games").delete(gameId);
});
}
};
export const VariantRules = class DarkRules extends ChessRules {
// Analyse in Dark mode makes no sense
- static get CanAnalyse() {
+ static get CanAnalyze() {
return false;
}
| {{ game.players[1].name || "@nonymous" }}
span.time(v-if="game.score=='*'") {{ virtualClocks[1] }}
BaseGame(
+ ref="basegame"
:game="game"
:vr="vr"
@newmove="processMove"
// Socket init required before loading remote game:
const socketInit = callback => {
if (!!this.conn && this.conn.readyState == 1)
- //1 == OPEN state
+ // 1 == OPEN state
callback();
- //socket not ready yet (initial loading)
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 = () => {
}
};
if (!this.gameRef.rid)
- //game stored locally or on server
+ // Game stored locally or on server
this.loadGame(null, () => socketInit(this.roomInit));
- //game stored remotely: need socket to retrieve it
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.
// A more general approach would be to store it somewhere.
if (this.game.type == "live" && !!this.game.mycolor)
GameStorage.update(this.gameRef.id, { drawOffer: "" });
}
- this.$set(this.game, "moveToPlay", move);
+ this.$refs["basegame"].play(move, "received");
break;
}
case "resign":
- this.gameOver(data.side == "b" ? "1-0" : "0-1", "Resign");
+ const score = data.side == "b" ? "1-0" : "0-1";
+ const side = data.side == "w" ? "White" : "Black";
+ this.gameOver(score, side + " surrender");
break;
case "abort":
- this.gameOver("?", "Abort");
+ this.gameOver("?", "Stop");
break;
case "draw":
this.gameOver("1/2", data.data);
const L = this.game.moves.length;
if (data.movesCount > L) {
// Just got last move from him
- this.$set(
- this.game,
- "moveToPlay",
+ this.$refs["basegame"].play(
Object.assign({ initime: data.initime }, data.lastMove)
);
}
},
abortGame: function() {
if (!this.game.mycolor || !confirm(this.st.tr["Terminate game?"])) return;
- this.gameOver("?", "Abort");
+ this.gameOver("?", "Stop");
this.send("abort");
},
resign: function() {
if (!this.game.mycolor || !confirm(this.st.tr["Resign the game?"]))
return;
this.send("resign", { data: this.game.mycolor });
- this.gameOver(this.game.mycolor == "w" ? "0-1" : "1-0", "Resign");
+ const score = this.game.mycolor == "w" ? "0-1" : "1-0";
+ 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)
game.players[0]
];
}
- // corr game: needs to compute the clocks + initime
+ // corr game: need to compute the clocks + initime
// NOTE: clocks in seconds, initime in milliseconds
game.clocks = [tc.mainTime, tc.mainTime];
game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of
}
if (L >= 1) game.initime[L % 2] = game.moves[L - 1].played;
}
- const reformattedMoves = game.moves.map(m => {
- const s = m.squares;
- return {
- appear: s.appear,
- vanish: s.vanish,
- start: s.start,
- end: s.end
- };
- });
// Sort chat messages from newest to oldest
game.chats.sort((c1, c2) => {
return c2.added - c1.added;
});
if (myIdx >= 0 && game.chats.length > 0) {
- // TODO: group multi-moves into an array, to deduce color from index
- // and not need this (also repeated in BaseGame::re_setVariables())
- let vr_tmp = new V(game.fenStart); //vr is already at end of game
- for (let i = 0; i < reformattedMoves.length; i++) {
- game.moves[i].color = vr_tmp.turn;
- vr_tmp.play(reformattedMoves[i]);
- }
- // Blue background on chat button if last chat message arrived after my last move.
+ // Did a chat message arrive after my last move?
+ let vr_tmp = new V(game.fen); //start from last position
let dtLastMove = 0;
for (let midx = game.moves.length - 1; midx >= 0; midx--) {
- if (game.moves[midx].color == mycolor) {
+ vr_tmp.undo(game.moves[midx]);
+ if (vr_tmp.turn == mycolor) {
dtLastMove = game.moves[midx].played;
break;
}
document.getElementById("chatBtn").classList.add("somethingnew");
}
// Now that we used idx and played, re-format moves as for live games
- game.moves = reformattedMoves;
+ game.moves = game.moves.map(m => m.squares);
}
if (gtype == "live" && game.clocks[0] < 0) {
- //game unstarted
+ // Game is unstarted
game.clocks = [tc.mainTime, tc.mainTime];
if (game.score == "*") {
game.initime[0] = Date.now();
}
if (game.drawOffer) {
if (game.drawOffer == "t")
- //three repetitions
+ // Three repetitions
this.drawOffer = "threerep";
else {
+ // Draw offered by any of the players:
if (myIdx < 0) this.drawOffer = "received";
- //by any of the players
else {
// I play in this game:
if (
(game.drawOffer == "b" && myIdx == 1)
)
this.drawOffer = "sent";
- //all other cases
else this.drawOffer = "received";
}
}
}
- if (game.scoreMsg) game.scoreMsg = this.st.tr[game.scoreMsg]; //stored in english
- delete game["moveToPlay"]; //in case of!
this.game = Object.assign(
{},
game,
this.send("askfullgame", { target: this.gameRef.rid });
} else {
// Local or corr game
+ // NOTE: afterRetrieval() is never called if game not found
GameStorage.get(this.gameRef.id, afterRetrieval);
}
},
return;
}
const currentTurn = this.vr.turn;
+ const currentMovesCount = this.game.moves.length;
const colorIdx = ["w", "b"].indexOf(currentTurn);
let countdown =
this.game.clocks[colorIdx] -
let clockUpdate = setInterval(() => {
if (
countdown < 0 ||
- this.vr.turn != currentTurn ||
+ this.game.moves.length > currentMovesCount ||
this.game.score != "*"
) {
clearInterval(clockUpdate);
if (countdown < 0)
this.gameOver(
- this.vr.turn == "w" ? "0-1" : "1-0",
+ currentTurn == "w" ? "0-1" : "1-0",
this.st.tr["Time"]
);
} else
);
}, 1000);
},
- // Post-process a move (which was just played in BaseGame)
+ // Post-process a (potentially partial) move (which was just played in BaseGame)
processMove: function(move) {
if (this.game.type == "corr" && move.color == this.game.mycolor) {
if (
this.st.tr["Are you sure?"]
)
) {
- this.$set(this.game, "moveToUndo", move);
+ this.$refs["basegame"].undo(move);
return;
}
}
// https://stackoverflow.com/a/38750895
if (this.game.mycolor) {
const allowed_fields = ["appear", "vanish", "start", "end"];
- // NOTE: 'var' to see this variable outside this block
+ // NOTE: 'var' to see that variable outside this block
var filtered_move = Object.keys(move)
.filter(key => allowed_fields.includes(key))
.reduce((obj, key) => {
this.game.clocks[colorIdx] += addTime;
// move.initime is set only when I receive a "lastate" move from opponent
this.game.initime[nextIdx] = move.initime || Date.now();
- this.re_setClocks();
+ //if (colorIdx != nextIdx)
+ this.re_setClocks();
// If repetition detected, consider that a draw offer was received:
const fenObj = V.ParseFen(move.fen);
let repIdx = fenObj.position + "_" + fenObj.turn;
// Since corr games are stored at only one location, update should be
// done only by one player for each move:
if (
- !!this.game.mycolor &&
+ this.game.mycolor &&
(this.game.type == "live" || move.color == this.game.mycolor)
) {
let drawCode = "";
played: Date.now(),
idx: this.game.moves.length - 1
},
- drawOffer: drawCode || "n" //"n" for "None" to force reset (otherwise it's ignored)
+ // Code "n" for "None" to force reset (otherwise it's ignored)
+ drawOffer: drawCode || "n"
});
- } //live
+ }
else {
+ // Live game:
GameStorage.update(this.gameRef.id, {
fen: move.fen,
move: filtered_move,
},
gameOver: function(score, scoreMsg) {
this.game.score = score;
- this.game.scoreMsg = this.st.tr[
- scoreMsg ? scoreMsg : getScoreMessage(score)
- ];
+ this.$set(this.game, "scoreMsg", scoreMsg || getScoreMessage(score));
const myIdx = this.game.players.findIndex(p => {
return p.sid == this.st.user.sid || p.uid == this.st.user.id;
});
if (myIdx >= 0) {
- //OK, I play in this game
+ // OK, I play in this game
GameStorage.update(this.gameRef.id, {
score: score,
scoreMsg: scoreMsg
fieldset
label(for="cadence") {{ st.tr["Cadence"] }} *
div#predefinedCadences
- button 3+2
- button 5+3
- button 15+5
+ button(type="button") 3+2
+ button(type="button") 5+3
+ button(type="button") 15+5
input#cadence(
type="text"
v-model="newchallenge.cadence"
},
// Challenge lifecycle:
newChallenge: async function() {
+ if (this.newchallenge.cadence.match(/^[0-9]+$/))
+ this.newchallenge.cadence += "+0"; //assume minutes, no increment
+ const ctype = this.classifyObject(this.newchallenge);
+ // TODO: cadence still unchecked so ctype could be wrong...
let error = "";
- if (this.newchallenge.vid == "")
+ if (!this.newchallenge.vid)
error = this.st.tr["Please select a variant"];
- else if (!!this.newchallenge.to && this.newchallenge.to == this.st.user.name)
- error = this.st.tr["Self-challenge is forbidden"];
+ else if (ctype == "corr" && this.st.user.id <= 0)
+ error = this.st.tr["Please log in to play correspondance games"];
+ else if (this.newchallenge.to) {
+ if (this.newchallenge.to == this.st.user.name)
+ error = this.st.tr["Self-challenge is forbidden"];
+ else if (
+ ctype == "live" &&
+ Object.values(this.people).every(p => p.name != this.newchallenge.to)
+ )
+ error = this.newchallenge.to + " " + this.st.tr["is not online"];
+ }
if (error) {
alert(error);
return;
const vname = this.getVname(this.newchallenge.vid);
const vModule = await import("@/variants/" + vname + ".js");
window.V = vModule.VariantRules;
- if (this.newchallenge.cadence.match(/^[0-9]+$/))
- this.newchallenge.cadence += "+0"; //assume minutes, no increment
- const ctype = this.classifyObject(this.newchallenge);
error = checkChallenge(this.newchallenge);
- if (!error && ctype == "corr" && this.st.user.id <= 0)
- error = this.st.tr["Please log in to play correspondance games"];
if (error) {
alert(error);
return;
initime: [0, 0], //initialized later
score: "*"
});
- GameStorage.add(game);
- if (this.st.settings.sound >= 1)
- new Audio("/sounds/newgame.mp3").play().catch(() => {});
- this.$router.push("/game/" + gameInfo.id);
+ GameStorage.add(game, (err) => {
+ // If an error occurred, game is not added: abort
+ if (!err) {
+ if (this.st.settings.sound >= 1)
+ new Audio("/sounds/newgame.mp3").play().catch(() => {});
+ this.$router.push("/game/" + gameInfo.id);
+ }
+ });
}
}
};
{
UserModel.getOne("name", challenge.to, (err,user) => {
if (!!err || !user)
- return res.json(err | {errmsg: "Typo in player name"});
+ return res.json(err || {errmsg: "Typo in player name"});
challenge.to = user.id; //ready now to insert challenge
insertChallenge();
if (user.notify)
{
const pg = obj.page || page; //required for askidentity and askgame
// In cas askfullgame to wrong SID for example, would crash:
- if (!!clients[pg][obj.target])
+ if (clients[pg] && clients[pg][obj.target])
{
const tmpIds = Object.keys(clients[pg][obj.target]);
if (obj.target == sid) //targetting myself
case "lastate":
{
const pg = obj.target[2] || page; //required for identity and game
- send(clients[pg][obj.target[0]][obj.target[1]], {code:obj.code, data:obj.data});
+ // NOTE: if in game we ask identity to opponent still in Hall,
+ // but leaving Hall, clients[pg] or clients[pg][target] could be ndefined
+ if (clients[pg] && clients[pg][obj.target[0]])
+ send(clients[pg][obj.target[0]][obj.target[1]], {code:obj.code, data:obj.data});
break;
}
}