From f0a812b7b11bc2a1514d2aa10ecc257d10d988d5 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 26 Jan 2021 23:11:44 +0100 Subject: [PATCH] Add Ice Age Chess --- client/public/images/pieces/Iceage/SOURCE | 1 + .../public/images/pieces/Iceage/icecube.svg | 163 ++++++++++++++++++ client/src/base_rules.js | 4 + client/src/translations/en.js | 5 +- client/src/translations/es.js | 5 +- client/src/translations/fr.js | 5 +- client/src/translations/rules/Iceage/en.pug | 43 +++++ client/src/translations/rules/Iceage/es.pug | 48 ++++++ client/src/translations/rules/Iceage/fr.pug | 48 ++++++ client/src/translations/variants/en.pug | 1 + client/src/translations/variants/es.pug | 1 + client/src/translations/variants/fr.pug | 1 + client/src/variants/Iceage.js | 143 +++++++++++++++ client/src/views/Game.vue | 24 +-- server/db/populate.sql | 1 + 15 files changed, 476 insertions(+), 17 deletions(-) create mode 100644 client/public/images/pieces/Iceage/SOURCE create mode 100644 client/public/images/pieces/Iceage/icecube.svg create mode 100644 client/src/translations/rules/Iceage/en.pug create mode 100644 client/src/translations/rules/Iceage/es.pug create mode 100644 client/src/translations/rules/Iceage/fr.pug create mode 100644 client/src/variants/Iceage.js diff --git a/client/public/images/pieces/Iceage/SOURCE b/client/public/images/pieces/Iceage/SOURCE new file mode 100644 index 00000000..764be1fc --- /dev/null +++ b/client/public/images/pieces/Iceage/SOURCE @@ -0,0 +1 @@ +https://all-free-download.com/free-vector/download/ice-cube-clip-art_23220.html diff --git a/client/public/images/pieces/Iceage/icecube.svg b/client/public/images/pieces/Iceage/icecube.svg new file mode 100644 index 00000000..b3de8f91 --- /dev/null +++ b/client/public/images/pieces/Iceage/icecube.svg @@ -0,0 +1,163 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index aff47109..df150262 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -135,6 +135,10 @@ export const ChessRules = class ChessRules { static get LoseOnRepetition() { return false; } + // And in some others (Iceage), repetitions should be ignored: + static get IgnoreRepetition() { + return false; + } // At some stages, some games could wait clicks only: onlyClick() { diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 7bf8e720..cd10d039 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -222,14 +222,15 @@ export const translations = { "Friendly pieces": "Friendly pieces", "Fusion pieces (v1)": "Fusion pieces (v1)", "Fusion pieces (v2)": "Fusion pieces (v2)", - "In the shadow": "In the shadow", - "Interweaved colorbound teams": "Interweaved colorbound teams", "General's Chess": "General's Chess", "Geometric Chess": "Geometric Chess", "Get strong at self-mate": "Get strong at self-mate", "Give three checks": "Give three checks", "Harassed kings": "Harassed kings", "Hawaiian Checkers": "Hawaiian Checkers", + "Ice Age is coming!": "Ice Age is coming!", + "In the shadow": "In the shadow", + "Interweaved colorbound teams": "Interweaved colorbound teams", "Japanese Chess": "Japanese Chess", "Jump the borders": "Jump the borders", "Keep antiking in check (v1)": "Keep antiking in check (v1)", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 3154983b..11d64a48 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -222,14 +222,15 @@ export const translations = { "Friendly pieces": "Piezas amistosas", "Fusion pieces (v1)": "Fusionar piezas (v1)", "Fusion pieces (v2)": "Fusionar piezas (v2)", - "In the shadow": "En la sombra", - "Interweaved colorbound teams": "Equipos unicolores entrelazados", "General's Chess": "Ajedrez de los Generales", "Geometric Chess": "Ajedrez geométrico", "Get strong at self-mate": "Progreso en mates asistidos", "Give three checks": "Dar tres jaques", "Harassed kings": "Reyes acosados", "Hawaiian Checkers": "Damas hawaianas", + "Ice Age is coming!": "¡Se acerca la Edad de Hielo!", + "In the shadow": "En la sombra", + "Interweaved colorbound teams": "Equipos unicolores entrelazados", "Japanese Chess": "Ajedrez japonés", "Jump the borders": "Saltar las fronteras", "Keep antiking in check (v1)": "Mantener el antirey en jaque (v1)", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index a0518ee3..4d134fc2 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -222,14 +222,15 @@ export const translations = { "Friendly pieces": "Pièces amies", "Fusion pieces (v1)": "Fusionner les pièces (v1)", "Fusion pieces (v2)": "Fusionner les pièces (v2)", - "In the shadow": "Dans l'ombre", - "Interweaved colorbound teams": "Équipes unicolores entremêlées", "General's Chess": "Échecs des Généraux", "Geometric Chess": "Échecs géométriques", "Get strong at self-mate": "Progressez en mats aidés", "Give three checks": "Donnez trois échecs", "Harassed kings": "Rois harcelés", "Hawaiian Checkers": "Dames hawaïennes", + "Ice Age is coming!": "L'Âge de Glace arrive !", + "In the shadow": "Dans l'ombre", + "Interweaved colorbound teams": "Équipes unicolores entremêlées", "Japanese Chess": "Échecs japonais", "Jump the borders": "Sauter les frontières", "Keep antiking in check (v1)": "Gardez l'antiroi en échec (v1)", diff --git a/client/src/translations/rules/Iceage/en.pug b/client/src/translations/rules/Iceage/en.pug new file mode 100644 index 00000000..fbcac3ca --- /dev/null +++ b/client/src/translations/rules/Iceage/en.pug @@ -0,0 +1,43 @@ +p.boxed + | Every 20 moves, most empty squares freeze and isolated pieces vanish. + +p + | Ice Age is a + a(href="https://www.chessvariants.com/boardrules.dir/iceage.html") + | chess variant + |  invented by Köksal Karakus in 2000. + | It can also be played on + a(href="https://brainking.com/en/GameRules?tp=100") brainking.com + | . + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/cccccccc/cccccccc/cccccccc/cccccccc/PPPPPPPP/RNBQKBNR: + figcaption Deterministic initial position + +p. + If the board is filled with ice cubes, you can only move by capturing them. + Ice cubes behave as normal pieces, which cannot move. + +p. + Just after the 20th black move (and before the 21st white one), + "ice age is coming": +ul + li all empty squares freeze, except those orthogonally surrounded by pieces. + li all pieces which are not adjacent to any other piece (black or white) vanish. +p. + If only one king remains, the other side loses. + But if both disappeared, then the result is a draw. + +figure.diagram-container + .diagram.diag12 + | fen:2rq1rk1/3bpp2/1c1p1bP1/1p2n3/4P2P/1NN2P1c/PPPQ4/1K1R3R: + .diagram.diag22 + | fen:cccq1rkc/cccbppcc/cccp1bPc/ccccnccc/ccccPccc/cNrccPcc/PPPQcccc/1K1Rcccc: + figcaption Before and after 20...Rxc3 + +p. + Then, every 20 moves ice age comes again: after move 40, 60, and so on. + This condition excepted, the game follows FIDE chess rules, without draws + on repetition: indeed, as the author notices, sometimes a win is possible + by "waiting until the enemy king freezes". diff --git a/client/src/translations/rules/Iceage/es.pug b/client/src/translations/rules/Iceage/es.pug new file mode 100644 index 00000000..03328437 --- /dev/null +++ b/client/src/translations/rules/Iceage/es.pug @@ -0,0 +1,48 @@ +p.boxed + | Cada 20 movimientos, la mayoría de las casillas vacías se congelan + | y las piezas aisladas desaparecen. + +p + | Ice Age es + a(href="https://www.chessvariants.com/boardrules.dir/iceage.html") + | una variante + |  inventado por Köksal Karakus en 2000. + | También puedes jugarlo en + a(href="https://brainking.com/en/GameRules?tp=100") brainking.com + | . + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/cccccccc/cccccccc/cccccccc/cccccccc/PPPPPPPP/RNBQKBNR: + figcaption Posición inicial determinista + +p. + Si el tablero está lleno de bloques de hielo, solo puedes mover + capturarlos. Se comportan como piezas estacionarias normales. + +p. + Inmediatamente después del 20mo movimiento negro (y antes del 20mo + movimiento blanco), "la edad de hielo se acerca": +ul + li. + todas las casillas vacías se congelan, + excepto aquellas rodeadas ortogonalmente por piezas. + li. + todas las piezas que no están adyacentes a otra habitación + (amigo o enemigo) desaparece. +p. + Si solo queda un rey, el otro lado pierde. + Pero si ambos se van, entonces el juego es un empate. + +figure.diagram-container + .diagram.diag12 + | fen:2rq1rk1/3bpp2/1c1p1bP1/1p2n3/4P2P/1NN2P1c/PPPQ4/1K1R3R: + .diagram.diag22 + | fen:cccq1rkc/cccbppcc/cccp1bPc/ccccnccc/ccccPccc/cNrccPcc/PPPQcccc/1K1Rcccc: + figcaption Antes y después de 20...Rxc3 + +p. + Luego, cada 20 jugadas regresa la edad de hielo: después del 40, 60, + etc. Salvo esta condición, el juego sigue las reglas de ajedrez de la FIDE, + sin los empates por repetición: de hecho, como señala el autor, + a veces puedes ganar "esperando que el rey contrario se congele". diff --git a/client/src/translations/rules/Iceage/fr.pug b/client/src/translations/rules/Iceage/fr.pug new file mode 100644 index 00000000..8f285b73 --- /dev/null +++ b/client/src/translations/rules/Iceage/fr.pug @@ -0,0 +1,48 @@ +p.boxed + | Tous les 20 coups, la plupart des cases vides gèlent + | et les pièces isolées disparaissent. + +p + | Ice Age est + a(href="https://www.chessvariants.com/boardrules.dir/iceage.html") + | une variante + |  inventée par Köksal Karakus en 2000. + | On peut aussi y jouer sur + a(href="https://brainking.com/en/GameRules?tp=100") brainking.com + | . + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/cccccccc/cccccccc/cccccccc/cccccccc/PPPPPPPP/RNBQKBNR: + figcaption Position initiale déterministe + +p. + Si l'échiquier est rempli de blocs de glace, vous ne pouvez bougez qu'en + les capturant. Ceux-ci se comportent comme des pièces normales, immobiles. + +p. + Immédiatement après le 20eme coup noir (et avant le 21 coup blanc), + "l'âge de glace arrive" : +ul + li. + toutes les cases vides gèlent, + sauf celles orthogonalement entourées de pièces. + li. + toutes les pièces qui ne sont pas adjacentes à une autre pièce + (amie ou ennemie) disparaissent. +p. + Si un seul roi subsiste, l'autre camp perd. + Mais si les deux ont disparu, alors la partie est nulle. + +figure.diagram-container + .diagram.diag12 + | fen:2rq1rk1/3bpp2/1c1p1bP1/1p2n3/4P2P/1NN2P1c/PPPQ4/1K1R3R: + .diagram.diag22 + | fen:cccq1rkc/cccbppcc/cccp1bPc/ccccnccc/ccccPccc/cNrccPcc/PPPQcccc/1K1Rcccc: + figcaption Avant et après 20...Rxc3 + +p. + Ensuite, tous les 20 coups l'âge de glace revient : après le 40eme, 60eme, + etc. Cette condition exceptée, la partie suit les règles FIDE des échecs, + sans les nulles par répétition : en effet, comme l'auteur le fait remarquer, + on peut parfois gagner en "attendant que le roi adverse gèle". diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 2c14ba6d..a895843b 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -467,6 +467,7 @@ p. "Fusion", "Gridolina", "Hamilton", + "Iceage", "Kingsmaker", "Magnetic", "Refusal", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 69756b12..609ac032 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -477,6 +477,7 @@ p. "Fusion", "Gridolina", "Hamilton", + "Iceage", "Kingsmaker", "Magnetic", "Refusal", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 91585e93..6c846970 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -475,6 +475,7 @@ p. "Fusion", "Gridolina", "Hamilton", + "Iceage", "Kingsmaker", "Magnetic", "Refusal", diff --git a/client/src/variants/Iceage.js b/client/src/variants/Iceage.js new file mode 100644 index 00000000..01f82d85 --- /dev/null +++ b/client/src/variants/Iceage.js @@ -0,0 +1,143 @@ +import { ChessRules } from "@/base_rules"; + +export class IceageRules extends ChessRules { + + static get IgnoreRepetition() { + return true; + } + + static get ICECUBE() { + return "cc"; + } + + static board2fen(b) { + if (b[0] == 'c') return 'c'; + return ChessRules.board2fen(b); + } + + static fen2board(f) { + if (f == 'c') return V.ICECUBE; + return ChessRules.fen2board(f); + } + + getPpath(b) { + if (b[0] == 'c') return "Iceage/icecube"; + return b; + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + let kings = { "k": 0, "K": 0 }; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (['K','k'].includes(row[i])) kings[row[i]]++; + if (['c'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num)) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + if (Object.values(kings).some(v => v != 1)) return false; + return true; + } + + static GenRandInitFen(randomness) { + return ChessRules.GenRandInitFen(randomness).replace(/8/g, "cccccccc"); + } + + play(move) { + const iceAgeAfterMove = (this.movesCount % 40 == 39); + if (iceAgeAfterMove) + // Next ice age after this move: + move.state = JSON.stringify(this.board); + super.play(move); + if (iceAgeAfterMove) { + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if (this.board[i][j] == V.EMPTY) { + const surrounded = V.steps[V.ROOK].every(s => { + const [ii, jj] = [i + s[0], j + s[1]]; + return ( + !V.OnBoard(ii, jj) || + ![V.EMPTY, V.ICECUBE].includes(this.board[ii][jj]) + ); + }); + if (!surrounded) this.board[i][j] = V.ICECUBE; + } + else if (this.board[i][j] != V.ICECUBE) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const connected = steps.some(s => { + const [ii, jj] = [i + s[0], j + s[1]]; + return ( + V.OnBoard(ii, jj) && + ![V.EMPTY, V.ICECUBE].includes(this.board[ii][jj]) + ); + }); + if (!connected) this.board[i][j] = V.ICECUBE; + } + } + } + // Update king position (no need to update flags: game over) + const kp = this.kingPos; + if (this.getPiece(kp['w'][0], kp['w'][1]) != V.KING) + this.kingPos['w'] = [-1, -1]; + if (this.getPiece(kp['b'][0], kp['b'][1]) != V.KING) + this.kingPos['b'] = [-1, -1]; + } + } + + undo(move) { + super.undo(move); + if (!!move.state) { + this.board = JSON.parse(move.state); + if (this.kingPos['w'][0] < 0 || this.kingPos['b'][0] < 0) { + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if (this.board[i][j] != V.EMPTY && this.getPiece(i, j) == V.KING) + this.kingPos[this.getColor(i, j)] = [i, j]; + } + } + } + } + } + + getCheckSquares() { + if (this.kingPos[this.turn][0] < 0) return []; + return super.getCheckSquares(); + } + + getCurrentScore() { + const kingDisappear = { + w: this.kingPos['w'][0] < 0, + b: this.kingPos['b'][0] < 0 + }; + if (kingDisappear['w'] && kingDisappear['b']) return "1/2"; + if (kingDisappear['w']) return "0-1"; + if (kingDisappear['b']) return "1-0"; + return super.getCurrentScore(); + } + + static get SEARCH_DEPTH() { + return 2; + } + + evalPosition() { + let evaluation = 0; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (![V.EMPTY,V.ICECUBE].includes(this.board[i][j])) { + const sign = this.getColor(i, j) == "w" ? 1 : -1; + evaluation += sign * V.VALUES[this.getPiece(i, j)]; + } + } + } + return evaluation; + } + +}; diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 6aea261e..65d46460 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -1406,18 +1406,20 @@ export default { // In corr games, just reset clock to mainTime: this.game.clocks[colorIdx] = extractTime(this.game.cadence).mainTime; } - // If repetition detected, consider that a draw offer was received: - const fenObj = this.vr.getFenForRepeat(); - this.repeat[fenObj] = - !!this.repeat[fenObj] - ? this.repeat[fenObj] + 1 - : 1; - if (this.repeat[fenObj] >= 3) { - if (V.LoseOnRepetition) - this.gameOver(moveCol == "w" ? "0-1" : "1-0", "Repetition"); - else this.drawOffer = "threerep"; + if (!V.IgnoreRepetition) { + // If repetition detected, consider that a draw offer was received: + const fenObj = this.vr.getFenForRepeat(); + this.repeat[fenObj] = + !!this.repeat[fenObj] + ? this.repeat[fenObj] + 1 + : 1; + if (this.repeat[fenObj] >= 3) { + if (V.LoseOnRepetition) + this.gameOver(moveCol == "w" ? "0-1" : "1-0", "Repetition"); + else this.drawOffer = "threerep"; + } + else if (this.drawOffer == "threerep") this.drawOffer = ""; } - else if (this.drawOffer == "threerep") this.drawOffer = ""; if (!!this.game.mycolor && !data.receiveMyMove) { // NOTE: 'var' to see that variable outside this block var filtered_move = getFilteredMove(move); diff --git a/server/db/populate.sql b/server/db/populate.sql index 56483794..9b7842be 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -80,6 +80,7 @@ insert or ignore into Variants (name, description) values ('Hoppelpoppel', 'Knibis and Bisknis'), ('Horde', 'A pawns cloud'), ('Hypnotic', 'Mind control (v1)'), + ('Iceage', 'Ice Age is coming!'), ('Interweave', 'Interweaved colorbound teams'), ('Isardam', 'No paralyzed pieces'), ('Janggi', 'Korean Chess'), -- 2.44.0