From 39fe711a185ee73c907f3d61ddd459a33f40696b Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 6 Jan 2021 15:54:10 +0100 Subject: [PATCH] Fix PocketKnight + add Screen variant --- TODO | 3 +- client/src/components/BaseGame.vue | 12 +- client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + client/src/translations/rules/Screen/en.pug | 18 ++ client/src/translations/rules/Screen/es.pug | 19 ++ client/src/translations/rules/Screen/fr.pug | 19 ++ client/src/translations/variants/en.pug | 1 + client/src/translations/variants/es.pug | 1 + client/src/translations/variants/fr.pug | 1 + client/src/variants/Pocketknight.js | 6 + client/src/variants/Screen.js | 263 ++++++++++++++++++++ server/db/populate.sql | 1 + 14 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 client/src/translations/rules/Screen/en.pug create mode 100644 client/src/translations/rules/Screen/es.pug create mode 100644 client/src/translations/rules/Screen/fr.pug create mode 100644 client/src/variants/Screen.js diff --git a/TODO b/TODO index 68e8b7ad..5f03a4dc 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,6 @@ If new live game starts in background, "new game" notify OK but not first move. NEW VARIANTS: https://www.chessvariants.com/incinf.dir/bario.html -https://www.chessvariants.com/mvopponent.dir/avalanche.html https://www.pychess.org/variant/manchu https://www.pychess.org/variant/dobutsu https://musketeerchess.net/games/musketeer/index.php Attention règle de promotion + SVG / PNG @@ -15,8 +14,10 @@ https://www.chessvariants.com/other.dir/fugue.html https://www.chessvariants.com/rules/spartan-chess https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html + https://brainking.com/en/GameRules?tp=47&fwa=ArchivedGame!g=8204276$i=1 related to: Crown Chess: place all units on move 1 (similar to Sittuyin, more freely --> in own half-board, possible pawns on 1st rank) + http://history.chess.free.fr/rollerball.htm Squatter Chess: safe on last rank = win Companion Chess : pieces of same nature don't attack each others diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index ee8b1fcb..f1017024 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -520,7 +520,8 @@ export default { } this.inMultimove = true; //potentially this.cursor++; - } else if (!navigate) { + } + else if (!navigate) { // Already in the middle of a multi-move const L = this.moves.length; if (!Array.isArray(this.moves[L-1])) @@ -548,7 +549,8 @@ export default { if (moveIdx < move.length) setTimeout(executeMove, 500); else afterMove(smove, initurn); }); - } else { + } + else { playSubmove(smove); if (moveIdx < move.length) executeMove(); else afterMove(smove, initurn); @@ -600,7 +602,8 @@ export default { const L = this.moves.length; // NOTE: always emit the score, even in unfinished this.$emit("newmove", this.moves[L-1], { score: this.score }); - } else { + } + else { this.inPlay = false; if (this.stackToPlay.length > 0) // Move(s) arrived in-between @@ -659,7 +662,8 @@ export default { this.incheck = this.vr.getCheckSquares(); if (this.cursor >= 0) this.lastMove = this.moves[this.cursor]; else this.lastMove = null; - } else { + } + else { if (!move) { const minCursor = this.moves.length > 0 && this.moves[0].notation == "..." diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 59801a78..0aebead6 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -205,6 +205,7 @@ export const translations = { "Extra bishops and knights": "Extra bishops and knights", "Faster development": "Faster development", "Four new pieces": "Four new pieces", + "Free initial setup": "Free initial setup", "In the shadow": "In the shadow", "Interweaved colorbound teams": "Interweaved colorbound teams", "Get strong at self-mate": "Get strong at self-mate", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 5e96bf4e..2a9bb365 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -205,6 +205,7 @@ export const translations = { "Extra bishops and knights": "Alfiles y caballos adicionales", "Faster development": "Desarrollo acelerado", "Four new pieces": "Quatro nuevas piezas", + "Free initial setup": "Posición inicial libre", "In the shadow": "En la sombra", "Interweaved colorbound teams": "Equipos unicolores entrelazados", "Get strong at self-mate": "Progreso en mates asistidos", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index f5e9e6a1..7552b3a2 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -205,6 +205,7 @@ export const translations = { "Extra bishops and knights": "Fous et cavaliers supplémentaires", "Faster development": "Développement accéléré", "Four new pieces": "Quatre nouvelles pièces", + "Free initial setup": "Position initiale libre", "In the shadow": "Dans l'ombre", "Interweaved colorbound teams": "Équipes unicolores entremêlées", "Get strong at self-mate": "Progressez en mats aidés", diff --git a/client/src/translations/rules/Screen/en.pug b/client/src/translations/rules/Screen/en.pug new file mode 100644 index 00000000..f8237a29 --- /dev/null +++ b/client/src/translations/rules/Screen/en.pug @@ -0,0 +1,18 @@ +p.boxed + | Place all your pieces freely on first turn. Then play normally. + +p. + Each side first chooses a setup freely, and independently: + initial positions are revealed only after both are determined. + +p Some constraints apply: +ul + li Bishops on different colors. + li One pawn per column, no pawn on first rank. + +h3 More information + +p + | See the + a(href="https://brainking.com/en/GameRules?tp=47") Screen Chess + |  variant on brainkings.com. diff --git a/client/src/translations/rules/Screen/es.pug b/client/src/translations/rules/Screen/es.pug new file mode 100644 index 00000000..924665c4 --- /dev/null +++ b/client/src/translations/rules/Screen/es.pug @@ -0,0 +1,19 @@ +p.boxed + | Coloca todas tus piezas libremente en el primer turno. + | Entonces juega normalmente. + +p. + Cada lado elige primero una configuración de forma libre e independiente: + las posiciones iniciales no se revelan hasta que se hayan determinado ambas. + +p Se aplican algunas restricciones: +ul + li Los alfiles en diferentes colores. + li Un peón por columna, no hay peones en la primera fila. + +h3 Más información + +p + | Ver la variante + a(href="https://brainking.com/en/GameRules?tp=47") Screen Chess + |  en brainkings.com. diff --git a/client/src/translations/rules/Screen/fr.pug b/client/src/translations/rules/Screen/fr.pug new file mode 100644 index 00000000..980e7948 --- /dev/null +++ b/client/src/translations/rules/Screen/fr.pug @@ -0,0 +1,19 @@ +p.boxed + | Placez toutes vos pièces librement au premier tour. + | Jouez ensuite normalement. + +p. + Chaque camp choisit d'abord une configuration librement, et indépendamment : + les positions initiales ne sont révélées qu'une fois toutes deux déterminées. + +p Quelques contraintes s'appliquent : +ul + li Les fous sur des couleurs différentes. + li Un pion par colonne, pas de pion sur la première rangée. + +h3 Plus d'information + +p + | Voir la variante + a(href="https://brainking.com/en/GameRules?tp=47") Screen Chess + |  sur brainkings.com. diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 6850dc93..9baec9d1 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -404,6 +404,7 @@ p. "Magnetic", "Pacosako", "Parachute", + "Screen", "Takenmake", "Titan", "Wormhole" diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index d63fe46a..e1eb1902 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -415,6 +415,7 @@ p. "Magnetic", "Pacosako", "Parachute", + "Screen", "Takenmake", "Titan", "Wormhole" diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index c29a5acb..209211ae 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -414,6 +414,7 @@ p. "Magnetic", "Pacosako", "Parachute", + "Screen", "Takenmake", "Titan", "Wormhole" diff --git a/client/src/variants/Pocketknight.js b/client/src/variants/Pocketknight.js index 8756ab9f..f40265e6 100644 --- a/client/src/variants/Pocketknight.js +++ b/client/src/variants/Pocketknight.js @@ -45,6 +45,12 @@ export class PocketknightRules extends ChessRules { ); } + canIplay(side, [x, y]) { + if (this.subTurn == 1) return super.canIplay(side, [x, y]); + // subturn == 2, drop the knight: + return side == this.turn && this.board[x][y] == V.EMPTY; + } + getPotentialMovesFrom([x, y]) { if (this.subTurn == 1) { let moves = super.getPotentialMovesFrom([x, y]); diff --git a/client/src/variants/Screen.js b/client/src/variants/Screen.js new file mode 100644 index 00000000..0a8a75e2 --- /dev/null +++ b/client/src/variants/Screen.js @@ -0,0 +1,263 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; +import { randInt } from "@/utils/alea"; +import { ArrayFun } from "@/utils/array"; + +export class ScreenRules extends ChessRules { + + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + get showFirstTurn() { + return true; + } + + get canAnalyze() { + return this.movesCount >= 2; + } + + get someHiddenMoves() { + return this.movesCount <= 1; + } + + static GenRandInitFen() { + // Empty board + return "8/8/8/8/8/8/8/8 w 0"; + } + + re_setReserve(subTurn) { + const mc = this.movesCount; + const wc = (mc == 0 ? 1 : 0); + const bc = (mc <= 1 ? 1 : 0); + this.reserve = { + w: { + [V.PAWN]: wc * 8, + [V.ROOK]: wc * 2, + [V.KNIGHT]: wc * 2, + [V.BISHOP]: wc * 2, + [V.QUEEN]: wc, + [V.KING]: wc + }, + b: { + [V.PAWN]: bc * 8, + [V.ROOK]: bc * 2, + [V.KNIGHT]: bc * 2, + [V.BISHOP]: bc * 2, + [V.QUEEN]: bc, + [V.KING]: bc + } + } + this.subTurn = subTurn || 1; + } + + re_setEnlightened(onOff) { + if (!onOff) delete this["enlightened"]; + else { + // Turn on: + this.enlightened = { + 'w': ArrayFun.init(8, 8, false), + 'b': ArrayFun.init(8, 8, false) + }; + for (let i=0; i<4; i++) { + for (let j=0; j<8; j++) this.enlightened['b'][i][j] = true; + } + for (let i=5; i<8; i++) { + for (let j=0; j<8; j++) this.enlightened['w'][i][j] = true; + } + } + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + if (this.movesCount <= 1) { + this.re_setReserve(); + this.re_setEnlightened(true); + } + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; + return this.board[i][j].charAt(1); + } + + getReservePpath(index, color) { + return color + V.RESERVE_PIECES[index]; + } + + static get RESERVE_PIECES() { + return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING]; + } + + getPotentialMovesFrom([x, y]) { + if (this.movesCount >= 2) return super.getPotentialMovesFrom([x, y]); + // Only reserve moves are allowed for now: + if (V.OnBoard(x, y)) return []; + const color = this.turn; + const p = V.RESERVE_PIECES[y]; + if (this.reserve[color][p] == 0) return []; + const shift = (p == V.PAWN ? 1 : 0); + let iBound = (color == 'w' ? [4, 7 - shift] : [shift, 3]); + let moves = []; + + // Pawns cannot stack on files, one bishop per color + let forbiddenFiles = []; + if (p == V.PAWN) { + const colorShift = (color == 'w' ? 4 : 1); + forbiddenFiles = + ArrayFun.range(8).filter(jj => { + return ArrayFun.range(3).some(ii => { + return ( + this.board[colorShift + ii][jj] != V.EMPTY && + this.getPiece(colorShift + ii, jj) == V.PAWN + ); + }) + }); + } + let forbiddenColor = -1; + if (p == V.BISHOP) { + const colorShift = (color == 'w' ? 4 : 0); + outerLoop: for (let ii = colorShift; ii < colorShift + 4; ii++) { + for (let jj = 0; jj < 8; jj++) { + if ( + this.board[ii][jj] != V.EMPTY && + this.getPiece(ii, jj) == V.BISHOP + ) { + forbiddenColor = (ii + jj) % 2; + break outerLoop; + } + } + } + } + + for (let i = iBound[0]; i <= iBound[1]; i++) { + for (let j = 0; j < 8; j++) { + if ( + this.board[i][j] == V.EMPTY && + (p != V.PAWN || !forbiddenFiles.includes(j)) && + (p != V.BISHOP || (i + j) % 2 != forbiddenColor) + ) { + // Ok, move is valid: + let mv = new Move({ + appear: [ + new PiPo({ + x: i, + y: j, + c: color, + p: p + }) + ], + vanish: [], + start: { x: x, y: y }, + end: { x: i, y: j } + }); + moves.push(mv); + } + } + } + moves.forEach(m => { m.end.noHighlight = true; }); + return moves; + } + + underCheck(color) { + if (this.movesCount <= 1) return false; + return super.underCheck(color); + } + + getAllValidMoves() { + if (this.movesCount >= 2) return super.getAllValidMoves(); + const color = this.turn; + let moves = []; + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + moves = moves.concat( + this.getPotentialMovesFrom([V.size.x + (color == "w" ? 0 : 1), i]) + ); + } + return this.filterValid(moves); + } + + play(move) { + const color = move.appear[0].c; + if (this.movesCount <= 1) { + V.PlayOnBoard(this.board, move); + const piece = move.appear[0].p; + this.reserve[color][piece]--; + if (piece == V.KING) this.kingPos[color] = [move.end.x, move.end.y]; + if (this.subTurn == 16) { + // All placement moves are done + this.movesCount++; + this.turn = V.GetOppCol(color); + if (this.movesCount == 1) this.subTurn = 1; + else { + // Initial placement is over + delete this["reserve"]; + delete this["subTurn"]; + } + } + else this.subTurn++; + } + else { + if (this.movesCount == 2) this.re_setEnlightened(false); + super.play(move); + } + } + + undo(move) { + const color = move.appear[0].c; + if (this.movesCount <= 2) { + V.UndoOnBoard(this.board, move); + const piece = move.appear[0].p; + if (piece == V.KING) this.kingPos[color] = [-1, -1]; + if (!this.subTurn || this.subTurn == 1) { + // All placement moves are undone (if any) + if (!this.subTurn) this.re_setReserve(16); + else this.subTurn = 16; + this.movesCount--; + if (this.movesCount == 1) this.re_setEnlightened(true); + this.turn = color; + } + else this.subTurn--; + this.reserve[color][piece]++; + } + else super.undo(move); + } + + getCheckSquares() { + if (this.movesCount <= 1) return []; + return super.getCheckSquares(); + } + + getCurrentScore() { + if (this.movesCount <= 1) return "*"; + return super.getCurrentScore(); + } + + getComputerMove() { + if (this.movesCount >= 2) return super.getComputerMove(); + // Play a random "initialization move" + let res = []; + for (let i=0; i<16; i++) { + const moves = this.getAllValidMoves(); + const moveIdx = randInt(moves.length); + this.play(moves[moveIdx]); + res.push(moves[moveIdx]); + } + for (let i=15; i>=0; i--) this.undo(res[i]); + return res; + } + + getNotation(move) { + // Do not note placement moves (complete move would be too long) + if (move.vanish.length == 0) return ""; + return super.getNotation(move); + } + +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 17fe0d0f..e1156e5d 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -109,6 +109,7 @@ insert or ignore into Variants (name, description) values ('Royalrace', 'Kings cross the 11x11 board'), ('Rugby', 'Transform an essay'), ('Schess', 'Seirawan-Harper Chess'), + ('Screen', 'Free initial setup'), ('Shako', 'Non-conformism and utopia'), ('Shatranj', 'Ancient rules'), ('Shogi', 'Japanese Chess'), -- 2.44.0