From: Benjamin Auder Date: Wed, 6 Jan 2021 10:41:02 +0000 (+0100) Subject: Some fixes + draft Avalanche X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/css/user/doc/html/config.php?a=commitdiff_plain;h=4258b58c6aff86ce69ebfbcd40d704836df27ac9;p=vchess.git Some fixes + draft Avalanche --- diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 5f47904f..0f18dbec 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -1173,6 +1173,7 @@ export const ChessRules = class ChessRules { } updateCastleFlags(move, piece, color) { + // TODO: check flags. If already off, no need to always re-evaluate const c = color || V.GetOppCol(this.turn); const firstRank = (c == "w" ? V.size.x - 1 : 0); // Update castling flags if rooks are moved diff --git a/client/src/translations/en.js b/client/src/translations/en.js index dad1e8c0..59801a78 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -246,6 +246,7 @@ export const translations = { "Non-conformism and utopia": "Non-conformism and utopia", "Occupy the enemy palace": "Occupy the enemy palace", "Paralyzed pieces": "Paralyzed pieces", + "Pawnfalls": "Pawnfalls", "Pawns capture backward": "Pawns capture backward", "Pawns move diagonally": "Pawns move diagonally", "Pieces upside down": "Pieces upside down", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index c1658bc0..5e96bf4e 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -246,6 +246,7 @@ export const translations = { "Non-conformism and utopia": "No-conformismo y utopía", "Occupy the enemy palace": "Ocupar el palacio enemigo", "Paralyzed pieces": "Piezas paralizadas", + "Pawnfalls": "Peones cayendo", "Pawns capture backward": "Los peones capturan hacia atrás", "Pawns move diagonally": "Los peones se mueven en diagonal", "Pieces upside down": "Piezas al revés", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 630437d3..f5e9e6a1 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -246,6 +246,7 @@ export const translations = { "Non-conformism and utopia": "Non-conformisme et utopie", "Occupy the enemy palace": "Occuper le palais ennemi", "Paralyzed pieces": "Pièces paralysées", + "Pawnfalls": "Chutes de pions", "Pawns capture backward": "Les pions capturent en arrière", "Pawns move diagonally": "Les pions vont en diagonale", "Pieces upside down": "Pièces à l'envers", diff --git a/client/src/translations/rules/Avalanche/en.pug b/client/src/translations/rules/Avalanche/en.pug new file mode 100644 index 00000000..8469d057 --- /dev/null +++ b/client/src/translations/rules/Avalanche/en.pug @@ -0,0 +1,34 @@ +p.boxed + | A normal move is always followed by an opponent's pawn push. + +p. + Each turn consists of two parts. The first part is a move + legal with the orthodox chess rules. The second part of the move, + called "push", consists of moving an opponent's pawn a single space forward + (toward the player in turn). + White has no pawn push on the first move ("balanced" Avalanche). + +p. + If no pawn can advance at the second part, then it's + opponent's turn. If a pushed pawn reaches its last rank, + the opponent decides next in which piece it promotes. + +figure.diagram-container + .diagram.diag12 + | fen:rnbqkbnr/1pppp1pp/8/p7/P3Pp2/8/1PPPKPPP/RNBQ1BNR: + .diagram.diag22 + | fen:rnbqkbnr/1pppp1pp/8/p7/P3P3/2N2p2/1PPPKPPP/R1BQ1BNR: + figcaption. + White plays Nc3,f3 (bad idea). Then Black can capture the king. + +p In some cases, kings can be captured. This counts as a win. + +p There are no en-passant captures. + +h3 More information + +p + | See + a(href="https://www.chessvariants.com/mvopponent.dir/avalanche.html") + | Avalanche Chess + |  on chessvariants.com. diff --git a/client/src/translations/rules/Avalanche/es.pug b/client/src/translations/rules/Avalanche/es.pug new file mode 100644 index 00000000..00ccc05a --- /dev/null +++ b/client/src/translations/rules/Avalanche/es.pug @@ -0,0 +1,37 @@ +p.boxed + | Un movimiento normal siempre va seguido de un empujón de un peón contrario. + +p. + Cada ronda consta de dos partes. El primero es un movimiento legal según + reglas ortodoxas. La segunda parte, denominada "empujar", consta de + mueve un peón enemigo hacia adelante una casilla (hacia el jugador al turno). + Las blancas no tienen un empujón en la primera jugada + (Avalancha "Equilibrada"). + +p. + Si ningún peón puede avanzar en el segundo parte, entonces el turno + pasa al oponente. Si un peón empujado llega a su última fila, + luego su dueño decide en qué pieza lo asciende. + +figure.diagram-container + .diagram.diag12 + | fen:rnbqkbnr/1pppp1pp/8/p7/P3Pp2/8/1PPPKPPP/RNBQ1BNR: + .diagram.diag22 + | fen:rnbqkbnr/1pppp1pp/8/p7/P3P3/2N2p2/1PPPKPPP/R1BQ1BNR: + figcaption. + Las blancas juegan Nc3,f3 (mala idea). + Las negras pueden entonces llevarse al rey. + +p. + En algunos casos, se pueden capturar reyes. + Cuenta como una victoria. + +p No hay capturas en-passant. + +h3 Más información + +p + | Ver + a(href="https://www.chessvariants.com/mvopponent.dir/avalanche.html") + | Avalanche Chess + |  en chessvariants.com. diff --git a/client/src/translations/rules/Avalanche/fr.pug b/client/src/translations/rules/Avalanche/fr.pug new file mode 100644 index 00000000..652df0fe --- /dev/null +++ b/client/src/translations/rules/Avalanche/fr.pug @@ -0,0 +1,36 @@ +p.boxed + | Un coup normal est toujours suivi d'une poussée d'un pion adverse. + +p. + Chaque tour consiste en deux parties. La première est un coup légal selon + les règles orthodoxes. La seconde partie, appelée "poussée", consiste à + déplacer un pion ennemi d'une case vers l'avant (vers le joueur au trait). + Les blancs n'ont pas de poussée au premier coup (Avalanche "équilibrée"). + +p. + Si aucun pion ne peut avancer lors de la seconde partie, alors le tour + passe à l'adversaire. Si un pion poussé atteint sa dernière rangée, + alors son propriétaire décide en quelle pièce il est promu. + +figure.diagram-container + .diagram.diag12 + | fen:rnbqkbnr/1pppp1pp/8/p7/P3Pp2/8/1PPPKPPP/RNBQ1BNR: + .diagram.diag22 + | fen:rnbqkbnr/1pppp1pp/8/p7/P3P3/2N2p2/1PPPKPPP/R1BQ1BNR: + figcaption. + Les blancs jouent Nc3,f3 (mauvaise idée). + Les noirs peuvent alors prendre le roi. + +p. + Dans certains cas les rois peuvent être capturés. + Cela compte comme une victoire. + +p Il n'y a pas de captures en passant. + +h3 Plus d'information + +p + | Voir + a(href="https://www.chessvariants.com/mvopponent.dir/avalanche.html") + | Avalanche Chess + |  sur chessvariants.com. diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index fad590cf..6850dc93 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -391,6 +391,7 @@ p. var varlist = [ "Alice", "Ambiguous", + "Avalanche", "Bicolour", "Castle", "Doublearmy", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 2b30fc32..d63fe46a 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -402,6 +402,7 @@ p. var varlist = [ "Alice", "Ambiguous", + "Avalanche", "Bicolour", "Castle", "Doublearmy", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 876fa496..c29a5acb 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -401,6 +401,7 @@ p. var varlist = [ "Alice", "Ambiguous", + "Avalanche", "Bicolour", "Castle", "Doublearmy", diff --git a/client/src/variants/Avalanche.js b/client/src/variants/Avalanche.js new file mode 100644 index 00000000..edeb5648 --- /dev/null +++ b/client/src/variants/Avalanche.js @@ -0,0 +1,382 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; +import { randInt } from "@/utils/alea"; + +export class AvalancheRules extends ChessRules { + + static get PawnSpecs() { + return ( + Object.assign( + { promotions: [V.PAWN] }, + ChessRules.PawnSpecs + ) + ); + } + + static get HasEnpassant() { + return false; + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParts = fen.split(" "); + if (fenParts.length != 5) return false; + if (!fenParts[4].match(/^[0-8]$/)) return false; + return true; + } + + canIplay(side, [x, y]) { + if (this.subTurn == 0) return (x >= V.size.x); + const c = this.getColor(x, y); + return ( + (this.subTurn == 1 && c == side) || + (this.subTurn == 2 && c != side && this.getPiece(x, y) == V.PAWN) + ); + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { promoteFile: fenParts[4] } + ); + } + + getPromoteFen() { + const L = this.promoteFile.length; + return (this.promoteFile[L-1] + 1); + } + + getFen() { + return super.getFen() + " " + this.getPromoteFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getPromoteFen(); + } + + static GenRandInitFen(randomness) { + return ChessRules.GenRandInitFen(randomness).slice(0, -1) + "0"; + } + + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; + return this.board[i][j].charAt(1); + } + + static get RESERVE_PIECES() { + // Promotion pieces + return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + const fenPromoteFile = V.ParseFen(fen).promoteFile; + this.promoteFile = [parseInt(fenPromoteFile, 10) - 1]; + this.reserve = { 'w': null, 'b': null }; + if (this.promoteFile[0] >= 0) { + this.reserve = { + [this.turn]: { + [V.ROOK]: 1, + [V.KNIGHT]: 1, + [V.BISHOP]: 1, + [V.QUEEN]: 1 + } + }; + this.subTurn = 0; + } + else this.subTurn = 1; + } + + getReservePpath(index, color) { + return color + V.RESERVE_PIECES[index]; + } + + getReserveMove(y) { + // Send a new piece piece to our first rank + const color = this.turn; + const L = this.promoteFile.length; + const [rank, file] = [color == 'w' ? 0 : 7, this.promoteFile[L-1]]; + return new Move({ + appear: [ + new PiPo({ x: rank, y: file, c: color, p: V.RESERVE_PIECES[y] }) + ], + vanish: [ + new PiPo({ x: rank, y: file, c: color, p: V.PAWN }) + ], + start: { x: 8, y: y }, + end: { x: rank, y: file } + }); + } + + getPotentialMovesFrom([x, y]) { + if (this.subTurn == 0) + // Reserves, outside of board: x == sizeX(+1) + return (x >= 8 ? [this.getReserveMove(y)] : []); + if (this.subTurn == 1) + // Usual case: + return super.getPotentialMovesFrom([x, y]); + // subTurn == 2: only allowed to push an opponent's pawn (if possible) + const oppPawnShift = (this.turn == 'w' ? 1 : -1); + if ( + V.OnBoard(x + oppPawnShift, y) && + this.board[x + oppPawnShift][y] == V.EMPTY + ) { + return [this.getBasicMove([x, y], [x + oppPawnShift, y])]; + } + return []; + } + + getAllValidMoves() { + if (this.subTurn == 0) { + let moves = []; + for (let y = 0; y < V.RESERVE_PIECES.length; y++) + moves.push(this.getReserveMove(y)); + return moves; + } + if (this.subTurn == 1) + return this.filterValid(super.getAllPotentialMoves()); + // subTurn == 2: move opponent's pawn only + let moves = []; + const oppCol = V.GetOppCol(this.turn); + for (let i = 0; i < 8; i++) { + for (let j = 0; j < 8; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol && + this.getPiece(i, j) == V.PAWN + ) { + Array.prototype.push.apply( + moves, this.getPotentialMovesFrom([i, j])); + } + } + } + return moves; + } + + filterValid(moves) { + if (this.subTurn != 1) return moves; //self-checks by pawns are allowed + return super.filterValid(moves); + } + + atLeastOneMove() { + if (this.subTurn == 0) return true; //TODO: never called in this situation + if (this.subTurn == 1) { + // Cannot use super method: infinite recursive calls + const color = this.turn; + for (let i = 0; i < 8; i++) { + for (let j = 0; j < 8; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { + const moves = this.getPotentialMovesFrom([i, j]); + if (moves.length > 0) { + for (let k = 0; k < moves.length; k++) { + const piece = moves[k].vanish[0].p; + if (piece == V.KING) { + this.kingPos[color] = + [moves[k].appear[0].x, moves[k].appear[0].y]; + } + V.PlayOnBoard(this.board, moves[k]); + const res = !this.underCheck(color); + V.UndoOnBoard(this.board, moves[k]); + if (piece == V.KING) { + this.kingPos[color] = + [moves[k].vanish[0].x, moves[k].vanish[0].y]; + } + if (res) return true; + } + } + } + } + } + return false; + } + // subTurn == 2: need to find an enemy pawn which can advance + const oppCol = V.GetOppCol(this.turn); + const oppPawnShift = (oppCol == 'w' ? -1 : 1); + for (let i = 0; i < 8; i++) { + for (let j = 0; j < 8; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol && + this.getPiece(i, j) == V.PAWN && + V.OnBoard(i + oppPawnShift, j) && + this.board[i + oppPawnShift][j] == V.EMPTY + ) { + return true; + } + } + } + return false; + } + + getCheckSquares() { + if (this.kingPos[this.turn][0] < 0) return []; + return super.getCheckSquares(); + } + + getCurrentScore() { + // If my king disappeared: I lost! + const c = this.turn; + if (this.kingPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0"); + return super.getCurrentScore(); + } + + prePlay(move) { + if (this.subTurn != 1) return; + const c = move.vanish[0].c; + const piece = move.vanish[0].p; + const firstRank = c == "w" ? V.size.x - 1 : 0; + if (piece == V.KING) { + this.kingPos[c] = [move.appear[0].x, move.appear[0].y]; + this.castleFlags[c] = [V.size.y, V.size.y]; + return; + } + const oppCol = V.GetOppCol(c); + if (move.vanish.length == 2 && move.vanish[1].p == V.KING) { + // Opponent's king is captured, game over + this.kingPos[oppCol] = [-1, -1]; + move.captureKing = true; + } + const oppFirstRank = V.size.x - 1 - firstRank; + 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; + } + 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; + } + } + + play(move) { + move.flags = JSON.stringify(this.aggregateFlags()); + this.prePlay(move); + V.PlayOnBoard(this.board, move); + const c = this.turn; + move.turn = [c, this.subTurn]; + const oppCol = V.GetOppCol(c); + const oppLastRank = (c == 'w' ? 7 : 0); + if (this.subTurn <= 1) this.reserve[oppCol] = null; + if (this.subTurn == 0) { + this.subTurn++; + this.reserve[c] = null; + } + else if (this.subTurn == 1) { + this.subTurn++; + if ( + this.movesCount == 0 || + !!move.captureKing || + !this.atLeastOneMove() + ) { + this.turn = oppCol; + this.movesCount++; + this.subTurn = 1; + this.promoteFile.push(-1); + move.pushPromote = true; + } + } + else { + // subTurn == 2 + this.turn = oppCol; + if (move.end.x == oppLastRank) { + this.promoteFile.push(move.end.y); + this.reserve[oppCol] = { + [V.ROOK]: 1, + [V.KNIGHT]: 1, + [V.BISHOP]: 1, + [V.QUEEN]: 1 + }; + this.subTurn = 0; + } + else { + this.subTurn = 1; + this.promoteFile.push(-1); + } + move.pushPromote = true; + this.movesCount++; + } + } + + undo(move) { + this.disaggregateFlags(JSON.parse(move.flags)); + V.UndoOnBoard(this.board, move); + const changeTurn = (this.turn != move.turn[0]); + this.turn = move.turn[0]; + this.subTurn = move.turn[1]; + if (!!move.pushPromote) { + const promoteFile = this.promoteFile.pop(); + if (promoteFile >= 0) this.reserve[V.GetOppCol(this.turn)] = null; + } + else if (this.subTurn == 0) { + this.reserve[this.turn] = { + [V.ROOK]: 1, + [V.KNIGHT]: 1, + [V.BISHOP]: 1, + [V.QUEEN]: 1 + }; + } + if (changeTurn) this.movesCount--; + this.postUndo(move); + } + + postUndo(move) { + if (this.subTurn != 1) return; + if (move.vanish.length == 2 && move.vanish[1].p == V.KING) + // Opponent's king was captured + this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y]; + super.postUndo(move); + } + + getComputerMove() { + // Just try to capture as much material as possible (1-half move) + const moves = this.getAllValidMoves(); + if (this.subTurn == 0) { + this.play(moves[3]); //HACK... 3 = queen index + const res = this.getComputerMove(); + this.undo(moves[3]); + return [moves[3], res]; + } + // subTurn == 1 (necessarily) + let candidates = []; + let maxValue = -V.INFINITY; + for (let m of moves) { + let value = 0; + if (m.vanish.length == 2) { + // Compute delta value, to not give all material on pawns... (TODO) + // 0.5 to favor captures (if same type of piece). + value = 0.5 + + ChessRules.VALUES[m.vanish[1].p] - ChessRules.VALUES[m.vanish[0].p]; + } + if (value > maxValue) { + candidates = [m]; + maxValue = value; + } + else if (value == maxValue) candidates.push(m); + } + const m1 = candidates[randInt(candidates.length)]; + this.play(m1); + let m2 = null; + if (this.subTurn == 2) { + // Just pick a pawn at random + const moves2 = this.getAllValidMoves(); + m2 = moves2[randInt(moves2.length)]; + } + this.undo(m1); + if (!m2) return m1; + return [m1, m2]; + } + + getNotation(move) { + if (this.subTurn == 0) + return move.appear[0].p.toUpperCase() + "@" + V.CoordsToSquare(move.end); + if (this.subTurn == 1) return super.getNotation(move); + // subTurn == 2: just indicate final square + return V.CoordsToSquare(move.end); + } + +}; diff --git a/client/src/variants/Isardam.js b/client/src/variants/Isardam.js index 4ca9a856..d745f150 100644 --- a/client/src/variants/Isardam.js +++ b/client/src/variants/Isardam.js @@ -73,4 +73,8 @@ export class IsardamRules extends ChessRules { ); } + static get SEARCH_DEPTH() { + return 2; + } + }; diff --git a/client/src/variants/Pacosako.js b/client/src/variants/Pacosako.js index 8cec688f..c6f466f1 100644 --- a/client/src/variants/Pacosako.js +++ b/client/src/variants/Pacosako.js @@ -671,27 +671,9 @@ export class PacosakoRules extends ChessRules { return moves.filter(m => !this.oppositeMoves(this.umoves[L - 1], m)); } - play(move) { - move.flags = JSON.stringify(this.aggregateFlags()); - this.epSquares.push(this.getEpSquare(move)); - // Check if the move is the last of the turn: all cases except releases - if (!move.released) { - // No more union releases available - this.turn = V.GetOppCol(this.turn); - this.movesCount++; - this.lastMoveEnd.push(null); - } - 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); - } - updateCastleFlags(move, piece) { - const c = V.GetOppCol(this.turn); + const c = this.turn; const firstRank = (c == "w" ? 7 : 0); - const oppCol = this.turn; - const oppFirstRank = 7 - firstRank; if (piece == V.KING && move.appear.length > 0) this.castleFlags[c] = [V.size.y, V.size.y]; else if ( @@ -701,34 +683,46 @@ export class PacosakoRules extends ChessRules { const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); this.castleFlags[c][flagIdx] = V.size.y; } - // No more checking: a rook in union can take part in castling. + else if ( + move.end.x == firstRank && + this.castleFlags[c].includes(move.end.y) + ) { + // Move to our rook: necessary normal piece, to union, releasing + // (or the rook was moved before!) + const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } } - postPlay(move) { - if (move.vanish.length == 0) - // A released piece just moved. Cannot be the king. - return; - const c = move.vanish[0].c; - const piece = move.vanish[0].p; + prePlay(move) { + // Easier before move is played in this case (flags are saved) + const c = this.turn; + const L = this.lastMoveEnd.length; + const lm = this.lastMoveEnd[L-1]; + const piece = (!!lm ? lm.p : move.vanish[0].p); if (piece == V.KING) this.kingPos[c] = [move.appear[0].x, move.appear[0].y]; this.updateCastleFlags(move, piece); - if ( - [1, 6].includes(move.start.x) && - move.vanish.length >= 1 && - move.appear.length == 1 - ) { - // Does this move turn off a 2-squares pawn flag? - if ( - move.vanish[0].p == V.PAWN || - ( - !(ChessRules.PIECES.includes(move.vanish[0].p)) && - this.getUnionPieces(move.vanish[0].c, move.vanish[0].p)[c] == V.PAWN - ) - ) { - this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; - } + const pawnFirstRank = (c == 'w' ? 6 : 1); + if (move.start.x == pawnFirstRank) + // This move (potentially) turns off a 2-squares pawn flag + this.pawnFlags[c][move.start.y] = false; + } + + play(move) { + move.flags = JSON.stringify(this.aggregateFlags()); + this.prePlay(move); + this.epSquares.push(this.getEpSquare(move)); + // Check if the move is the last of the turn: all cases except releases + if (!move.released) { + // No more union releases available + this.turn = V.GetOppCol(this.turn); + this.movesCount++; + this.lastMoveEnd.push(null); } + else this.lastMoveEnd.push(Object.assign({ p: move.released }, move.end)); + V.PlayOnBoard(this.board, move); + this.umoves.push(this.getUmove(move)); } undo(move) { diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index ebb46753..7d7f234f 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -982,13 +982,13 @@ export default { .getElementById("btnC" + newChall.type) .classList.add("somethingnew"); } - if (!!chall.to) { + if (chall.to == this.st.user.name) { notify( "New challenge", // fromValues.name should exist since the player is online, but // let's consider there is some chance that the challenge arrives // right after we connected and before receiving the poll result: - { body: "from " + (fromValues.name || "unknown yet...") } + { body: "from " + (fromValues.name || "@nonymous") } ); } } diff --git a/server/db/populate.sql b/server/db/populate.sql index 216dd66f..17fe0d0f 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -21,6 +21,7 @@ insert or ignore into Variants (name, description) values ('Arena', 'Middle battle'), ('Atomic1', 'Explosive captures (v1)'), ('Atomic2', 'Explosive captures (v2)'), + ('Avalanche', 'Pawnfalls'), ('Ball', 'Score a goal'), ('Balaklava', 'Meet the Mammoth'), ('Baroque', 'Exotic captures'),