# Various files
/server/db/vchess.sqlite
+/server/db/dbconnect.py
+/server/db/__pycache__/
/server/config/parameters.js
/server/fallback/*
!/server/fallback/README
--- /dev/null
+#$# git-fat 4f0df77ae573ed9f84315aa2284edf8e74133553 967280
// En-passant square, if any
getEpSquare(moveOrSquare) {
- if (!moveOrSquare) return undefined;
+ if (!moveOrSquare) return undefined; //TODO: necessary line?!
if (typeof moveOrSquare === "string") {
const square = moveOrSquare;
if (square == "-") return undefined;
V.OnBoard(rx, ry) &&
this.board[rx][ry] != V.EMPTY &&
this.getPiece(rx, ry) == piece &&
- this.getColor(rx, ry) == color
+ this.getColor(rx, ry) == color &&
+ this.canTake([rx, ry], [x, y])
) {
return true;
}
background-color: #FFCC66
padding: 5px
+.warning
+ background-color: lightyellow
+ color: red
+ font-weight: bold
+
.bigfont
font-size: 1.2em
-p.boxed TODO
+p.boxed
+ | "Capturing" a piece creates an union,
+ | which your opponent can still use on his turn.
+ | Enter an union to release your piece.
-p WARNING 1: totally buggish right now.
-
-p WARNING 2: this variant may in the end not be playable here at all - will depend on the author decision.
+p.
+ The variant's name means "Chess of Peace" in Esperanto.
+ Paco-Sako was invented by Felix Albers in 2017, and further developped
+ also by Rolf Kreibaum and Raimond Fluijt.
p
- a(href="https://www.youtube.com/watch?v=tQ2JLsFvfxI") Video
- | showing gameplay. See also
- a(href="http://pacosako.com/") the main website
- | , and the associated
- a(href="http://pacoplay.com/") playing area
+ | You can learn more about the variant's history and buy nice dedicated
+ | pieces (and boards) on the official website
+ a(href="http://pacosako.com/") pacosako.com
+ | . The variant is playable online at
+ a(href="http://pacoplay.com/") pacoplay.com
| .
+ br
+ | Consequently, Paco-Sako is
+ span.warning not garanteed to remain playable on vchess.club.
+ br
+ | You're invited to play over there instead :-)
+ | Besides, they have cuter unions' drawings.
+
+h3 Basic rules
+
+p.
+ There are no captures in this game: only unions of pieces,
+ which are released when replaced by another friendly piece.
+ The goal is to create an union with the enemy king.
+ I like to think of unions as "pieces dancing together", so both
+ terms will be used on this page.
+
+figure.showPieces.text-center
+ img(src="/images/pieces/Pacosako/wc.png")
+ img(src="/images/pieces/Pacosako/bc.png")
+ img(src="/images/pieces/Pacosako/bt.png")
+ img(src="/images/pieces/Pacosako/wv.png")
+ figcaption Some union pieces.
+
+p.
+ At each turn, a player chooses either one of his pieces or an union piece;
+ let's write this piece A.
+ul
+ li.
+ Case 1: A is a dancing piece. Then, it's only allowed to move
+ to a vacant square according to our piece's type.
+ li.
+ Case 2: A is a standard piece.
+ It can then be moved anywhere but on our own (normal) pieces.
+ "Capturing" an enemy piece creates an union composed of both pieces.
+ "Capturing" an union releases our piece formerly in union,
+ which has to be moved immediately by the same player. It can in turn
+ release another piece, thus following a chain of unions.
+
+p
+ | This may appear confusing at first reading, but is simpler than it seems.
+ | See for example this
+ a(href="https://www.youtube.com/watch?v=tQ2JLsFvfxI") gameplay video
+ | , or another one from the same YouTube channel.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:4k3/8/2q5/8/4O3/2w2B2/8/5K2:
+ .diagram.diag22
+ | fen:4k3/8/2Y5/8/4s3/2S5/8/5K2:
+ figcaption Before and after the chaining move Bxe4, Ne4xc3, Qc3xc6.
+
+h3 Special moves, additional notes
+
+p.
+ "Capturing" an union en passant releases our dancing piece from the
+ intermediate square.
+
+p Promotion occur when any pawn (in union or not) reaches its final rank.
+
+p.
+ Attacks on the king are ignored in this implementation: you can run
+ or remain into "check". So, castling conditions are quite permissive.
+ Also, if you form an union with your king but end dancing with the
+ other king on the other end of the chain, the game is a draw.
+ span.warning This does not follow (at all) the official rules.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:rnbq1r2/1ppppp1k/p6p/4P1OP/1PPP3c/3B4/P2V1PP1/R2QK1N1:
+ .diagram.diag22
+ | fen:rnbq1r2/1ppppp1k/p7/4P1dP/1PPPn2c/3B4/P2V1PP1/R2QK1N1:
+ figcaption.
+ Left: Bd3(+) can be covered by Right: h6xg5 (releasing the knight), Ne4.
+
+p.
+ Canceling an union move is forbidden. For example if a bishop is
+ dancing with a queen, and makes the move e5 to g3, the other player cannot
+ move it back to e5 just after. This is also non-official.
+
+h3 More information
+
+p
+ | The authors wrote
+ a(href="/variants/Pacosako/manual.pdf") a manual
+ | with many more diagrams and explanations.
-p.boxed TODO
+p.boxed
+ | "Capturar" una pieza crea una unión, que tu oponente aún puede
+ | utilizar en turno. Entra en una unión para entregar tu pieza.
-p WARNING 1: totally buggish right now.
-
-p WARNING 2: this variant may in the end not be playable here at all - will depend on the author decision.
+p.
+ El nombre de la variante significa "El Ajedrez de la Paz" en Esperanto.
+ Paco-Sako fue inventado por Felix Albers en 2017, y luego desarrollado
+ también por Rolf Kreibaum y Raimond Fluijt.
p
- a(href="https://www.youtube.com/watch?v=tQ2JLsFvfxI") Video
- | showing gameplay. See also
- a(href="http://pacosako.com/") the main website
- | , and the associated
- a(href="http://pacoplay.com/") playing area
+ | Puede obtener más información sobre el historial de la variante y comprar
+ | piezas bastante dedicadas (y tableros) en el sitio web oficial
+ a(href="http://pacosako.com/") pacosako.com
+ | . La variante se puede jugar en línea en
+ a(href="http://pacoplay.com/") pacoplay.com
| .
+ br
+ | Por tanto, Paco-Sako
+ span.warning no se garantiza que siga siendo jugable en vchess.club.
+ br
+ | Puedes ir a jugar allí en su lugar :-)
+ | Además, los diseños de las uniones son más lindos.
+
+h3 Reglas básicas
+
+p.
+ No hay capturas en este juego: solo uniones de piezas,
+ que se emiten cuando otras piezas amigas los reemplazan.
+ El objetivo es crear una unión con el rey contrario.
+ Me gusta pensar en las uniones en términos de "piezas bailando juntas",
+ por lo tanto, ambos términos se utilizarán en esta página.
+
+figure.showPieces.text-center
+ img(src="/images/pieces/Pacosako/wc.png")
+ img(src="/images/pieces/Pacosako/bc.png")
+ img(src="/images/pieces/Pacosako/bt.png")
+ img(src="/images/pieces/Pacosako/wv.png")
+ figcaption Algunas uniones de piezas.
+
+p.
+ Cada turno, un jugador selecciona una de sus piezas o
+ una pieza-unión; denotar por A.
+ul
+ li.
+ Caso 1: A es una pieza bailando. Entonces ella solo puede moverse
+ a una casilla vacía dependiendo del tipo de nuestra pieza.
+ li.
+ Caso 2: A es una pieza estándar.
+ Luego se puede mover a cualquier lugar excepto en nuestras propias piezas
+ (normales). "Capturar" una pieza enemiga crea una unión formada por
+ dos piezas. "Capturar" una unión libera nuestra pieza en ella, y
+ debe ser movido inmediatamente por el mismo jugador. Ella puede
+ a su vez entregan otras piezas, siguiendo así una cadena de uniones.
+
+p
+ | Esto puede parecer indigerible en la primera lectura, pero es más fácil
+ | de lo que parece. Ver por ejemplo esto
+ a(href="https://www.youtube.com/watch?v=tQ2JLsFvfxI") video de gameplay
+ | , u otro del mismo canal de YouTube.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:4k3/8/2q5/8/4O3/2w2B2/8/5K2:
+ .diagram.diag22
+ | fen:4k3/8/2Y5/8/4s3/2S5/8/5K2:
+ figcaption.
+ Antes y después del movimiento de "encadenamiento" Bxe4, Ne4xc3, Qc3xc6.
+
+h3 Movimientos especiales, notas adicionales
+
+p.
+ "Capturar" una unión en passant libera nuestra pieza bailando despues la
+ casilla intermedia.
+
+p.
+ Una promoción tiene lugar cuando cualquier peón (posiblemente en unión)
+ llegó a su última fila.
+
+p.
+ Los ataques al rey se ignoran en esta implementación:
+ puede ir o permanecer en "jaque". Por lo tanto, las condiciones de enroque
+ son encontrar relajado.
+ Si formas una unión con tu rey pero terminas bailando con
+ el rey oponente en el otro extremo de la cadena, el juego se empata.
+ span.warning Esto no sigue las reglas oficiales (en absoluto).
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:rnbq1r2/1ppppp1k/p6p/4P1OP/1PPP3c/3B4/P2V1PP1/R2QK1N1:
+ .diagram.diag22
+ | fen:rnbq1r2/1ppppp1k/p7/4P1dP/1PPPn2c/3B4/P2V1PP1/R2QK1N1:
+ figcaption.
+ Izquierda: Bd3(+) puede ser bloqueado por
+ Derecha: h6xg5 (liberando al caballo), Ne4.
+
+p.
+ Está prohibido cancelar una jugada de unión. Por ejemplo, si un alfil
+ baila con una reina y hace un movimiento de e5 a g3, el otro jugador no
+ puede lo reemplace en e5 inmediatamente después. Esto tampoco es oficial.
+
+h3 Más información
+
+p
+ | Los autores escribieron
+ a(href="/variants/Pacosako/manual.pdf") un manual
+ | con muchos más diagramas y explicaciones.
-p.boxed TODO
+p.boxed
+ | "Capturer" une pièce crée une union, que votre adversaire peut encore
+ | utiliser sur son tour. Entrez dans une union pour délivrer votre pièce.
-p WARNING 1: totally buggish right now.
-
-p WARNING 2: this variant may in the end not be playable here at all - will depend on the author decision.
+p.
+ Le nom de la variante signifie "Les Échecs de la Paix" en Esperanto.
+ Paco-Sako a été inventée par Felix Albers en 2017, et développée ensuite
+ également par Rolf Kreibaum et Raimond Fluijt.
p
- a(href="https://www.youtube.com/watch?v=tQ2JLsFvfxI") Video
- | showing gameplay. See also
- a(href="http://pacosako.com/") the main website
- | , and the associated
- a(href="http://pacoplay.com/") playing area
+ | Vous pouvez en apprendre plus sur l'histoire de la variante et acheter
+ | de jolies pièces (et échiquiers) dédiées sur le site officiel
+ a(href="http://pacosako.com/") pacosako.com
+ | . La variante est jouable en ligne sur
+ a(href="http://pacoplay.com/") pacoplay.com
| .
+ br
+ | Par conséquent, Paco-Sako n'est
+ span.warning pas garantie de rester jouable sur vchess.club.
+ br
+ | Vous êtes invités à plutôt aller y jouer là-bas :-)
+ | En outre, les dessins des unions sont plus mignons.
+
+h3 Règles de base
+
+p.
+ Il n'y a pas de captures dans ce jeu : seulement des unions de pièces,
+ qui sont délivrées quand d'autres pièces amies les remplacent.
+ L'objectif est de créer une union avec le roi adverse.
+ J'aime penser aux unions en terme de "pièces dansant ensemble",
+ donc les deux termes seront utilisés sur cette page.
+
+figure.showPieces.text-center
+ img(src="/images/pieces/Pacosako/wc.png")
+ img(src="/images/pieces/Pacosako/bc.png")
+ img(src="/images/pieces/Pacosako/bt.png")
+ img(src="/images/pieces/Pacosako/wv.png")
+ figcaption Quelques pièces unions.
+
+p.
+ À chaque tour, un joueur sélectionne l'une de ses pièces ou
+ une pièce-union ; notons la A.
+ul
+ li.
+ Cas 1 : A est une pièce dansante. Alors, elle ne peut que se déplacer
+ vers une case vide selon le type de notre pièce.
+ li.
+ Cas 2 : A est une pièce standard.
+ Elle peut alors être déplacée n'importe où sauf sur nos propres pièces
+ (normales). "Capturer" une pièce ennemie crée une union composée des
+ deux pièces. "Capturer" une union libère notre pièce s'y trouvant, et
+ celle-ci doit être déplacée immédiatement par le même joueur. Elle peut
+ à son tour délivrer d'autres pièces, suivant ainsi une chaines d'unions.
+
+p
+ | Ceci peut paraître indigeste à première lecture, mais c'est plus simple
+ | que ça en a l'air. Voyez par exemple cette
+ a(href="https://www.youtube.com/watch?v=tQ2JLsFvfxI") vidéo de gameplay
+ | , ou une autre de la même chaîne YouTube.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:4k3/8/2q5/8/4O3/2w2B2/8/5K2:
+ .diagram.diag22
+ | fen:4k3/8/2Y5/8/4s3/2S5/8/5K2:
+ figcaption Avant et après le coup "chaînant" Bxe4, Ne4xc3, Qc3xc6.
+
+h3 Coups spéciaux, notes additionnelles
+
+p.
+ "Capturer" une union en passant libère notre pièce dansante depuis la
+ case intermédiaire.
+
+p.
+ Une promotion a lieu quand n'importe quel pion (éventuellement en union)
+ atteint sa dernière rangée.
+
+p.
+ Les attaques sur le roi sont ignorées dans cette implémentation : vous
+ pouvez aller ou rester en "échec". Ainsi, les conditions du roque se
+ retrouvent assouplie.
+ Si vous formez une union avec votre roi mais terminez par danser avec
+ le roi adverse à l'autre bout de la chaîne, la partie est nulle.
+ span.warning Cela ne suit pas (du tout) les règles officielles.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:rnbq1r2/1ppppp1k/p6p/4P1OP/1PPP3c/3B4/P2V1PP1/R2QK1N1:
+ .diagram.diag22
+ | fen:rnbq1r2/1ppppp1k/p7/4P1dP/1PPPn2c/3B4/P2V1PP1/R2QK1N1:
+ figcaption.
+ Gauche : Bd3(+) peut être paré par
+ Droite : h6xg5 (libérant le cavalier), Ne4.
+
+p.
+ Annuler un coup d'union est interdit. Par exemple, si un fou danse avec
+ une dame, et effectue un déplacement de e5 en g3, l'autre joueur ne peut
+ pas la replacer en e5 immédiatement après. Ceci est également non-officiel.
+
+h3 Plus d'information
+
+p
+ | Les auteurs ont écrit
+ a(href="/variants/Pacosako/manual.pdf") un manuel
+ | avec bien plus de diagrammes et d'explications.
return "Pacosako/" + b;
}
- getPPath(m) {
+ getPPpath(m) {
if (ChessRules.PIECES.includes(m.appear[0].p)) return super.getPPpath(m);
// For an union, show only relevant piece:
// The color must be deduced from the move: reaching final rank of who?
- const color = (m.appear[0].x == 0 ? 'b' : 'w');
- const up = this.getUnionPieces(color, m.appear[0].p);
- return color + up[color];
+ const color = (m.appear[0].x == 0 ? 'w' : 'b');
+ const up = this.getUnionPieces(m.appear[0].c, m.appear[0].p);
+ return "Pacosako/" + color + up[color];
}
canTake([x1, y1], [x2, y2]) {
- const c1 = this.getColor(x1, y1);
- const c2 = this.getColor(x2, y2);
- return (c1 != 'u' && c2 != c1);
+ const p1 = this.board[x1][y1].charAt(1);
+ if (!(ChessRules.PIECES.includes(p1))) return false;
+ const p2 = this.board[x2][y2].charAt(1);
+ if (!(ChessRules.PIECES.includes(p2))) return true;
+ const c1 = this.board[x1][y1].charAt(0);
+ const c2 = this.board[x2][y2].charAt(0);
+ return (c1 != c2);
}
canIplay(side, [x, y]) {
- return this.turn == side && this.getColor(x, y) != V.GetOppCol(side);
+ return (
+ this.turn == side &&
+ (
+ !(ChessRules.PIECES.includes(this.board[x][y].charAt(1))) ||
+ this.board[x][y].charAt(0) == side
+ )
+ );
}
scanKings(fen) {
super.setOtherVariables(fen);
// Stack of "last move" only for intermediate chaining
this.lastMoveEnd = [null];
+ // Local stack of non-capturing union moves:
+ this.umoves = [];
+ const umove = V.ParseFen(fen).umove;
+ if (umove == "-") this.umoves.push(null);
+ else {
+ this.umoves.push({
+ start: ChessRules.SquareToCoords(umove.substr(0, 2)),
+ end: ChessRules.SquareToCoords(umove.substr(2))
+ });
+ }
+ }
+
+ static IsGoodFen(fen) {
+ if (!ChessRules.IsGoodFen(fen)) return false;
+ const fenParts = fen.split(" ");
+ if (fenParts.length != 6) return false;
+ if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/))
+ return false;
+ return true;
+ }
+
+ getUmove(move) {
+ if (
+ move.vanish.length == 1 &&
+ !(ChessRules.PIECES.includes(move.appear[0].p))
+ ) {
+ // An union moving
+ return { start: move.start, end: move.end };
+ }
+ return null;
+ }
+
+ static ParseFen(fen) {
+ const fenParts = fen.split(" ");
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ { umove: fenParts[5] }
+ );
+ }
+
+ static GenRandInitFen(randomness) {
+ // Add empty umove
+ return ChessRules.GenRandInitFen(randomness) + " -";
+ }
+
+ getUmoveFen() {
+ const L = this.umoves.length;
+ return (
+ !this.umoves[L - 1]
+ ? "-"
+ : ChessRules.CoordsToSquare(this.umoves[L - 1].start) +
+ ChessRules.CoordsToSquare(this.umoves[L - 1].end)
+ );
+ }
+
+ getFen() {
+ return super.getFen() + " " + this.getUmoveFen();
+ }
+
+ getFenForRepeat() {
+ return super.getFenForRepeat() + "_" + this.getUmoveFen();
}
getColor(i, j) {
const p = this.board[i][j].charAt(1);
if (ChessRules.PIECES.includes(p)) return super.getColor(i, j);
- return 'u'; //union
+ return this.turn; //union: I can use it, so it's "my" color...
}
getPiece(i, j, color) {
};
}
+ // p1: white piece, p2: black piece
getUnionCode(p1, p2) {
let uIdx = (
Object.values(V.UNIONS).findIndex(v => v[0] == p1 && v[1] == p2)
}
getBasicMove([sx, sy], [ex, ey], tr) {
- const initColor = this.board[sx][sy].charAt(0);
- const initPiece = this.board[sx][sy].charAt(1);
+ const L = this.lastMoveEnd.length;
+ const lm = this.lastMoveEnd[L-1];
+ const piece = (!!lm ? lm.p : null);
+ const initColor = (!!piece ? this.turn : this.board[sx][sy].charAt(0));
+ const initPiece = (piece || this.board[sx][sy].charAt(1));
+ const c = this.turn;
+ const oppCol = V.GetOppCol(c);
+ if (!!tr && !(ChessRules.PIECES.includes(initPiece))) {
+ // Transformation computed without taking union into account
+ const up = this.getUnionPieces(initColor, initPiece);
+ let args = [tr.p, up[oppCol]];
+ if (c == 'b') args = args.reverse();
+ const cp = this.getUnionCode(args[0], args[1]);
+ tr.c = cp.c;
+ tr.p = cp.p;
+ }
// 4 cases : moving
// - union to free square (other cases are illegal: return null)
// - normal piece to free square,
// to enemy normal piece, or
// to union (releasing our piece)
let mv = new Move({
- vanish: [
+ start: { x: sx, y: sy },
+ end: { x: ex, y: ey },
+ vanish: []
+ });
+ if (!piece) {
+ mv.vanish = [
new PiPo({
x: sx,
y: sy,
c: initColor,
p: initPiece
})
- ],
- end: { x: ex, y: ey }
- });
+ ];
+ }
// Treat free square cases first:
if (this.board[ex][ey] == V.EMPTY) {
mv.appear = [
new PiPo({
x: ex,
y: ey,
- c: initColor,
+ c: !!tr ? tr.c : initColor,
p: !!tr ? tr.p : initPiece
})
];
);
if (ChessRules.PIECES.includes(destPiece)) {
// Normal piece: just create union
- const cp = this.getUnionCode(!!tr ? tr.p : initPiece, destPiece);
+ let args = [!!tr ? tr.p : initPiece, destPiece];
+ if (c == 'b') args = args.reverse();
+ const cp = this.getUnionCode(args[0], args[1]);
mv.appear = [
new PiPo({
x: ex,
}
// Releasing a piece in an union: keep track of released piece
const up = this.getUnionPieces(destColor, destPiece);
- const c = this.turn;
- const oppCol = V.GetOppCol(c);
- const cp = this.getUnionCode(!!tr ? tr.p : initPiece, up[oppCol])
+ let args = [!!tr ? tr.p : initPiece, up[oppCol]];
+ if (c == 'b') args = args.reverse();
+ const cp = this.getUnionCode(args[0], args[1]);
mv.appear = [
new PiPo({
x: ex,
return mv;
}
- getPotentialMoves([x, y]) {
+ getPotentialMovesFrom([x, y]) {
const L = this.lastMoveEnd.length;
const lm = this.lastMoveEnd[L-1];
- let piece = null;
+ if (!!lm && (x != lm.x || y != lm.y)) return [];
+ const piece = (!!lm ? lm.p : this.getPiece(x, y));
if (!!lm) {
- if (x != lm.x || y != lm.y) return [];
- piece = lm.p;
- }
- if (!!piece) {
- var unionOnBoard = this.board[x][y];
+ var saveSquare = this.board[x][y];
this.board[x][y] = this.turn + piece;
}
let baseMoves = [];
// When a pawn in an union reaches final rank with a non-standard
// promotion move: apply promotion anyway
let moves = [];
+ const c = this.turn;
+ const oppCol = V.GetOppCol(c);
+ const oppLastRank = (c == 'w' ? 7 : 0);
baseMoves.forEach(m => {
- // (move to first rank, which is last rank for opponent [pawn]), should show promotion choices.
- //if (m. //bring enemy pawn to his first rank ==> union types involved... color...
- moves.push(m); //TODO
+ if (
+ m.end.x == oppLastRank &&
+ ['c', 'd', 'e', 'f', 'g'].includes(m.appear[0].p)
+ ) {
+ // Move to first rank, which is last rank for opponent's pawn.
+ // => Show promotion choices.
+ // Find our piece in union (not a pawn)
+ const up = this.getUnionPieces(m.appear[0].c, m.appear[0].p);
+ // merge with all potential promotion pieces + push (loop)
+ for (let promotionPiece of [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]) {
+ let args = [up[c], promotionPiece];
+ if (c == 'b') args = args.reverse();
+ const cp = this.getUnionCode(args[0], args[1]);
+ let cpMove = JSON.parse(JSON.stringify(m));
+ cpMove.appear[0].c = cp.c;
+ cpMove.appear[0].p = cp.p;
+ moves.push(cpMove);
+ }
+ }
+ else {
+ if (
+ m.vanish.length > 0 &&
+ m.vanish[0].p == V.PAWN &&
+ m.start.y != m.end.y &&
+ this.board[m.end.x][m.end.y] == V.EMPTY
+ ) {
+ if (!!lm)
+ // No en-passant inside a chaining
+ return;
+ // Fix en-passant capture: union type, maybe released piece too
+ const cs = [m.end.x + (c == 'w' ? 1 : -1), m.end.y];
+ const color = this.board[cs[0]][cs[1]].charAt(0);
+ const code = this.board[cs[0]][cs[1]].charAt(1);
+ if (code == V.PAWN) {
+ // Simple en-passant capture (usual: just form union)
+ m.appear[0].c = 'w';
+ m.appear[0].p = 'a';
+ }
+ else {
+ // An union pawn + something juste moved two squares
+ const up = this.getUnionPieces(color, code);
+ m.released = up[c];
+ let args = [V.PAWN, up[oppCol]];
+ if (c == 'b') args = args.reverse();
+ const cp = this.getUnionCode(args[0], args[1]);
+ m.appear[0].c = cp.c;
+ m.appear[0].p = cp.p;
+ }
+ }
+ moves.push(m);
+ }
});
- if (!!piece) this.board[x][y] = unionOnBoard;
+ if (!!lm) this.board[x][y] = saveSquare;
return moves;
}
+ getEpSquare(moveOrSquare) {
+ if (typeof moveOrSquare === "string") {
+ const square = moveOrSquare;
+ if (square == "-") return undefined;
+ return V.SquareToCoords(square);
+ }
+ const move = moveOrSquare;
+ const s = move.start,
+ e = move.end;
+ const oppCol = V.GetOppCol(this.turn);
+ if (
+ s.y == e.y &&
+ Math.abs(s.x - e.x) == 2 &&
+ this.getPiece(s.x, s.y, oppCol) == V.PAWN
+ ) {
+ return {
+ x: (s.x + e.x) / 2,
+ y: s.y
+ };
+ }
+ return undefined;
+ }
+
+ // Does m2 un-do m1 ? (to disallow undoing union moves)
+ oppositeMoves(m1, m2) {
+ return (
+ !!m1 &&
+ !(ChessRules.PIECES.includes(m2.appear[0].p)) &&
+ m2.vanish.length == 1 &&
+ m1.start.x == m2.end.x &&
+ m1.end.x == m2.start.x &&
+ m1.start.y == m2.end.y &&
+ m1.end.y == m2.start.y
+ );
+ }
+
+ // Do not consider checks for now (TODO)
+ underCheck() {
+ return false;
+ }
+ getCheckSquares() {
+ return [];
+ }
+ filterValid(moves) {
+ if (moves.length == 0) return [];
+ const L = this.umoves.length; //at least 1: init from FEN
+ return moves.filter(m => !this.oppositeMoves(this.umoves[L - 1], m));
+ }
+
play(move) {
this.epSquares.push(this.getEpSquare(move));
// Check if the move is the last of the turn: all cases except releases
- move.last = (
- move.vanish.length == 1 ||
- ChessRules.PIECES.includes(move.vanish[1].p)
- );
- if (move.last) {
+ if (!move.released) {
// No more union releases available
this.turn = V.GetOppCol(this.turn);
this.movesCount++;
this.lastMoveEnd.push(null);
}
- else {
- const color = this.board[move.end.x][move.end.y].charAt(0);
- const oldUnion = this.board[move.end.x][move.end.y].charAt(1);
- const released = this.getUnionPieces(color, oldUnion)[this.turn];
- this.lastMoveEnd.push(Object.assign({}, move.end, { p: released }));
- }
+ else this.lastMoveEnd.push(Object.assign({ p: move.released }, move.end));
V.PlayOnBoard(this.board, move);
+ this.umoves.push(this.getUmove(move));
this.postPlay(move);
}
+ postPlay(move) {
+ if (move.vanish.length == 0)
+ // A piece released just moved. Cannot be the king.
+ return;
+ const c = move.vanish[0].c;
+ const piece = move.vanish[0].p;
+ if (piece == V.KING)
+ this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
+ this.updateCastleFlags(move, piece);
+ }
+
undo(move) {
this.epSquares.pop();
V.UndoOnBoard(this.board, move);
this.lastMoveEnd.pop();
- if (move.last) {
+ if (!move.released) {
this.turn = V.GetOppCol(this.turn);
this.movesCount--;
}
+ this.umoves.pop();
this.postUndo(move);
}
+ postUndo(move) {
+ if (this.getPiece(move.start.x, move.start.y) == V.KING)
+ this.kingPos[this.turn] = [move.start.x, move.start.y];
+ }
+
getCurrentScore() {
// Check kings: if one is dancing, the side lost
+ // But, if both dancing, let's say it's a draw :-)
const [kpW, kpB] = [this.kingPos['w'], this.kingPos['b']];
- if (this.board[kpB[0]][kpB[1]].charAt(1) != 'k') return "1-0";
- if (this.board[kpW[0]][kpW[1]].charAt(1) != 'k') return "0-1";
+ const atKingPlace = [
+ this.board[kpW[0]][kpW[1]].charAt(1),
+ this.board[kpB[0]][kpB[1]].charAt(1)
+ ];
+ if (!atKingPlace.includes('k')) return "1/2";
+ if (atKingPlace[0] != 'k') return "0-1";
+ if (atKingPlace[1] != 'k') return "1-0";
return "*";
}
getComputerMove() {
- let moves = this.getAllValidMoves();
- if (moves.length == 0) return null;
- // Just play random moves (for now at least. TODO?)
- let mvArray = [];
- while (moves.length > 0) {
- const mv = moves[randInt(moves.length)];
- mvArray.push(mv);
- this.play(mv);
- if (!mv.last)
- // A piece was just released from an union
- moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
- else break;
+ let initMoves = this.getAllValidMoves();
+ if (initMoves.length == 0) return null;
+ // Loop until valid move is found (no blocked pawn released...)
+ while (true) {
+ let moves = JSON.parse(JSON.stringify(initMoves));
+ let mvArray = [];
+ let mv = null;
+ // Just play random moves (for now at least. TODO?)
+ while (moves.length > 0) {
+ mv = moves[randInt(moves.length)];
+ mvArray.push(mv);
+ this.play(mv);
+ if (!!mv.released)
+ // A piece was just released from an union
+ moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
+ else break;
+ }
+ for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+ if (!mv.released) return (mvArray.length > 1 ? mvArray : mvArray[0]);
}
- for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
- return (mvArray.length > 1 ? mvArray : mvArray[0]);
}
// NOTE: evalPosition() is wrong, but unused since bot plays at random
getNotation(move) {
- // TODO: in case of enemy pawn promoted, add "=..." in the end
- return super.getNotation(move);
+ if (move.appear.length == 2 && move.appear[0].p == V.KING)
+ return (move.end.y < move.start.y ? "0-0-0" : "0-0");
+
+ const c = this.turn;
+ const L = this.lastMoveEnd.length;
+ const lm = this.lastMoveEnd[L-1];
+ let piece = null;
+ if (!lm && move.vanish.length == 0)
+ // When importing a game, the info move.released is lost
+ piece = move.appear[0].p;
+ else piece = (!!lm ? lm.p : move.vanish[0].p);
+ if (!(ChessRules.PIECES.includes(piece))) {
+ // Decode (moving) union
+ const up = this.getUnionPieces(
+ move.vanish.length > 0 ? move.vanish[0].c : move.appear[0].c, piece);
+ piece = up[c]
+ }
+
+ // Basic move notation:
+ let notation = piece.toUpperCase();
+ if (
+ this.board[move.end.x][move.end.y] != V.EMPTY ||
+ (piece == V.PAWN && move.start.y != move.end.y)
+ ) {
+ notation += "x";
+ }
+ const finalSquare = V.CoordsToSquare(move.end);
+ notation += finalSquare;
+
+ // Add potential promotion indications:
+ const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]);
+ if (move.end.x == firstLastRank[1] && piece == V.PAWN) {
+ const up = this.getUnionPieces(move.appear[0].c, move.appear[0].p);
+ notation += "=" + up[c].toUpperCase();
+ }
+ else if (
+ move.end.x == firstLastRank[0] &&
+ move.vanish.length > 0 &&
+ ['c', 'd', 'e', 'f', 'g'].includes(move.vanish[0].p)
+ ) {
+ // We promoted an opponent's pawn
+ const oppCol = V.GetOppCol(c);
+ const up = this.getUnionPieces(move.appear[0].c, move.appear[0].p);
+ notation += "=" + up[oppCol].toUpperCase();
+ }
+
+ return notation;
}
};
"DELETE",
{ data: { gid: this.game.id } }
);
- } else {
+ }
+ else {
// Live game
GameStorage.update(this.gameRef, { delchat: true });
}
// For self multi-connects tests:
this.newConnect[data.from[0]] = true;
this.send("askidentity", { target: data.from[0] });
- } else {
+ }
+ else {
this.people[data.from[0]].tmpIds[data.from[1]] = { focus: true };
this.$forceUpdate(); //TODO: shouldn't be required
}
) {
this.send("asklastate", { target: user.sid });
counter++;
- } else {
- clearInterval(this.askLastate);
}
+ else clearInterval(this.askLastate);
},
1500
);
gameInfo.players.some(p => p.sid == this.st.user.sid)
) {
this.addAndGotoLiveGame(gameInfo);
- } else if (
+ }
+ else if (
gameType == "corr" &&
this.st.user.id > 0 &&
gameInfo.players.some(p => p.id == this.st.user.id)
) {
this.$router.push("/game/" + gameInfo.id);
- } else {
+ }
+ else {
this.rematchId = gameInfo.id;
document.getElementById("modalRules").checked = false;
document.getElementById("modalScore").checked = false;
// Just got last move from him
this.$refs["basegame"].play(data.lastMove, "received");
this.processMove(data.lastMove);
- } else {
+ }
+ else {
if (!!this.clockUpdate) clearInterval(this.clockUpdate);
this.re_setClocks();
}
: "Three repetitions";
this.send("draw", { data: message });
this.gameOver("1/2", message);
- } else if (this.drawOffer == "") {
+ }
+ else if (this.drawOffer == "") {
// No effect if drawOffer == "sent"
if (this.game.mycolor != this.vr.turn) {
alert(this.st.tr["Draw offer only in your turn"]);
this.gameRef,
{ drawOffer: this.game.mycolor }
);
- } else this.updateCorrGame({ drawOffer: this.game.mycolor });
+ }
+ else this.updateCorrGame({ drawOffer: this.game.mycolor });
}
},
addAndGotoLiveGame: function(gameInfo, callback) {
}
);
}
- } else if (this.rematchOffer == "") {
+ }
+ else if (this.rematchOffer == "") {
this.rematchOffer = "sent";
this.send("rematchoffer", { data: true });
if (this.game.type == "live") {
this.gameRef,
{ rematchOffer: this.game.mycolor }
);
- } else this.updateCorrGame({ rematchOffer: this.game.mycolor });
- } else if (this.rematchOffer == "sent") {
+ }
+ else this.updateCorrGame({ rematchOffer: this.game.mycolor });
+ }
+ else if (this.rematchOffer == "sent") {
// Toggle rematch offer (on --> off)
this.rematchOffer = "";
this.send("rematchoffer", { data: false });
this.gameRef,
{ rematchOffer: '' }
);
- } else this.updateCorrGame({ rematchOffer: 'n' });
+ }
+ else this.updateCorrGame({ rematchOffer: 'n' });
}
},
abortGame: function() {
{ clocks: game.clocks }
);
}
- } else {
- if (!!game.initime)
- // It's my turn: clocks not updated yet
- game.clocks[myIdx] -= (Date.now() - game.initime) / 1000;
}
+ else if (!!game.initime)
+ // It's my turn: clocks not updated yet
+ game.clocks[myIdx] -= (Date.now() - game.initime) / 1000;
}
else
// gtype == "import"
currentTurn == "w" ? "0-1" : "1-0",
"Time"
);
- } else {
+ }
+ else {
this.$set(
this.virtualClocks,
colorIdx,
}
}
);
- } else addChallenges();
+ }
+ else addChallenges();
}
}
);
// For self multi-connects tests:
this.newConnect[data.from[0]] = true;
this.send("askidentity", { target: data.from[0], page: page });
- } else {
+ }
+ else {
this.people[data.from[0]].tmpIds[data.from[1]] =
{ page: page, focus: true };
this.$forceUpdate(); //TODO: shouldn't be required
"all"
);
}
- } else {
+ }
+ else {
// Remove the matching live game if now unreachable
const gid = data.page.match(/[a-zA-Z0-9]+$/)[0];
// Corr games are always reachable:
);
});
this.games = this.games.concat(moreGames);
- } else this.hasMore = false;
+ }
+ else this.hasMore = false;
}
}
);
position: parsedFen.position
//,orientation: parsedFen.turn
});
- } else this.newchallenge.diag = "";
+ }
+ else this.newchallenge.diag = "";
},
newChallFromPreset(pchall) {
this.partialResetNewchallenge();
if (ctype == "live") {
// Live challenges have a random ID
finishAddChallenge(null);
- } else {
+ }
+ else {
// Correspondence game: send challenge to server
ajax(
"/challenges",
else
// Corr challenge: just remove the challenge
this.send("deletechallenge_s", { data: { cid: c.id } });
- } else {
+ }
+ else {
const oppsid = this.getOppsid(c);
if (!!oppsid)
this.send("refusechallenge", { data: c.id, target: oppsid });
if (c.type == "live") {
notifyNewgame();
this.startNewGame(gameInfo);
- } else {
+ // Increment game stats counter in DB
+ ajax(
+ "/gamestat",
+ "POST",
+ { data: { vid: gameInfo.vid } }
+ );
+ }
+ else {
// corr: game only on server
ajax(
"/games",
foreign key (vid) references Variants(id)
);
+create table GameStat (
+ vid integer,
+ total integer default 0,
+ foreign key (vid) references Variants(id)
+);
+
create table Games (
id integer primary key,
vid integer,
--- /dev/null
+#!/usr/bin/env python
+
+import sqlite3
+from sqlite3 import Error
+
+vchess_db_path = "/path/to/vchess.sqlite"
+
+def create_connection():
+ """
+ Create a database connection to the vchess SQLite database
+ :return: Connection object or None
+ """
+
+ conn = None
+ try:
+ conn = sqlite3.connect(vchess_db_path)
+ except Error as e:
+ print(e)
+
+ return conn
--- /dev/null
+#!/usr/bin/env python
+
+# Manually (for now: TODO) add an entry in GameStat when a variant is added
+
+from dbconnect import create_connection
+
+def sync_gamestat():
+ """
+ (Incrementally) Synchronize GameStat table from Variants update
+ """
+
+ conn = create_connection()
+ cur = conn.cursor()
+
+ cur.execute("SELECT max(vid) FROM GameStat");
+ vid_max = cur.fetchone()[0] or 0
+ cur.execute("SELECT id FROM Variants WHERE id > ?", (vid_max,))
+ rows = cur.fetchall()
+ for variant in rows:
+ cur.execute("INSERT INTO GameStat(vid) VALUES (?)", (variant[0],))
+
+ conn.commit()
+ cur.close()
+
+sync_gamestat()
);
},
+ incrementCounter: function(vid, cb) {
+ db.serialize(function() {
+ let query =
+ "UPDATE GameStat " +
+ "SET total = total + 1 " +
+ "WHERE vid = " + vid;
+ db.run(query, cb);
+ });
+ },
+
create: function(vid, fen, randomness, cadence, players, cb) {
db.serialize(function() {
let query =
const access = require("../utils/access");
const params = require("../config/parameters");
+router.post("/gamestat", access.ajax, (req,res) => {
+ const vid = req.body.vid;
+ if (!!vid && !!vid.toString().match(/^[0-9]+$/)) {
+ GameModel.incrementCounter(vid);
+ res.json({});
+ }
+});
+
// From main hall, start game between players 0 and 1
router.post("/games", access.logged, access.ajax, (req,res) => {
const gameInfo = req.body.gameInfo;