castleSide++ //large, then small
) {
if (this.castleFlags[c][castleSide] >= V.size.y) continue;
- // If this code is reached, rooks and king are on initial position
+ // If this code is reached, rook and king are on initial position
// NOTE: in some variants this is not a rook, but let's keep variable name
const rookPos = this.castleFlags[c][castleSide];
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 += "+";
+ if (L > 0 && this.moves[L - 1].notation != "...") {
+ 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;
"Squares disappear": "Squares disappear",
"Standard rules": "Standard rules",
"Transform an essay": "Transform an essay",
+ "Two royal pieces": "Two royal pieces",
"Unidentified pieces": "Unidentified pieces"
};
"Squares disappear": "Las casillas desaparecen",
"Standard rules": "Reglas estandar",
"Transform an essay": "Transformar un ensayo",
+ "Two royal pieces": "Dos piezas reales",
"Unidentified pieces": "Piezas no identificadas"
};
"Squares disappear": "Les cases disparaissent",
"Standard rules": "Règles usuelles",
"Transform an essay": "Transformer un essai",
+ "Two royal pieces": "Deux pièces royales",
"Unidentified pieces": "Pièces non identifiées"
};
p.boxed
- | TODO
+ | Checkmating the queen wins too. A queen cannot go or stay under check.
+
+p Just as the king, the queen can be checked and mated. This means that
+ul
+ li It is not allowed to make a move such that the queen can be captured.
+ li.
+ When your queen is attacked, you must play a move such that the queen
+ is no longer attacked.
+ li If it's impossible, then you lose.
+
+p.
+ Since the king remains royal, this allows a new way to win: check both
+ royal pieces at the same time, like on the following diagram.
+
+figure.diagram-container
+ .diagram
+ | fen:4Q3/4K3/8/8/3N4/5k2/2q5/8:
+ figcaption Both black king and queen are in check: white wins.
+
+h3 Special moves
+
+p.
+ If a pawn promotes into a queen, the latter is royal as well.
+ So under-promotions might be wiser.
+
+p.
+ You can castle with the queen or the king and any of the two rooks,
+ under the same conditions as orthodox castling.
+ Here is the resulting position after two white small castles and
+ one black large castle with the queen:
+
+figure.diagram-container
+ .diagram
+ | fen:r4rq1/ppppppkp/6p1/8/8/8/PPPPPPPP/1QR2RK1:
+ figcaption After two white small castles and one black large castle.
+
+p.
+ Note: to castle in a game you need to select
+ the king or queen first, and then move it to a rook.
+
+h3 Source
+
+p
+ a(href="https://www.chessvariants.com/winning.dir/coregal.html") Coregal Chess
+ | on chessvariants.com.
+ | This variant can be played too
+ a(href="https://greenchess.net/rules.php?v=coregal") on greenchess.net
+ | .
+
+p Inventor: Vernon R. Parton (1970)
p.boxed
- | TODO
+ | Es posible ganar dando jaque mate a la dama.
+ | Una dama no puede ir o permanecer en jaque.
+
+p Al igual que el rey, la dama puede ser en jaque (mate). Es decir que
+ul
+ li Se prohíbe un movimiento que permita al oponente capturar a la dama.
+ li.
+ Cuando tu dama es atacada, debes hacer un movimiento
+ para que ya no sea atacado.
+ li Si es imposible, entonces has perdido.
+
+p.
+ Como el rey sigue siendo real, esto agrega una nueva forma de ganar:
+ jaque las dos piezas reales al mismo tiempo, como en el siguiente diagrama.
+
+figure.diagram-container
+ .diagrama
+ | fen:4Q3/4K3/8/8/3N4/5k2/2q5/8:
+ figcaption Las blancas ganan porque el rey y la dama negra están en jaque.
+
+h3 Movimientos especiales
+
+p.
+ Si un peón es promovido a reina, este último también es real.
+ Entonces, las sub-promociones pueden ser más sabias.
+
+p.
+ Puedes hacer el enroque con la dama o el rey y cualquiera de los
+ dos torres, en las mismas condiciones que para el ajedrez ortodoxo.
+ Aquí hay una posible posición después de dos pequeñas rocas blancas y
+ un gran enroque negro con la dama:
+
+figure.diagram-container
+ .diagrama
+ | fen:r4rq1/ppppppkp/6p1/8/8/8/PPPPPPPP/1QR2RK1:
+ figcaption Después de dos pequeñas rocas blancas y un gran enroque negro.
+
+p.
+ Nota: para enrocarse en una partida debes seleccionar el rey o la dama
+ primero, luego mueva la pieza a una torre.
+
+h3 Fuente
+
+p
+ | La
+ a(href="https://www.chessvariants.com/winning.dir/coregal.html") cariante Coregal
+ | en chessvariants.com.
+ | Esta variante también es jugable
+ a(href="https://greenchess.net/rules.php?v=coregal") en greenchess.net
+ | .
+
+p Inventor: Vernon R. Parton (1970)
p.boxed
- | TODO
+ | On peut gagner en matant la dame. Une dame ne peut aller ou rester en échec.
+
+p Tout comme le roi, la dame peut être mise en échec et matée. C'est-à-dire que
+ul
+ li Un coup qui laisserait l'adversaire capturer la dame est interdit.
+ li.
+ Quand votre dame est attaquée, vous devez jouer un coup faisant
+ en sorte que celle-ci ne soit plus attaquée.
+ li Si c'est impossible, alors vous avez perdu.
+
+p.
+ Puisque le roi reste royal, ceci ajoute une nouvelle manière de gagner :
+ mettre en échc les deux pièces royales en même temps,
+ comme sur le diagramme suivant.
+
+figure.diagram-container
+ .diagram
+ | fen:4Q3/4K3/8/8/3N4/5k2/2q5/8:
+ figcaption Les blancs gagnent car le roi et la dame noir sont en échec.
+
+h3 Coups spéciaux
+
+p.
+ Si un pion est promu en dame, cette dernière est royale également.
+ Ainsi, les sous promotions peuvent être plus sages.
+
+p.
+ Vous pouvez roquer avec la dame ou le roi et n'importe laquelle des
+ deux tours, sous les mêmes conditions qu'aux échecs orthodoxes.
+ Voici une position possible après deux petits roques blancs et
+ un grand roque noir avec la dame :
+
+figure.diagram-container
+ .diagram
+ | fen:r4rq1/ppppppkp/6p1/8/8/8/PPPPPPPP/1QR2RK1:
+ figcaption Après deux petits roques blancs et un grand roque noir.
+
+p.
+ Note : pour roquer dans une partie il faut sélectionner le roi ou la dame
+ d'abord, puis déplacer la pièce sur une tour.
+
+h3 Source
+
+p
+ | La
+ a(href="https://www.chessvariants.com/winning.dir/coregal.html") variante Coregal
+ | sur chessvariants.com.
+ | Cette variante est jouable également
+ a(href="https://greenchess.net/rules.php?v=coregal") sur greenchess.net
+ | .
+
+p Inventeur : Vernon R. Parton (1970)
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
import { ArrayFun } from "@/utils/array";
import { randInt, sample } from "@/utils/alea";
export class CoregalRules extends ChessRules {
static IsGoodPosition(position) {
if (!super.IsGoodPosition(position)) return false;
+ const rows = position.split("/");
// Check that at least one queen of each color is there:
let queens = {};
for (let row of rows) {
return !!flags.match(/^[a-z]{8,8}$/);
}
+ // Scanning king position for faster updates is still interesting,
+ // but no need for INIT_COL_KING because it's given in castle flags.
+ scanKings(fen) {
+ this.kingPos = { w: [-1, -1], b: [-1, -1] };
+ const fenRows = V.ParseFen(fen).position.split("/");
+ const startRow = { 'w': V.size.x - 1, 'b': 0 };
+ for (let i = 0; i < fenRows.length; i++) {
+ let k = 0;
+ for (let j = 0; j < fenRows[i].length; j++) {
+ switch (fenRows[i].charAt(j)) {
+ case "k":
+ this.kingPos["b"] = [i, k];
+ break;
+ case "K":
+ this.kingPos["w"] = [i, k];
+ break;
+ default: {
+ const num = parseInt(fenRows[i].charAt(j));
+ if (!isNaN(num)) k += num - 1;
+ }
+ }
+ k++;
+ }
+ }
+ }
+
getCheckSquares(color) {
let squares = [];
const oppCol = V.GetOppCol(color);
if (this.isAttacked(this.kingPos[color], oppCol))
- squares.push(this.kingPos[color]);
+ squares.push(JSON.parse(JSON.stringify(this.kingPos[color])));
for (let i=0; i<V.size.x; i++) {
for (let j=0; j<V.size.y; j++) {
if (
pieces[c][bishop2Pos] = "b";
pieces[c][knight2Pos] = "n";
pieces[c][rook2Pos] = "r";
- flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(queenPos) +
- V.CoordToColumn(kingPos) + V.CoordToColumn(rook2Pos);
+ flags +=
+ [rook1Pos, queenPos, kingPos, rook2Pos].sort().map(V.CoordToColumn).join("");
}
// Add turn + flags + enpassant
return (
}
setFlags(fenflags) {
- // white a-castle, h-castle, black a-castle, h-castle
+ // white pieces positions, then black pieces positions
this.castleFlags = { w: [...Array(4)], b: [...Array(4)] };
for (let i = 0; i < 8; i++) {
this.castleFlags[i < 4 ? "w" : "b"][i % 4] =
- V.ColumnToCoord(fenflags.charAt(i));
+ V.ColumnToCoord(fenflags.charAt(i))
}
}
return super.getPotentialQueenMoves(sq).concat(this.getCastleMoves(sq));
}
- getCastleMoves([x, y], castleInCheck) {
- return [];
-// const c = this.getColor(x, y);
-// if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
-// return []; //x isn't first rank, or king has moved (shortcut)
-//
-// // Castling ?
-// const oppCol = V.GetOppCol(c);
-// let moves = [];
-// let i = 0;
-// // King, then rook:
-// const finalSquares = [
-// [2, 3],
-// [V.size.y - 2, V.size.y - 3]
-// ];
-// castlingCheck: for (
-// let castleSide = 0;
-// castleSide < 2;
-// castleSide++ //large, then small
-// ) {
-// if (this.castleFlags[c][castleSide] >= V.size.y) continue;
-// // If this code is reached, rooks and king are on initial position
-//
-// // NOTE: in some variants this is not a rook, but let's keep variable name
-// const rookPos = this.castleFlags[c][castleSide];
-// const castlingPiece = this.getPiece(x, rookPos);
-// if (this.getColor(x, rookPos) != c)
-// // Rook is here but changed color (see Benedict)
-// continue;
-//
-// // Nothing on the path of the king ? (and no checks)
-// const finDist = finalSquares[castleSide][0] - y;
-// let step = finDist / Math.max(1, Math.abs(finDist));
-// i = y;
-// do {
-// if (
-// (!castleInCheck && this.isAttacked([x, i], oppCol)) ||
-// (this.board[x][i] != V.EMPTY &&
-// // NOTE: next check is enough, because of chessboard constraints
-// (this.getColor(x, i) != c ||
-// ![V.KING, castlingPiece].includes(this.getPiece(x, i))))
-// ) {
-// continue castlingCheck;
-// }
-// i += step;
-// } while (i != finalSquares[castleSide][0]);
-//
-// // Nothing on the path to the rook?
-// step = castleSide == 0 ? -1 : 1;
-// for (i = y + step; i != rookPos; i += step) {
-// if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-// }
-//
-// // Nothing on final squares, except maybe king and castling rook?
-// for (i = 0; i < 2; i++) {
-// if (
-// this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-// this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
-// finalSquares[castleSide][i] != rookPos
-// ) {
-// continue castlingCheck;
-// }
-// }
-//
-// // If this code is reached, castle is valid
-// moves.push(
-// new Move({
-// appear: [
-// new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
-// new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c })
-// ],
-// vanish: [
-// new PiPo({ x: x, y: y, p: V.KING, c: c }),
-// new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
-// ],
-// end:
-// Math.abs(y - rookPos) <= 2
-// ? { x: x, y: rookPos }
-// : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-// })
-// );
-// }
-//
-// return moves;
+ getCastleMoves([x, y]) {
+ const c = this.getColor(x, y);
+ if (
+ x != (c == "w" ? V.size.x - 1 : 0) ||
+ !this.castleFlags[c].slice(1, 3).includes(y)
+ ) {
+ // x isn't first rank, or piece moved
+ return [];
+ }
+ const castlingPiece = this.getPiece(x, y);
+
+ // Relative position of the selected piece: left or right ?
+ // If left: small castle left, large castle right.
+ // If right: usual situation.
+ const relPos = (this.castleFlags[c][1] == y ? "left" : "right");
+
+ // Castling ?
+ const oppCol = V.GetOppCol(c);
+ let moves = [];
+ let i = 0;
+ // Castling piece, then rook:
+ const finalSquares = {
+ 0: (relPos == "left" ? [1, 2] : [2, 3]),
+ 3: (relPos == "right" ? [6, 5] : [5, 4])
+ };
+
+ // Left, then right castle:
+ castlingCheck: for (let castleSide of [0, 3]) {
+ if (this.castleFlags[c][castleSide] >= 8) continue;
+
+ // Rook and castling piece are on initial position
+ const rookPos = this.castleFlags[c][castleSide];
+
+ // Nothing on the path of the king ? (and no checks)
+ const finDist = finalSquares[castleSide][0] - y;
+ let step = finDist / Math.max(1, Math.abs(finDist));
+ i = y;
+ do {
+ if (
+ this.isAttacked([x, i], oppCol) ||
+ (this.board[x][i] != V.EMPTY &&
+ // NOTE: next check is enough, because of chessboard constraints
+ (this.getColor(x, i) != c ||
+ ![castlingPiece, V.ROOK].includes(this.getPiece(x, i))))
+ ) {
+ continue castlingCheck;
+ }
+ i += step;
+ } while (i != finalSquares[castleSide][0]);
+
+ // Nothing on the path to the rook?
+ step = castleSide == 0 ? -1 : 1;
+ for (i = y + step; i != rookPos; i += step) {
+ if (this.board[x][i] != V.EMPTY) continue castlingCheck;
+ }
+
+ // Nothing on final squares, except maybe castling piece and rook?
+ for (i = 0; i < 2; i++) {
+ if (
+ this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
+ ![y, rookPos].includes(finalSquares[castleSide][i])
+ ) {
+ continue castlingCheck;
+ }
+ }
+
+ // If this code is reached, castle is valid
+ moves.push(
+ new Move({
+ appear: [
+ new PiPo({ x: x, y: finalSquares[castleSide][0], p: castlingPiece, c: c }),
+ new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
+ ],
+ vanish: [
+ new PiPo({ x: x, y: y, p: castlingPiece, c: c }),
+ new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
+ ],
+ // In this variant, always castle by playing onto the rook
+ end: { x: x, y: rookPos }
+ })
+ );
+ }
+
+ return moves;
}
underCheck(color) {
}
updateCastleFlags(move, piece) {
-// const c = V.GetOppCol(this.turn);
-// const firstRank = (c == "w" ? V.size.x - 1 : 0);
-// // Update castling flags if rooks are moved
-// const oppCol = V.GetOppCol(c);
-// const oppFirstRank = V.size.x - 1 - firstRank;
-// if (piece == V.KING && move.appear.length > 0)
-// this.castleFlags[c] = [V.size.y, V.size.y];
-// else if (
-// move.start.x == firstRank && //our rook moves?
-// this.castleFlags[c].includes(move.start.y)
-// ) {
-// const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
-// this.castleFlags[c][flagIdx] = V.size.y;
-// } else if (
-// move.end.x == oppFirstRank && //we took opponent rook?
-// this.castleFlags[oppCol].includes(move.end.y)
-// ) {
-// const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
-// this.castleFlags[oppCol][flagIdx] = V.size.y;
-// }
+ const c = V.GetOppCol(this.turn);
+ const firstRank = (c == "w" ? V.size.x - 1 : 0);
+ // Update castling flags if castling pieces moved or were captured
+ const oppCol = V.GetOppCol(c);
+ const oppFirstRank = V.size.x - 1 - firstRank;
+ if (move.start.x == firstRank && [V.KING, V.QUEEN].includes(piece)) {
+ if (this.castleFlags[c][1] == move.start.y)
+ this.castleFlags[c][1] = 8;
+ else if (this.castleFlags[c][2] == move.start.y)
+ this.castleFlags[c][2] = 8;
+ // Else: the flag is already turned off
+ }
+ else if (
+ move.start.x == firstRank && //our rook moves?
+ [this.castleFlags[c][0], this.castleFlags[c][3]].includes(move.start.y)
+ ) {
+ const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 3);
+ this.castleFlags[c][flagIdx] = 8;
+ } else if (
+ move.end.x == oppFirstRank && //we took opponent rook?
+ [this.castleFlags[oppCol][0], this.castleFlags[oppCol][3]].includes(move.end.y)
+ ) {
+ const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 3);
+ this.castleFlags[oppCol][flagIdx] = 8;
+ }
}
// NOTE: do not set queen value to 1000 or so, because there may be several.
+
+ getNotation(move) {
+ if (move.appear.length == 2) {
+ // Castle: determine the right notation
+ const color = move.appear[0].c;
+ let symbol = (move.appear[0].p == V.QUEEN ? "Q" : "") + "0-0";
+ if (
+ (
+ this.castleFlags[color][1] == move.vanish[0].y &&
+ move.end.y > move.start.y
+ )
+ ||
+ (
+ this.castleFlags[color][2] == move.vanish[0].y &&
+ move.end.y < move.start.y
+ )
+ ) {
+ symbol += "-0";
+ }
+ return symbol;
+ }
+ return super.getNotation(move);
+ }
};
// 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;
+ this.conn.addEventListener("message", this.socketMessageListener);
+ this.conn.addEventListener("close", this.socketCloseListener);
// Socket init required before loading remote game:
const socketInit = callback => {
- if (!!this.conn && this.conn.readyState == 1)
+ if (this.conn.readyState == 1)
// 1 == OPEN state
callback();
else
}
case "killed":
// I logged in elsewhere:
+ this.conn.removeEventListener("message", this.socketMessageListener);
+ this.conn.removeEventListener("close", this.socketCloseListener);
this.conn = null;
alert(this.st.tr["New connexion detected: tab now offline"]);
break;
.filter(k =>
[
"id","fen","players","vid","cadence","fenStart","vname",
- "moves","clocks","initime","score","drawOffer","rematchOffer"
+ "moves","clocks","score","drawOffer","rematchOffer"
].includes(k))
.reduce(
(obj, k) => {
case "lastate": {
// Got opponent infos about last move
this.gotLastate = true;
- if (!data.data.nothing) {
- this.lastate = data.data;
- if (this.game.rendered)
- // Game is rendered (Board component)
- this.processLastate();
- // Else: will be processed when game is ready
- }
+ this.lastate = data.data;
+ if (this.game.rendered)
+ // Game is rendered (Board component)
+ this.processLastate();
+ // Else: will be processed when game is ready
break;
}
case "newmove": {
}
}
this.$refs["basegame"].play(movePlus.move, "received", null, true);
+ const moveColIdx = ["w", "b"].indexOf(movePlus.color);
+ this.game.clocks[moveColIdx] = movePlus.clock;
this.processMove(
movePlus.move,
- {
- clock: movePlus.clock,
- receiveMyMove: receiveMyMove
- }
+ { receiveMyMove: receiveMyMove }
);
}
}
}
case "gotmove": {
this.opponentGotMove = true;
- // Now his clock starts running:
+ // Now his clock starts running on my side:
const oppIdx = ['w','b'].indexOf(this.vr.turn);
- this.game.initime[oppIdx] = Date.now();
this.re_setClocks();
break;
}
);
},
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 });
- }
+ // 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.vr.turn != this.game.mycolor)
+ ? 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 != "*" ? this.game.score : undefined,
+ scoreMsg: this.game.score != "*" ? this.game.scoreMsg : undefined,
+ movesCount: L
+ };
+ this.send("lastate", { data: myLastate, target: target });
},
// lastate was received, but maybe game wasn't ready yet:
processLastate: function() {
const data = this.lastate;
this.lastate = undefined; //security...
const L = this.game.moves.length;
+ const oppIdx = 1 - ["w", "b"].indexOf(this.game.mycolor);
+ this.game.clocks[oppIdx] = data.clock;
if (data.movesCount > L) {
// Just got last move from him
this.$refs["basegame"].play(data.lastMove, "received", null, true);
- this.processMove(data.lastMove, { clock: data.clock });
+ this.processMove(data.lastMove);
+ } else {
+ clearInterval(this.clockUpdate);
+ this.re_setClocks();
}
if (data.drawSent) this.drawOffer = "received";
if (data.rematchSent) this.rematchOffer = "received";
- if (data.score != "*") {
+ if (!!data.score) {
this.drawOffer = "";
if (this.game.score == "*")
this.gameOver(data.score, data.scoreMsg);
// Game state (including FEN): will be updated
moves: [],
clocks: [-1, -1], //-1 = unstarted
- initime: [0, 0], //initialized later
score: "*"
}
);
const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers
if (!game.chats) game.chats = []; //live games don't have chat history
if (gtype == "corr") {
- // NOTE: clocks in seconds, initime in milliseconds
+ // NOTE: clocks in seconds
game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of
game.clocks = [tc.mainTime, tc.mainTime];
const L = game.moves.length;
if (game.score == "*") {
- // Set clocks + initime
- game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
- 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.
+ // Adjust clocks
+ if (L >= 2) {
+ game.clocks[L % 2] -=
+ (Date.now() - game.moves[L-1].played) / 1000;
+ }
}
// Sort chat messages from newest to oldest
game.chats.sort((c1, c2) => {
// Now that we used idx and played, re-format moves as for live games
game.moves = game.moves.map(m => m.squares);
}
- if (gtype == "live" && game.clocks[0] < 0) {
- // Game is unstarted. clocks and initime are ignored until move 2
- game.clocks = [tc.mainTime, tc.mainTime];
- game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
- if (myIdx >= 0) {
- // I play in this live game
- GameStorage.update(game.id, {
- clocks: game.clocks,
- initime: game.initime
- });
+ if (gtype == "live") {
+ if (game.clocks[0] < 0) {
+ // Game is unstarted. clock is ignored until move 2
+ game.clocks = [tc.mainTime, tc.mainTime];
+ if (myIdx >= 0) {
+ // I play in this live game
+ GameStorage.update(game.id, {
+ clocks: game.clocks
+ });
+ }
+ } else {
+ if (!!game.initime)
+ // It's my turn: clocks not updated yet
+ game.clocks[myIdx] -= (Date.now() - game.initime) / 1000;
}
}
// TODO: merge next 2 "if" conditions
GameStorage.get(this.gameRef, callback);
},
re_setClocks: function() {
+ this.virtualClocks = this.game.clocks.map(s => ppt(s).split(':'));
if (this.game.moves.length < 2 || this.game.score != "*") {
// 1st move not completed yet, or game over: freeze time
- this.virtualClocks = this.game.clocks.map(s => ppt(s).split(':'));
return;
}
const currentTurn = this.vr.turn;
const currentMovesCount = this.game.moves.length;
const colorIdx = ["w", "b"].indexOf(currentTurn);
- let countdown =
- this.game.clocks[colorIdx] -
- (Date.now() - this.game.initime[colorIdx]) / 1000;
- this.virtualClocks = [0, 1].map(i => {
- const removeTime =
- i == colorIdx ? (Date.now() - this.game.initime[colorIdx]) / 1000 : 0;
- return ppt(this.game.clocks[i] - removeTime).split(':');
- });
this.clockUpdate = setInterval(
() => {
if (
- countdown < 0 ||
+ this.game.clocks[colorIdx] < 0 ||
this.game.moves.length > currentMovesCount ||
this.game.score != "*"
) {
clearInterval(this.clockUpdate);
- if (countdown < 0)
+ if (this.game.clocks[colorIdx] < 0)
this.gameOver(
currentTurn == "w" ? "0-1" : "1-0",
"Time"
);
- } else
+ } else {
this.$set(
this.virtualClocks,
colorIdx,
- ppt(Math.max(0, --countdown)).split(':')
+ ppt(Math.max(0, --this.game.clocks[colorIdx])).split(':')
);
+ }
},
1000
);
const nextIdx = 1 - colorIdx;
const doProcessMove = () => {
const origMovescount = this.game.moves.length;
- let addTime = 0; //for live games
+ // The move is (about to be) played: stop clock
+ clearInterval(this.clockUpdate);
if (moveCol == this.game.mycolor && !data.receiveMyMove) {
if (this.drawOffer == "received")
// I refuse draw
this.drawOffer = "";
if (this.game.type == "live" && origMovescount >= 2) {
- const elapsed = Date.now() - this.game.initime[colorIdx];
- // elapsed time is measured in milliseconds
- addTime = this.game.increment - elapsed / 1000;
+ this.game.clocks[colorIdx] += this.game.increment;
+ // For a correct display in casqe of disconnected opponent:
+ this.$set(
+ this.virtualClocks,
+ colorIdx,
+ ppt(this.game.clocks[colorIdx]).split(':')
+ );
+ GameStorage.update(this.gameRef, {
+ // It's not my turn anymore:
+ initime: null
+ });
}
}
// Update current game object:
playMove(move, this.vr);
- // The move is played: stop clock
- clearInterval(this.clockUpdate);
if (!data.score)
// Received move, score is computed in BaseGame, but maybe not yet.
// ==> Compute it here, although this is redundant (TODO)
if (data.score != "*") this.gameOver(data.score);
this.game.moves.push(move);
this.game.fen = this.vr.getFen();
- if (this.game.type == "live") {
- if (!!data.clock) this.game.clocks[colorIdx] = data.clock;
- else this.game.clocks[colorIdx] += addTime;
- } else {
+ if (this.game.type == "corr") {
// In corr games, just reset clock to mainTime:
this.game.clocks[colorIdx] = extractTime(this.game.cadence).mainTime;
}
- // NOTE: opponent's initime is reset after "gotmove" is received
- if (
- !this.game.mycolor ||
- moveCol != this.game.mycolor ||
- !!data.receiveMyMove
- ) {
- this.game.initime[nextIdx] = Date.now();
- }
// If repetition detected, consider that a draw offer was received:
const fenObj = this.vr.getFenForRepeat();
this.repeat[fenObj] =
}
// Since corr games are stored at only one location, update should be
// done only by one player for each move:
+ if (
+ this.game.type == "live" &&
+ !!this.game.mycolor &&
+ moveCol != this.game.mycolor &&
+ this.game.moves.length >= 2
+ ) {
+ // Receive a move: update initime
+ this.game.initime = Date.now();
+ GameStorage.update(this.gameRef, {
+ // It's my turn now!
+ initime: this.game.initime
+ });
+ }
if (
!!this.game.mycolor &&
!data.receiveMyMove &&
move: filtered_move,
moveIdx: origMovescount,
clocks: this.game.clocks,
- initime: this.game.initime,
drawOffer: drawCode
});
};
// The board might have been hidden:
if (boardDiv.style.visibility == "hidden")
boardDiv.style.visibility = "visible";
- if (data.score == "*") {
- this.game.initime[nextIdx] = Date.now();
- this.re_setClocks();
- }
+ if (data.score == "*") this.re_setClocks();
}
};
let el = document.querySelector("#buttonsConfirm > .acceptBtn");
encodeURIComponent(this.$route.path);
this.conn = new WebSocket(this.connexionString);
this.conn.onopen = connectAndPoll;
- this.conn.onmessage = this.socketMessageListener;
- this.conn.onclose = this.socketCloseListener;
+ this.conn.addEventListener("message", this.socketMessageListener);
+ this.conn.addEventListener("close", this.socketCloseListener);
},
mounted: function() {
document.addEventListener('visibilitychange', this.visibilityChange);
break;
case "killed":
// I logged in elsewhere:
+ this.conn.removeEventListener("message", this.socketMessageListener);
+ this.conn.removeEventListener("close", this.socketCloseListener);
this.conn = null;
alert(this.st.tr["New connexion detected: tab now offline"]);
break;
// Game state (including FEN): will be updated
moves: [],
clocks: [-1, -1], //-1 = unstarted
- initime: [0, 0], //initialized later
score: "*"
}
);
('Checkered', 'Shared pieces'),
('Chess960', 'Standard rules'),
('Circular', 'Run forward'),
+ ('Coregal', 'Two royal pieces'),
('Crazyhouse', 'Captures reborn'),
('Cylinder', 'Neverending rows'),
('Dark', 'In the shadow'),