From: Benjamin Auder Date: Tue, 25 Feb 2020 10:59:15 +0000 (+0100) Subject: Draft Royalrace variant X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/css/user/img/doc/%3C?a=commitdiff_plain;h=0ba6420d3515e278b34c29e5afa1e58f6e08e9eb;p=vchess.git Draft Royalrace variant --- diff --git a/client/src/base_rules.js b/client/src/base_rules.js index d5849f3b..4f1478e1 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -467,7 +467,7 @@ export const ChessRules = class ChessRules { return { x: 8, y: 8 }; } - // Color of thing on suqare (i,j). 'undefined' if square is empty + // Color of thing on square (i,j). 'undefined' if square is empty getColor(i, j) { return this.board[i][j].charAt(0); } @@ -543,7 +543,7 @@ export const ChessRules = class ChessRules { //////////////////// // MOVES GENERATION - // All possible moves from selected square (assumption: color is OK) + // All possible moves from selected square getPotentialMovesFrom([x, y]) { switch (this.getPiece(x, y)) { case V.PAWN: diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 963380a7..56b5bab3 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -141,6 +141,7 @@ export const translations = { "Explosive captures": "Explosive captures", "In the shadow": "In the shadow", "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", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 8b71fb4c..48eb21ec 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -141,6 +141,7 @@ export const translations = { "Explosive captures": "Capturas explosivas", "In the shadow": "En la sombra", "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", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 6d0f82d6..1e73e1e4 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -141,6 +141,7 @@ export const translations = { "Explosive captures": "Captures explosives", "In the shadow": "Dans l'ombre", "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", diff --git a/client/src/translations/rules/Royalrace/en.pug b/client/src/translations/rules/Royalrace/en.pug new file mode 100644 index 00000000..e9c796ad --- /dev/null +++ b/client/src/translations/rules/Royalrace/en.pug @@ -0,0 +1,12 @@ +p.boxed + | Bring your king to the other side of the board. + | Giving check is not allowed. + +p TODO + +h3 Source + +p + | Strongly inspired by the Racing Kings variant which is playable for example + a(href="https://lichess.org/variant/racingKings") on lichess + | . diff --git a/client/src/translations/rules/Royalrace/es.pug b/client/src/translations/rules/Royalrace/es.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Royalrace/es.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/translations/rules/Royalrace/fr.pug b/client/src/translations/rules/Royalrace/fr.pug new file mode 100644 index 00000000..4f56997b --- /dev/null +++ b/client/src/translations/rules/Royalrace/fr.pug @@ -0,0 +1 @@ +p TODO diff --git a/client/src/variants/Circular.js b/client/src/variants/Circular.js index 4db5a52a..9ec597bd 100644 --- a/client/src/variants/Circular.js +++ b/client/src/variants/Circular.js @@ -170,9 +170,9 @@ export const VariantRules = class CircularRules extends ChessRules { } isAttackedByPawn([x, y], colors) { + const pawnShift = 1; + const attackerRow = V.ComputeX(x + pawnShift); for (let c of colors) { - let pawnShift = 1; - const attackerRow = V.ComputeX(x + pawnShift); for (let i of [-1, 1]) { if ( y + i >= 0 && diff --git a/client/src/variants/Royalrace.js b/client/src/variants/Royalrace.js new file mode 100644 index 00000000..1ae531d5 --- /dev/null +++ b/client/src/variants/Royalrace.js @@ -0,0 +1,211 @@ +import { ChessRules } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt, shuffle } from "@/utils/alea"; + +export const VariantRules = class RoyalraceRules extends ChessRules { + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get CanFlip() { + return false; + } + + static get size() { + return { x: 11, y: 11 }; + } + + static GenRandInitFen() { + let pieces = { w: new Array(10), b: new Array(10) }; + // Shuffle pieces on first and second rank + for (let c of ["w", "b"]) { + // Reserve 4 and 5 which are pawns positions + let positions = ArrayFun.range(10).filter(i => i != 4 && i != 5); + + // Get random squares for bishops + let randIndex = 2 * randInt(4); + const bishop1Pos = positions[randIndex]; + // The second bishop must be on a square of different color + let randIndex_tmp = 2 * randInt(4) + 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); + + // Place the king at random on (remaining squares of) first row + let maxIndex = 4; + if (positions[maxIndex-1] >= 4) + maxIndex--; + if (positions[maxIndex-1] >= 4) + maxIndex--; + randIndex = randInt(maxIndex); + const kingPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random squares for knights + randIndex = randInt(5); + const knight1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(4); + const knight2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Get random squares for rooks + randIndex = randInt(3); + const rook1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(2); + const rook2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Queen position is now determined, + // because pawns are not placed at random + const queenPos = positions[0]; + + // Finally put the shuffled pieces in the board array + pieces[c][rook1Pos] = "r"; + pieces[c][knight1Pos] = "n"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; + pieces[c][4] = "p"; + pieces[c][5] = "p"; + } + const whiteFen = pieces["w"].join("").toUpperCase(); + const blackFen = pieces["b"].join(""); + return ( + "11/11/11/11/11/11/11/11/11/" + + whiteFen.substr(5).split("").reverse().join("") + + "1" + + blackFen.substr(5).split("").reverse().join("") + + "/" + + whiteFen.substr(0,5) + "1" + blackFen.substr(0,5) + + " w 0" + ); + } + + getPotentialPawnMoves([x, y]) { + // Normal moves (as a rook) + let moves = + this.getSlideNJumpMoves([x, y], V.steps[V.ROOK]).filter(m => { + // Remove captures. Alt: redefine canTake + return m.vanish.length == 1; + }); + + // Captures + const shiftX = -1; + for (let shiftY of [-1, 1]) { + if ( + V.OnBoard(x + shiftX, y + shiftY) && + 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])); + } + } + + return moves; + } + + getPotentialKnightMoves(sq) { + // Knight becomes knightrider: + return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]); + } + + // What are the king moves from square x,y ? + getPotentialKingMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); + } + + filterValid(moves) { + if (moves.length == 0) return []; + const color = this.turn; + const oppCol = V.GetOppCol(color); + return moves.filter(m => { + this.play(m); + // Giving check is forbidden as well: + const res = !this.underCheck(color) && !this.underCheck(oppCol); + this.undo(m); + return res; + }); + } + + isAttackedByPawn([x, y], colors) { + const pawnShift = 1; + if (x + pawnShift < V.size.x) { + for (let c of colors) { + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < V.size.y && + this.getPiece(x + pawnShift, y + i) == V.PAWN && + this.getColor(x + pawnShift, y + i) == c + ) { + return true; + } + } + } + } + return false; + } + + isAttackedByKnight(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.KNIGHT, + V.steps[V.KNIGHT] + ); + } + + getCurrentScore() { + // Turn has changed: + const color = V.GetOppCol(this.turn); + if (this.kingPos[color][0] == 0) + // The opposing edge is reached! + return color == "w" ? "1-0" : "0-1"; + return "*"; + } + + static get SEARCH_DEPTH() { + return 2; + } + + static get VALUES() { + return { + p: 2, + r: 5, + n: 3, + b: 3, + q: 9, + k: 1000 + }; + } + + evalPosition() { + // Count material: + let evaluation = super.evalPosition(); + // Ponder with king position: + return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0]; + } + + getNotation(move) { + // Since pawns are much more mobile, treat them as other pieces: + return ( + move.vanish[0].p.toUpperCase() + + (move.vanish.length > move.appear.length ? "x" : "") + + V.CoordsToSquare(move.end) + ); + } +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index e6291462..84a34d5d 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -22,6 +22,7 @@ insert or ignore into Variants (name,description) values ('Losers', 'Lose all pieces'), ('Magnetic', 'Laws of attraction'), ('Marseille', 'Move twice'), + ('Royalrace', 'King crosses the board'), ('Recycle', 'Reuse pieces'), ('Suction', 'Attract opposite king'), ('Upsidedown', 'Board upside down'),