From: Benjamin Auder Date: Tue, 25 Feb 2020 23:24:14 +0000 (+0100) Subject: Draft 2 new variants. Still 4 to add in this series X-Git-Url: https://git.auder.net/variants/current/doc/css/doc/html/packages.html?a=commitdiff_plain;h=c3a86f018aba40e3926e3672c7cea87acc6d1e25;p=vchess.git Draft 2 new variants. Still 4 to add in this series --- diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 56b5bab3..3c7cd7e6 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -126,6 +126,7 @@ export const translations = { "Your message": "Your message", // Variants boxes: + "Ancient rules": "Ancient rules", "Attract opposite king": "Attract opposite king", "Balanced sliders & leapers": "Balanced sliders & leapers", "Big board": "Big board", @@ -136,15 +137,17 @@ export const translations = { "Captures reborn": "Captures reborn", "Change colors": "Change colors", "Dangerous collisions": "Dangerous collisions", - "Exchange pieces positions": "Exchange pieces positions", "Exotic captures": "Exotic captures", "Explosive captures": "Explosive captures", "In the shadow": "In the shadow", + "Give three checks": "Give three checks", "Keep antiking in check": "Keep antiking in check", "King crosses the board": "King crosses the board", "Laws of attraction": "Laws of attraction", "Lose all pieces": "Lose all pieces", "Mate any piece": "Mate any piece", + "Middle battle": "Middle battle", + "Move like a knight": "Move like a knight", "Move twice": "Move twice", "Neverending rows": "Neverending rows", "Pawns move diagonally": "Pawns move diagonally", @@ -152,6 +155,8 @@ export const translations = { "Reverse captures": "Reverse captures", "Run forward": "Run forward", "Shared pieces": "Shared pieces", + "Shoot pieces": "Shoot pieces", + "Squares disappear": "Squares disappear", "Standard rules": "Standard rules", "Unidentified pieces": "Unidentified pieces" }; diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 48eb21ec..1024f787 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -126,6 +126,7 @@ export const translations = { "Your message": "Tu mensaje", // Variants boxes: + "Ancient rules": "Viejas reglas", "Attract opposite king": "Atraer al rey contrario", "Balanced sliders & leapers": "Modos de desplazamiento equilibrados", "Big board": "Gran tablero", @@ -136,15 +137,17 @@ export const translations = { "Captures reborn": "Las capturas renacen", "Change colors": "Cambiar colores", "Dangerous collisions": "Colisiones peligrosas", - "Exchange pieces positions": "Intercambiar las posiciones de las piezas", "Exotic captures": "Capturas exóticas", "Explosive captures": "Capturas explosivas", "In the shadow": "En la sombra", + "Give three checks": "Dar tres jaques", "Keep antiking in check": "Mantener el antirey en jaque", "King crosses the board": "El rey cruza el tablero", "Laws of attraction": "Las leyes de las atracciones", "Lose all pieces": "Perder todas las piezas", "Mate any piece": "Matar cualquier pieza", + "Middle battle": "Batalla media", + "Move like a knight": "Moverse como un caballo", "Move twice": "Mover dos veces", "Neverending rows": "Filas interminables", "Pawns move diagonally": "Peones se mueven en diagonal", @@ -152,6 +155,8 @@ export const translations = { "Reverse captures": "Capturas invertidas", "Run forward": "Correr hacia adelante", "Shared pieces": "Piezas compartidas", + "Shoot pieces": "Tirar de las piezas", + "Squares disappear": "Las casillas desaparecen", "Standard rules": "Reglas estandar", "Unidentified pieces": "Piezas no identificadas" }; diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 1e73e1e4..592e9a06 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -126,6 +126,7 @@ export const translations = { "Your message": "Votre message", // Variants boxes: + "Ancient rules": "Règles anciennes", "Attract opposite king": "Attirer le roi adverse", "Balanced sliders & leapers": "Modes de déplacement équilibrés", "Big board": "Grand échiquier", @@ -136,15 +137,17 @@ export const translations = { "Captures reborn": "Les captures renaissent", "Change colors": "Changer les couleurs", "Dangerous collisions": "Collisions dangeureuses", - "Exchange pieces positions": "Échangez les positions des pièces", "Exotic captures": "Captures exotiques", "Explosive captures": "Captures explosives", "In the shadow": "Dans l'ombre", + "Give three checks": "Donnez trois échecs", "Keep antiking in check": "Gardez l'antiroi en échec", "King crosses the board": "Le roi traverse l'échiquier", "Laws of attraction": "Les lois de l'attraction", "Lose all pieces": "Perdez toutes les pièces", "Mate any piece": "Mater n'importe quelle pièce", + "Middle battle": "Bataille du milieu", + "Move like a knight": "Bougez comme un cavalier", "Move twice": "Jouer deux coups", "Neverending rows": "Rangées sans fin", "Pawns move diagonally": "Les pions vont en diagonale", @@ -152,6 +155,8 @@ export const translations = { "Reverse captures": "Captures inversées", "Run forward": "Courir vers l'avant", "Shared pieces": "Pièces partagées", + "Shoot pieces": "Tirez sur les pièces", + "Squares disappear": "Les cases disparaissent", "Standard rules": "Règles usuelles", "Unidentified pieces": "Pièces non identifiées" }; diff --git a/client/src/translations/rules/Arena/en.pug b/client/src/translations/rules/Arena/en.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Arena/en.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Arena/es.pug b/client/src/translations/rules/Arena/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Arena/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Arena/fr.pug b/client/src/translations/rules/Arena/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Arena/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Chaturanga/en.pug b/client/src/translations/rules/Chaturanga/en.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Chaturanga/en.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Chaturanga/es.pug b/client/src/translations/rules/Chaturanga/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Chaturanga/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Chaturanga/fr.pug b/client/src/translations/rules/Chaturanga/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Chaturanga/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Check3/en.pug b/client/src/translations/rules/Check3/en.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Check3/en.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Check3/es.pug b/client/src/translations/rules/Check3/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Check3/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Check3/fr.pug b/client/src/translations/rules/Check3/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Check3/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Knightrelay/en.pug b/client/src/translations/rules/Knightrelay/en.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Knightrelay/en.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Knightrelay/es.pug b/client/src/translations/rules/Knightrelay/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Knightrelay/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Knightrelay/fr.pug b/client/src/translations/rules/Knightrelay/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Knightrelay/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Rifle/en.pug b/client/src/translations/rules/Rifle/en.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Rifle/en.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Rifle/es.pug b/client/src/translations/rules/Rifle/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Rifle/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Rifle/fr.pug b/client/src/translations/rules/Rifle/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Rifle/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Wormhole/en.pug b/client/src/translations/rules/Wormhole/en.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Wormhole/en.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Wormhole/es.pug b/client/src/translations/rules/Wormhole/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Wormhole/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Wormhole/fr.pug b/client/src/translations/rules/Wormhole/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Wormhole/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/variants/Arena.js b/client/src/variants/Arena.js new file mode 100644 index 00000000..b92be1ee --- /dev/null +++ b/client/src/variants/Arena.js @@ -0,0 +1,152 @@ +import { ChessRules } from "@/base_rules"; + +export const VariantRules = class ArenaRules extends ChessRules { + static get hasFlags() { + return false; + } + + static GenRandInitFen() { + return ChessRules.GenRandInitFen().replace("w 1111 -", "w -"); + } + + static InArena(x) { + return Math.abs(3.5 - x) <= 1.5; + } + + getPotentialMovesFrom([x, y]) { + const moves = super.getPotentialMovesFrom([x, y]); + // Eliminate moves which neither enter the arena or capture something + return moves.filter(m => { + const startInArena = V.InArena(m.start.x); + const endInArena = V.InArena(m.end.x); + return ( + (startInArena && endInArena && m.vanish.length == 2) || + (!startInArena && endInArena) + ); + }); + + return moves; + } + + getPotentialPawnMoves([x, y]) { + const color = this.turn; + let moves = []; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeX - 2 : 1; + + if (this.board[x + shiftX][y] == V.EMPTY) { + // One square forward + moves.push(this.getBasicMove([x, y], [x + shiftX, y])); + // Next condition because pawns on 1st rank can generally jump + if ( + x == startRank && + this.board[x + 2 * shiftX][y] == V.EMPTY + ) { + // Two squares jump + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); + } + } + // Captures + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY])); + } + } + + // En passant + const Lep = this.epSquares.length; + const epSquare = this.epSquares[Lep - 1]; //always at least one element + if ( + !!epSquare && + epSquare.x == x + shiftX && + Math.abs(epSquare.y - y) == 1 + ) { + let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); + enpassantMove.vanish.push({ + x: x, + y: epSquare.y, + p: "p", + c: this.getColor(x, epSquare.y) + }); + moves.push(enpassantMove); + } + + return moves; + } + + getPotentialQueenMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ).filter(m => { + // Filter out moves longer than 3 squares + return Math.max( + Math.abs(m.end.x - m.start.x), + Math.abs(m.end.y - m.start.y)) <= 3; + }); + } + + getPotentialKingMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ).filter(m => { + // Filter out moves longer than 3 squares + return Math.max( + Math.abs(m.end.x - m.start.x), + Math.abs(m.end.y - m.start.y)) <= 3; + }); + } + + getCheckSquares() { + return []; + } + + filterValid(moves) { + // No check conditions + return moves; + } + + getCurrentScore() { + const color = this.turn; + if (!this.atLeastOneMove()) + // I cannot move anymore + return color == "w" ? "0-1" : "1-0"; + // Win if the opponent has no more pieces left (in the Arena), + // (and/)or if he lost both his dukes. + let someUnitRemain = false; + let atLeastOneDuke = false; + let somethingInArena = false; + outerLoop: for (let i=0; i= 2 && V.InArena(i)) { + somethingInArena = true; + if (atLeastOneDuke) + break outerLoop; + } + if ([V.QUEEN,V.KING].includes(this.getPiece(i,j))) { + atLeastOneDuke = true; + if (this.movesCount < 2 || somethingInArena) + break outerLoop; + } + } + } + } + if ( + !someUnitRemain || + !atLeastOneDuke || + (this.movesCount >= 2 && !somethingInArena) + ) { + return color == "w" ? "0-1" : "1-0"; + } + return "*"; + } +}; diff --git a/client/src/variants/Chaturanga.js b/client/src/variants/Chaturanga.js new file mode 100644 index 00000000..a1b62284 --- /dev/null +++ b/client/src/variants/Chaturanga.js @@ -0,0 +1,121 @@ +import { ChessRules } from "@/base_rules"; + +export const VariantRules = class ChaturangaRules extends ChessRules { + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get ElephantSteps() { + return [ + [-2, -2], + [-2, 2], + [2, -2], + [2, 2] + ]; + } + + static GenRandInitFen() { + return ChessRules.GenRandInitFen().replace("w 1111 -", "w"); + } + + getPotentialPawnMoves([x, y]) { + const color = this.turn; + let moves = []; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + // Promotion in minister (queen) only: + const finalPiece = x + shiftX == lastRank ? V.QUEEN : V.PAWN; + + if (this.board[x + shiftX][y] == V.EMPTY) { + // One square forward + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { + c: color, + p: finalPiece + }) + ); + } + // Captures + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: color, + p: finalPiece + }) + ); + } + } + + return moves; + } + + getPotentialBishopMoves(sq) { + let moves = this.getSlideNJumpMoves(sq, V.ElephantSteps, "oneStep"); + // Complete with "repositioning moves": like a queen, without capture + let repositioningMoves = this.getSlideNJumpMoves( + sq, + V.steps[V.BISHOP], + "oneStep" + ).filter(m => m.vanish.length == 1); + return moves.concat(repositioningMoves); + } + + getPotentialQueenMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.BISHOP], + "oneStep" + ); + } + + getPotentialKingMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); + } + + isAttackedByBishop(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.BISHOP, + V.ElephantSteps, + "oneStep" + ); + } + + isAttackedByQueen(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.QUEEN, + V.steps[V.BISHOP], + "oneStep" + ); + } + + static get VALUES() { + return { + p: 1, + r: 5, + n: 3, + b: 2.5, + q: 2, + k: 1000 + }; + } +}; diff --git a/client/src/variants/Royalrace.js b/client/src/variants/Royalrace.js index e3ed224b..6f64d24f 100644 --- a/client/src/variants/Royalrace.js +++ b/client/src/variants/Royalrace.js @@ -178,7 +178,10 @@ export const VariantRules = class RoyalraceRules extends ChessRules { if (this.kingPos[color][0] == 0) // The opposing edge is reached! return color == "w" ? "1-0" : "0-1"; - return "*"; + if (this.atLeastOneMove()) + return "*"; + // Stalemate (will probably never happen) + return "1/2"; } static get SEARCH_DEPTH() { diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js new file mode 100644 index 00000000..964c5e40 --- /dev/null +++ b/client/src/variants/Wormhole.js @@ -0,0 +1,333 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +// TODO: + +export const VariantRules = class HiddenRules extends ChessRules { + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + // Analyse in Hidden mode makes no sense + static get CanAnalyze() { + return false; + } + + // Moves are revealed only when game ends + static get ShowMoves() { + return "none"; + } + + static get HIDDEN_DECODE() { + return { + s: "p", + t: "q", + u: "r", + c: "b", + o: "n", + l: "k" + }; + } + static get HIDDEN_CODE() { + return { + p: "s", + q: "t", + r: "u", + b: "c", + n: "o", + k: "l" + }; + } + + // Turn a hidden piece or revealed piece into revealed piece: + static Decode(p) { + if (Object.keys(V.HIDDEN_DECODE).includes(p)) + return V.HIDDEN_DECODE[p]; + return p; + } + + static get PIECES() { + return ChessRules.PIECES.concat(Object.values(V.HIDDEN_CODE)); + } + + // Pieces can be hidden :) + getPiece(i, j) { + const piece = this.board[i][j].charAt(1); + if (Object.keys(V.HIDDEN_DECODE).includes(piece)) + return V.HIDDEN_DECODE[piece]; + return piece; + } + + // Scan board for kings positions (no castling) + scanKingsRooks(fen) { + this.kingPos = { w: [-1, -1], b: [-1, -1] }; + const fenRows = V.ParseFen(fen).position.split("/"); + for (let i = 0; i < fenRows.length; i++) { + let k = 0; //column index on board + for (let j = 0; j < fenRows[i].length; j++) { + switch (fenRows[i].charAt(j)) { + case "k": + case "l": + this.kingPos["b"] = [i, k]; + break; + case "K": + case "L": + this.kingPos["w"] = [i, k]; + break; + default: { + const num = parseInt(fenRows[i].charAt(j)); + if (!isNaN(num)) k += num - 1; + } + } + k++; + } + } + } + + getPpath(b, color, score) { + if (Object.keys(V.HIDDEN_DECODE).includes(b[1])) { + // Supposed to be hidden. + if (score == "*" && (!color || color != b[0])) + return "Hidden/" + b[0] + "p"; + // Else: condition OK to show the piece + return b[0] + V.HIDDEN_DECODE[b[1]]; + } + // The piece is already not supposed to be hidden: + return b; + } + + getBasicMove([sx, sy], [ex, ey], tr) { + if ( + tr && + Object.keys(V.HIDDEN_DECODE).includes(this.board[sx][sy].charAt(1)) + ) { + // The transformed piece is a priori hidden + tr.p = V.HIDDEN_CODE[tr.p]; + } + let mv = new Move({ + appear: [ + new PiPo({ + x: ex, + y: ey, + c: tr ? tr.c : this.getColor(sx, sy), + p: tr ? tr.p : this.board[sx][sy].charAt(1) + }) + ], + vanish: [ + new PiPo({ + x: sx, + y: sy, + c: this.getColor(sx, sy), + p: this.board[sx][sy].charAt(1) + }) + ] + }); + + // The opponent piece disappears if we take it + if (this.board[ex][ey] != V.EMPTY) { + mv.vanish.push( + new PiPo({ + x: ex, + y: ey, + c: this.getColor(ex, ey), + p: this.board[ex][ey].charAt(1) + }) + ); + // Pieces are revealed when they capture + mv.appear[0].p = V.Decode(mv.appear[0].p); + } + + return mv; + } + + // What are the king moves from square x,y ? + getPotentialKingMoves(sq) { + // No castling: + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); + } + + filterValid(moves) { + return moves; + } + + static GenRandInitFen() { + let pieces = { w: new Array(8), b: new Array(8) }; + // Shuffle pieces + pawns on two first ranks + for (let c of ["w", "b"]) { + let positions = ArrayFun.range(16); + + // Get random squares for bishops + let randIndex = 2 * randInt(8); + const bishop1Pos = positions[randIndex]; + // The second bishop must be on a square of different color + let randIndex_tmp = 2 * randInt(8) + 1; + const bishop2Pos = positions[randIndex_tmp]; + // Remove chosen squares + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); + + // Get random squares for knights + randIndex = randInt(14); + const knight1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(13); + const knight2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random squares for rooks + randIndex = randInt(12); + const rook1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(11); + const rook2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random square for queen + randIndex = randInt(10); + const queenPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random square for king + randIndex = randInt(9); + const kingPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Pawns position are all remaining slots: + for (let p of positions) + pieces[c][p] = "s"; + + // Finally put the shuffled pieces in the board array + pieces[c][rook1Pos] = "u"; + pieces[c][knight1Pos] = "o"; + pieces[c][bishop1Pos] = "c"; + pieces[c][queenPos] = "t"; + pieces[c][kingPos] = "l"; + pieces[c][bishop2Pos] = "c"; + pieces[c][knight2Pos] = "o"; + pieces[c][rook2Pos] = "u"; + } + let upFen = pieces["b"].join(""); + upFen = upFen.substr(0,8) + "/" + upFen.substr(8).split("").reverse().join(""); + let downFen = pieces["b"].join("").toUpperCase(); + downFen = downFen.substr(0,8) + "/" + downFen.substr(8).split("").reverse().join(""); + return upFen + "/8/8/8/8/" + downFen + " w 0"; + } + + getCheckSquares() { + return []; + } + + updateVariables(move) { + super.updateVariables(move); + if ( + move.vanish.length >= 2 && + [V.KING,V.HIDDEN_CODE[V.KING]].includes(move.vanish[1].p) + ) { + // We took opponent king + this.kingPos[this.turn] = [-1, -1]; + } + } + + unupdateVariables(move) { + super.unupdateVariables(move); + const c = move.vanish[0].c; + const oppCol = V.GetOppCol(c); + if (this.kingPos[oppCol][0] < 0) + // Last move took opponent's king: + this.kingPos[oppCol] = [move.vanish[1].x, move.vanish[1].y]; + } + + getCurrentScore() { + const color = this.turn; + const kp = this.kingPos[color]; + if (kp[0] < 0) + // King disappeared + return color == "w" ? "0-1" : "1-0"; + // Assume that stalemate is impossible: + return "*"; + } + + getComputerMove() { + const color = this.turn; + let moves = this.getAllValidMoves(); + for (let move of moves) { + move.eval = 0; //a priori... + + // Can I take something ? If yes, do it with some probability + if (move.vanish.length == 2 && move.vanish[1].c != color) { + // OK this isn't a castling move + const myPieceVal = V.VALUES[move.appear[0].p]; + const hisPieceVal = Object.keys(V.HIDDEN_DECODE).includes(move.vanish[1].p) + ? undefined + : V.VALUES[move.vanish[1].p]; + if (!hisPieceVal) { + // Opponent's piece is unknown: do not take too much risk + move.eval = -myPieceVal + 1.5; //so that pawns always take + } + // Favor captures + else if (myPieceVal <= hisPieceVal) + move.eval = hisPieceVal - myPieceVal + 1; + else { + // Taking a pawn with minor piece, + // or minor piece or pawn with a rook, + // or anything but a queen with a queen, + // or anything with a king. + move.eval = hisPieceVal - myPieceVal; + } + } else { + // If no capture, favor small step moves, + // but sometimes move the knight anyway + const penalty = V.Decode(move.vanish[0].p) != V.KNIGHT + ? Math.abs(move.end.x - move.start.x) + Math.abs(move.end.y - move.start.y) + : (Math.random() < 0.5 ? 3 : 1); + move.eval -= penalty / (V.size.x + V.size.y - 1); + } + + // TODO: also favor movements toward the center? + } + + moves.sort((a, b) => b.eval - a.eval); + let candidates = [0]; + for (let j = 1; j < moves.length && moves[j].eval == moves[0].eval; j++) + candidates.push(j); + return moves[candidates[randInt(candidates.length)]]; + } + + getNotation(move) { + // Translate final square + const finalSquare = V.CoordsToSquare(move.end); + + const piece = this.getPiece(move.start.x, move.start.y); + if (piece == V.PAWN) { + // Pawn move + let notation = ""; + if (move.vanish.length > move.appear.length) { + // Capture + const startColumn = V.CoordToColumn(move.start.y); + notation = startColumn + "x" + finalSquare; + } + else notation = finalSquare; + if (move.appear.length > 0 && !["p","s"].includes(move.appear[0].p)) { + // Promotion + const appearPiece = V.Decode(move.appear[0].p); + notation += "=" + appearPiece.toUpperCase(); + } + return notation; + } + // Piece movement + return ( + piece.toUpperCase() + + (move.vanish.length > move.appear.length ? "x" : "") + + finalSquare + ); + } +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 84a34d5d..7043996f 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -5,11 +5,14 @@ insert or ignore into Variants (name,description) values ('Allmate', 'Mate any piece'), ('Antiking', 'Keep antiking in check'), ('Antimatter', 'Dangerous collisions'), + ('Arena', 'Middle battle'), ('Atomic', 'Explosive captures'), ('Baroque', 'Exotic captures'), ('Benedict', 'Change colors'), ('Berolina', 'Pawns move diagonally'), + ('Chaturanga', 'Ancient rules'), ('Checkered', 'Shared pieces'), + ('Check3', 'Give three checks'), ('Chess960', 'Standard rules'), ('Circular', 'Run forward'), ('Crazyhouse', 'Captures reborn'), @@ -19,12 +22,15 @@ insert or ignore into Variants (name,description) values ('Extinction', 'Capture all of a kind'), ('Grand', 'Big board'), ('Hidden', 'Unidentified pieces'), + ('Knightrelay', 'Move like a knight'), ('Losers', 'Lose all pieces'), ('Magnetic', 'Laws of attraction'), ('Marseille', 'Move twice'), + ('Rifle', 'Shoot pieces'), ('Royalrace', 'King crosses the board'), ('Recycle', 'Reuse pieces'), ('Suction', 'Attract opposite king'), ('Upsidedown', 'Board upside down'), ('Wildebeest', 'Balanced sliders & leapers'), + ('Wormhole', 'Squares disappear'), ('Zen', 'Reverse captures');