From: Benjamin Auder Date: Thu, 14 Jan 2021 00:50:33 +0000 (+0100) Subject: Add Rollerball variant X-Git-Url: https://git.auder.net/game/doc/html/current/mini-custom.min.css?a=commitdiff_plain;h=87f40859ac7fea9f468d6be168df99f501a01198;p=vchess.git Add Rollerball variant --- diff --git a/.gitattributes b/.gitattributes index ed553070..ac66ad09 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ *.ico filter=fat *.pdf filter=fat *.png filter=fat +*.gif filter=fat *.flac filter=fat diff --git a/client/public/variants/Rollerball/rollerball_directions.gif b/client/public/variants/Rollerball/rollerball_directions.gif new file mode 100644 index 00000000..dff78d67 --- /dev/null +++ b/client/public/variants/Rollerball/rollerball_directions.gif @@ -0,0 +1 @@ +#$# git-fat 0086b7f63ad2afec7e27c717ac39ae2c14b707bd 6736 diff --git a/client/public/variants/Rollerball/rook_example.gif b/client/public/variants/Rollerball/rook_example.gif new file mode 100644 index 00000000..4a219aa8 --- /dev/null +++ b/client/public/variants/Rollerball/rook_example.gif @@ -0,0 +1 @@ +#$# git-fat 0971d21d6aa9f36da7f3442ef28b0b9596939588 5546 diff --git a/client/src/translations/en.js b/client/src/translations/en.js index ef2ba14e..6d79e55a 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -171,6 +171,7 @@ export const translations = { "Absorb powers": "Absorb powers", "All of the same color": "All of the same color", "Ancient rules": "Ancient rules", + "As in the movie": "As in the movie", "Attract opposite king": "Attract opposite king", "Augmented Queens": "Augmented Queens", "Balanced sliders & leapers": "Balanced sliders & leapers", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 6aa5c10c..a50a9791 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -171,6 +171,7 @@ export const translations = { "Absorb powers": "Absorber poderes", "All of the same color": "Todo el mismo color", "Ancient rules": "Viejas reglas", + "As in the movie": "Como en la pelicula", "Attract opposite king": "Atraer al rey contrario", "Augmented Queens": "Damas aumentadas", "Balanced sliders & leapers": "Modos de desplazamiento equilibrados", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index a79d18cb..925f1412 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -171,6 +171,7 @@ export const translations = { "Absorb powers": "Absorber les pouvoirs", "All of the same color": "Tout de la même couleur", "Ancient rules": "Règles anciennes", + "As in the movie": "Comme dans le film", "Attract opposite king": "Attirer le roi adverse", "Augmented Queens": "Dames augmentées", "Balanced sliders & leapers": "Modes de déplacement équilibrés", diff --git a/client/src/translations/rules/Rollerball/en.pug b/client/src/translations/rules/Rollerball/en.pug index 21203baa..686cc7a6 100644 --- a/client/src/translations/rules/Rollerball/en.pug +++ b/client/src/translations/rules/Rollerball/en.pug @@ -1 +1,47 @@ -p.boxed TODO +p.boxed + | Pieces turn around the board, they cannot go in the middle. + +p. + Each side has only a king, two rooks, one bishop and two pawns. + Pieces can never enter the central area. + Pawns can only move "clockwise", acording to the image below: they move + and capture forward straight and diagonally in the arrow direction. + +figure + img.img-center(src="/variants/Rollerball/rollerball_directions.gif") + figcaption.text-center. + Main directions on the board. + The squares where direction changes are enlighted with white arrows. + +p. + When a pawn reaches the starting square of an enemy pawn, it promotes + into a bishop or rook. + +p. + Rooks and bishop move as usual, but are restricted to one square only when + moving counter-clockwise. + Moreover, they are allowed one rebound in certain circumstances: +ul + li a bishop rebounds on the first wall met, + li a rook rebounds (at 90 degrees) if it reaches a corner. + +figure + img.img-center(src="/variants/Rollerball/rook_example.gif") + figcaption.text-center Some rook movements. + +p. + The king moves as in orthodox chess. + The goal is either to bring your king on the initial square of the + opponent's king, or to checkmate him. + +h3 More information + +p + | See the + a(href="http://history.chess.free.fr/rollerball.htm") author's presentation + | , and the + a(href="https://www.chessvariants.com/40.dir/rollerball/index.html") + | chessvariants page + | . + +p Inventor: Jean-Louis Cazaux (1998) diff --git a/client/src/translations/rules/Rollerball/es.pug b/client/src/translations/rules/Rollerball/es.pug index 21203baa..9a8230a6 100644 --- a/client/src/translations/rules/Rollerball/es.pug +++ b/client/src/translations/rules/Rollerball/es.pug @@ -1 +1,49 @@ -p.boxed TODO +p.boxed + | Las piezas giran alrededor del tablero, no pueden ir al centro. + +p. + Cada lado tiene un rey, dos torres, un alfil y dos peones. + Las piezas nunca pueden ir al área central. + Los peones solo se mueven siguiendo las "agujas del reloj", según la imagen + abajo: se mueven y capturan en línea recta o en diagonal + en la dirección de la flecha. + +figure + img.img-center(src="/variants/Rollerball/rollerball_directions.gif") + figcaption.text-center. + Direcciones principales en el tablero. Las casillas donde ocurre + los cambios de dirección están marcados con flechas blancas. + +p. + Cuando un peón llega a la casilla inicial de un peón enemigo, es promovido + en un alfil o en una torre. + +p. + Las torres y los alfiles se mueven como de costumbre, pero están + restringidos por una casilla solo cuando se mueven en sentido antihorario. + Además, pueden rebotar una vez en determinadas circunstancias: +ul + li un loco rebota en la primera pared que encuentra, + li una torre rebota (90 grados) si golpea una esquina. + +figure + img.img-center(src="/variants/Rollerball/rook_example.gif") + figcaption.text-center Algunos movimientos de la torre. + +p. + El rey se mueve como en el ajedrez ortodoxo. + El objetivo es llevar a tu rey a la casilla de inicio del rey contrario, + o matarlo. + +h3 Más información + +p + | Ver la + a(href="http://history.chess.free.fr/rollerball.htm") + | presentación del autor + | , y la + a(href="https://www.chessvariants.com/40.dir/rollerball/index.html") + | página chessvariants + | . + +p Inventor: Jean-Louis Cazaux (1998) diff --git a/client/src/translations/rules/Rollerball/fr.pug b/client/src/translations/rules/Rollerball/fr.pug index 21203baa..d3080c3e 100644 --- a/client/src/translations/rules/Rollerball/fr.pug +++ b/client/src/translations/rules/Rollerball/fr.pug @@ -1 +1,50 @@ -p.boxed TODO +p.boxed + | Les pièces tournent autour de l'échiquier, + | elles ne peuvent pas aller au centre. + +p. + Chaque camp dispose d'un roi, deux tours, un fou et deux pions. + Les pièces ne peuvent jamais aller dans la zone centrale. + Les pions ne se déplacent que dans le "sens horaire", selon l'image + ci-dessous : ils se déplacent et capturent tout droit ou en diagonale + dans la direction de la flèche. + +figure + img.img-center(src="/variants/Rollerball/rollerball_directions.gif") + figcaption.text-center. + Principales directions sur l'échiquier. Les cases où survient + un changement de direction sont marquées par des flèches blanches. + +p. + Quand un pion atteint la case de départ d'un pion ennemi, il est promu + en un fou ou une tour. + +p. + Les tours et fous se déplacent comme d'habitude, mais sont restreints d'une + case seulement quand ils se déplacent dans le sens anti-horaire. + De plus, ils peuvent rebondir une fois dans certaines circonstances : +ul + li un fou rebondit sur le premier mur rencontré, + li une tour rebondit (à 90 degrés) si elle atteint un coin. + +figure + img.img-center(src="/variants/Rollerball/rook_example.gif") + figcaption.text-center Quelques déplacements de la tour. + +p. + Le roi se déplace comme aux échecs orthodoxes. + L'objectif est soit d'amener votre roi sur la case de départ du roi adverse, + ou bien de le mater. + +h3 Plus d'information + +p + | Voir la + a(href="http://history.chess.free.fr/rollerball.htm") + | présentation de l'auteur + | , et la + a(href="https://www.chessvariants.com/40.dir/rollerball/index.html") + | page chessvariants + | . + +p Inventeur : Jean-Louis Cazaux (1998) diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index ee21f973..66c14938 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -448,6 +448,7 @@ p. "Kingsmaker", "Magnetic", "Relayup", + "Rollerball", "Takenmake", "Wormhole" ] diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 38743601..39eda808 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -458,6 +458,7 @@ p. "Kingsmaker", "Magnetic", "Relayup", + "Rollerball", "Takenmake", "Wormhole" ] diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 07756b8b..9cf10ed9 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -456,6 +456,7 @@ p. "Kingsmaker", "Magnetic", "Relayup", + "Rollerball", "Takenmake", "Wormhole" ] diff --git a/client/src/variants/Omega.js b/client/src/variants/Omega.js index bac8d50d..a4276ed7 100644 --- a/client/src/variants/Omega.js +++ b/client/src/variants/Omega.js @@ -278,12 +278,9 @@ export class OmegaRules extends ChessRules { getPotentialMovesFrom([x, y]) { switch (this.getPiece(x, y)) { - case V.CHAMPION: - return this.getPotentialChampionMoves([x, y]); - case V.WIZARD: - return this.getPotentialWizardMoves([x, y]); - default: - return super.getPotentialMovesFrom([x, y]); + case V.CHAMPION: return this.getPotentialChampionMoves([x, y]); + case V.WIZARD: return this.getPotentialWizardMoves([x, y]); + default: return super.getPotentialMovesFrom([x, y]); } } diff --git a/client/src/variants/Rollerball.js b/client/src/variants/Rollerball.js new file mode 100644 index 00000000..36373b29 --- /dev/null +++ b/client/src/variants/Rollerball.js @@ -0,0 +1,570 @@ +import { ChessRules } from "@/base_rules"; + +export class RollerballRules extends ChessRules { + + static get HasEnpassant() { + return false; + } + + static get HasCastle() { + return false; + } + + static get DarkBottomRight() { + return true; + } + + static get PIECES() { + return [V.PAWN, V.KING, V.ROOK, V.BISHOP]; + } + + static get size() { + return { x: 7, y: 7 }; + } + + // TODO: the wall position should be checked too + 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 (['x'].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; + } + + // NOTE: canTake() is wrong, but next method is enough + static OnBoard(x, y) { + return ( + (x >= 0 && x <= 6 && y >= 0 && y <= 6) && + (![2, 3, 4].includes(x) || ![2, 3, 4].includes(y)) + ); + } + + static IsGoodFlags(flags) { + // 2 for kings: last zone reached + return !!flags.match(/^[0-7]{2,2}$/); + } + + setFlags(fenflags) { + this.kingFlags = { + w: parseInt(fenflags.charAt(0), 10), + b: parseInt(fenflags.charAt(1), 10) + }; + } + + aggregateFlags() { + return this.kingFlags; + } + + disaggregateFlags(flags) { + this.kingFlags = flags; + } + + getFlagsFen() { + return this.kingFlags['w'].toString() + this.kingFlags['b'].toString(); + } + + // For space in the middle: + static get NOTHING() { + return "xx"; + } + + static board2fen(b) { + if (b[0] == 'x') return 'x'; + return ChessRules.board2fen(b); + } + + static fen2board(f) { + if (f == 'x') return V.NOTHING; + return ChessRules.fen2board(f); + } + + getPpath(b) { + if (b[0] == 'x') return "Omega/nothing"; + return b; + } + + static GenRandInitFen() { + return "2rbp2/2rkp2/2xxx2/2xxx2/2xxx2/2PKR2/2PBR2 w 0 00"; + } + + getPotentialMovesFrom(sq) { + switch (this.getPiece(sq[0], sq[1])) { + case V.PAWN: return this.getPotentialPawnMoves(sq); + case V.ROOK: return this.getPotentialRookMoves(sq); + case V.BISHOP: return this.getPotentialBishopMoves(sq); + case V.KING: return super.getPotentialKingMoves(sq); + } + return []; + } + + getPotentialPawnMoves([x, y]) { + const c = this.turn; + // Need to know pawn area to deduce move options + const inMiddleX = [2, 3, 4].includes(x); + const inMiddleY = [2, 3, 4].includes(y); + // In rectangular areas on the sides? + if (inMiddleX) { + const forward = (y <= 1 ? -1 : 1); + return ( + super.getSlideNJumpMoves( + [x, y], [[forward, -1], [forward, 0], [forward, 1]], "oneStep") + ); + } + if (inMiddleY) { + const forward = (x <= 1 ? 1 : -1); + let moves = + super.getSlideNJumpMoves( + [x, y], [[-1, forward], [0, forward], [1, forward]], "oneStep"); + // Promotions may happen: + let extraMoves = []; + moves.forEach(m => { + if ( + (c == 'w' && x <= 1 && m.end.y == 4) || + (c == 'b' && x >= 5 && m.end.y == 2) + ) { + m.appear[0].p = V.ROOK; + let m2 = JSON.parse(JSON.stringify(m)); + m2.appear[0].p = V.BISHOP; + extraMoves.push(m2); + } + }); + Array.prototype.push.apply(moves, extraMoves); + return moves; + } + // In a corner: + const toRight = (x == 0 && [0, 1, 5].includes(y)) || (x == 1 && y == 1); + const toLeft = (x == 6 && [1, 5, 6].includes(y)) || (x == 5 && y == 5); + const toUp = (y == 0 && [1, 5, 6].includes(x)) || (x == 5 && y == 1); + const toBottom = (y == 6 && [0, 1, 5].includes(x)) || (x == 1 && y == 5); + if (toRight || toLeft) { + const forward = (toRight ? 1 : -1); + return ( + super.getSlideNJumpMoves( + [x, y], [[-1, forward], [0, forward], [1, forward]], "oneStep") + ); + } + const forward = (toUp ? -1 : 1); + return ( + super.getSlideNJumpMoves( + [x, y], [[forward, -1], [forward, 0], [forward, 1]], "oneStep") + ); + } + + getPotentialRookMoves([x, y]) { + let multiStep = [], + oneStep = []; + if (x <= 1) multiStep.push([0, 1]); + else oneStep.push([0, 1]); + if (y <= 1) multiStep.push([-1, 0]); + else oneStep.push([-1, 0]); + if (x >= 5) multiStep.push([0, -1]); + else oneStep.push([0, -1]); + if (y >= 5) multiStep.push([1, 0]); + else oneStep.push([1, 0]); + const c = this.turn; + let moves = super.getSlideNJumpMoves([x, y], oneStep, "oneStep"); + for (let step of multiStep) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j)) { + if (this.getColor(i, j) != c) + moves.push(this.getBasicMove([x, y], [i, j])); + } + else { + i -= step[0]; + j -= step[1]; + // Potential rebound if away from initial square + if (i != x || j != y) { + // Corners check + let nextStep = null; + if (i == 0 && j == 0) nextStep = [0, 1]; + else if (i == 0 && j == 6) nextStep = [1, 0]; + else if (i == 6 && j == 6) nextStep = [0, -1]; + else if (i == 6 && j == 0) nextStep = [-1, 0]; + if (!!nextStep) { + i += nextStep[0]; + j += nextStep[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + i += nextStep[0]; + j += nextStep[1]; + } + if (V.OnBoard(i, j) && this.getColor(i, j) != c) + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + } + } + return moves; + } + + static get DictBishopSteps() { + return { + "-1_-1": [-1, -1], + "-1_1": [-1, 1], + "1_-1": [1, -1], + "1_1": [1, 1] + }; + } + + getPotentialBishopMoves([x, y]) { + let multiStep = {}; + if (x <= 1) { + multiStep["-1_1"] = [-1, 1]; + multiStep["1_1"] = [1, 1]; + } + if (y <= 1) { + multiStep["-1_-1"] = [-1, -1]; + if (!multiStep["-1_1"]) multiStep["-1_1"] = [-1, 1]; + } + if (x >= 5) { + multiStep["1_-1"] = [1, -1]; + if (!multiStep["-1_-1"]) multiStep["-1_-1"] = [-1, -1]; + } + if (y >= 5) { + if (!multiStep["1_-1"]) multiStep["1_-1"] = [1, -1]; + if (!multiStep["1_1"]) multiStep["1_1"] = [1, 1]; + } + let oneStep = []; + Object.keys(V.DictBishopSteps).forEach(str => { + if (!multiStep[str]) oneStep.push(V.DictBishopSteps[str]); + }); + const c = this.turn; + let moves = super.getSlideNJumpMoves([x, y], oneStep, "oneStep"); + for (let step of Object.values(multiStep)) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j)) { + if (this.getColor(i, j) != c) + moves.push(this.getBasicMove([x, y], [i, j])); + } + else { + i -= step[0]; + j -= step[1]; + // Rebound, if we moved away from initial square + if (i != x || j != y) { + let nextStep = null; + if (step[0] == -1 && step[1] == -1) { + if (j == 0) nextStep = [-1, 1]; + else nextStep = [1, -1]; + } + else if (step[0] == -1 && step[1] == 1) { + if (i == 0) nextStep = [1, 1]; + else nextStep = [-1, -1]; + } + else if (step[0] == 1 && step[1] == -1) { + if (i == 6) nextStep = [-1, -1]; + else nextStep = [1, 1]; + } + else { + // step == [1, 1] + if (j == 6) nextStep = [1, -1]; + else nextStep = [-1, 1]; + } + i += nextStep[0]; + j += nextStep[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + i += nextStep[0]; + j += nextStep[1]; + } + if (V.OnBoard(i, j) && this.getColor(i, j) != c) + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + } + return moves; + } + + isAttacked(sq, color) { + return ( + super.isAttackedByKing(sq, color) || + this.isAttackedByRook(sq, color) || + this.isAttackedByBishop(sq, color) || + this.isAttackedByPawn(sq, color) + ); + } + + isAttackedByPawn([x, y], color) { + // Determine zone, shifted according to pawn movement + let attackDir = ""; + let forward = 0; + if ( + ([1, 2, 3, 4].includes(x) && y <= 1) || + (x == 5 && y == 0) + ) { + attackDir = "vertical"; + forward = 1; + } + else if ( + ([2, 3, 4, 5].includes(x) && [5, 6].includes(y)) || + (x == 1 && y == 6) + ) { + attackDir = "vertical"; + forward = -1; + } + else if ( + (x <= 1 && [2, 3, 4, 5].includes(y)) || + (x == 0 && y == 1) + ) { + attackDir = "horizontal"; + forward = -1; + } + else if ( + (x >= 5 && [1, 2, 3, 4].includes(y)) || + (x == 6 && y == 5) + ) { + attackDir = "horizontal"; + forward = 1; + } + if (forward != 0) { + const steps = + attackDir == "vertical" + ? [ [forward, -1], [forward, 0], [forward, 1] ] + : [ [-1, forward], [0, forward], [1, forward] ]; + return ( + super.isAttackedBySlideNJump([x, y], color, V.PAWN, steps, "oneStep") + ); + } + // In a corner: can be attacked by one square only + let step = null; + if (x == 0) { + if (y == 0) step = [1, 0]; + else step = [0, -1]; + } + else { + if (y == 0) step = [0, 1]; + else step = [-1, 0]; + } + return ( + super.isAttackedBySlideNJump([x, y], color, V.PAWN, [step], "oneStep") + ); + } + + isAttackedByRook([x, y], color) { + // "Reversing" the code of getPotentialRookMoves() + let multiStep = [], + oneStep = []; + if (x <= 1) multiStep.push([0, -1]); + else oneStep.push([0, -1]); + if (y <= 1) multiStep.push([1, 0]); + else oneStep.push([1, 0]); + if (x >= 5) multiStep.push([0, 1]); + else oneStep.push([0, 1]); + if (y >= 5) multiStep.push([-1, 0]); + else oneStep.push([-1, 0]); + if ( + super.isAttackedBySlideNJump([x, y], color, V.ROOK, oneStep, "oneStep") + ) { + return true; + } + for (let step of multiStep) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j)) { + if (this.getColor(i, j) == color && this.getPiece(i, j) == V.ROOK) + return true; + } + else { + i -= step[0]; + j -= step[1]; + if (i != x || j != y) { + let nextStep = null; + if (i == 0 && j == 0) nextStep = [1, 0]; + else if (i == 0 && j == 6) nextStep = [0, -1]; + else if (i == 6 && j == 6) nextStep = [-1, 0]; + else if (i == 6 && j == 0) nextStep = [0, 1]; + if (!!nextStep) { + i += nextStep[0]; + j += nextStep[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += nextStep[0]; + j += nextStep[1]; + } + if ( + V.OnBoard(i, j) && + this.getColor(i, j) == color && + this.getPiece(i, j) == V.ROOK + ) { + return true; + } + } + } + } + } + return false; + } + + isAttackedByBishop([x, y], color) { + // "Reversing" the code of getPotentiaBishopMoves() + let multiStep = {}; + if (x <= 1) { + multiStep["1_-1"] = [1, -1]; + multiStep["-1_-1"] = [-1, -1]; + } + if (y <= 1) { + multiStep["1_1"] = [1, 1]; + if (!multiStep["1_-1"]) multiStep["1_-1"] = [1, -1]; + } + if (x >= 5) { + multiStep["-1_1"] = [-1, 1]; + if (!multiStep["1_1"]) multiStep["1_1"] = [1, 1]; + } + if (y >= 5) { + if (!multiStep["-1_-1"]) multiStep["-1_-1"] = [-1, -1]; + if (!multiStep["-1_1"]) multiStep["-1_1"] = [-1, 1]; + } + let oneStep = []; + Object.keys(V.DictBishopSteps).forEach(str => { + if (!multiStep[str]) oneStep.push(V.DictBishopSteps[str]); + }); + if ( + super.isAttackedBySlideNJump([x, y], color, V.BISHOP, oneStep, "oneStep") + ) { + return true; + } + for (let step of Object.values(multiStep)) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j)) { + if (this.getColor(i, j) == color && this.getPiece(i, j) == V.BISHOP) + return true; + } + else { + i -= step[0]; + j -= step[1]; + if (i != x || j != y) { + let nextStep = null; + if (step[0] == -1 && step[1] == -1) { + if (j == 0) nextStep = [-1, 1]; + else nextStep = [1, -1]; + } + else if (step[0] == -1 && step[1] == 1) { + if (i == 0) nextStep = [1, 1]; + else nextStep = [-1, -1]; + } + else if (step[0] == 1 && step[1] == -1) { + if (i == 6) nextStep = [-1, -1]; + else nextStep = [1, 1]; + } + else { + // step == [1, 1] + if (j == 6) nextStep = [1, -1]; + else nextStep = [-1, 1]; + } + i += nextStep[0]; + j += nextStep[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += nextStep[0]; + j += nextStep[1]; + } + if ( + V.OnBoard(i, j) && + this.getColor(i, j) == color && + this.getPiece(i, j) == V.BISHOP + ) { + return true; + } + } + } + } + return false; + } + + // The board is divided in areas determined by "distance to target" + // A zone n+1 must be reached from a zone n. + getKingZone([x, y], color) { + if (color == 'w') { + if (y >= 4) return -1; //"out of zone" + if (y == 3 && [5, 6].includes(x)) return 0; + if (x == 6) return 1; + if (x == 5) return 2; + if (x == 4) return 3; + if (x == 3 || y == 0) return 4; + if (y == 1) return 5; + if (x == 0 || y == 2) return 6; + return 7; //x == 1 && y == 3 + } + // color == 'b': + if (y <= 2) return -1; //"out of zone" + if (y == 3 && [0, 1].includes(x)) return 0; + if (x == 0) return 1; + if (x == 1) return 2; + if (x == 2) return 3; + if (x == 3 || y == 6) return 4; + if (y == 5) return 5; + if (x == 6 || y == 4) return 6; + return 7; //x == 5 && y == 3 + } + + postPlay(move) { + super.postPlay(move); + if (move.vanish[0].p == V.KING) { + const c = move.vanish[0].c; + const z1 = this.getKingZone([move.vanish[0].x, move.vanish[0].y], c), + z2 = this.getKingZone([move.appear[0].x, move.appear[0].y], c); + if ( + z1 >= 0 && z2 >= 0 && z1 < z2 && + // There exist "zone jumps" (0 to 2 for example), + // so the following test "flag >= z1" is required. + this.kingFlags[c] >= z1 && this.kingFlags[c] < z2 + ) { + this.kingFlags[c] = z2; + } + } + } + + getCurrentScore() { + const oppCol = V.GetOppCol(this.turn); + if (this.kingFlags[oppCol] == 7) return (oppCol == 'w' ? "1-0" : "0-1"); + return super.getCurrentScore(); + } + + static get SEARCH_DEPTH() { + return 4; + } + + evalPosition() { + let evaluation = 0; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] != V.EMPTY) { + const sign = this.getColor(i, j) == "w" ? 1 : -1; + const piece = this.getPiece(i, j); + if (piece != 'x') evaluation += sign * V.VALUES[piece]; + } + } + } + // Taking flags into account in a rather naive way + return evaluation + this.kingFlags['w'] - this.kingFlags['b']; + } + +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index dd36fc9c..13f2e8a7 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -119,6 +119,7 @@ insert or ignore into Variants (name, description) values ('Relayup', 'Upgrade pieces'), ('Rifle', 'Shoot pieces'), ('Recycle', 'Reuse pieces'), + ('Rollerball', 'As in the movie'), ('Rococo', 'Capture on the edge'), ('Rookpawns', 'Rook versus pawns'), ('Royalrace', 'Kings cross the 11x11 board'),