From: Benjamin Auder Date: Mon, 30 Mar 2020 23:46:06 +0000 (+0200) Subject: Add Colorbound Clobberers (R. Betza) X-Git-Url: https://git.auder.net/variants/Chakart/pieces/current/css/doc/mini-custom.min.css?a=commitdiff_plain;h=801e28709e778bd3a93b014d1f9cb2fb7906e303;p=vchess.git Add Colorbound Clobberers (R. Betza) --- diff --git a/client/public/images/pieces/Colorbound/ba.svg b/client/public/images/pieces/Colorbound/ba.svg new file mode 100644 index 00000000..c7e24dfc --- /dev/null +++ b/client/public/images/pieces/Colorbound/ba.svg @@ -0,0 +1,91 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/bd.svg b/client/public/images/pieces/Colorbound/bd.svg new file mode 100644 index 00000000..d7ba58db --- /dev/null +++ b/client/public/images/pieces/Colorbound/bd.svg @@ -0,0 +1,91 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/bh.svg b/client/public/images/pieces/Colorbound/bh.svg new file mode 100644 index 00000000..729f599f --- /dev/null +++ b/client/public/images/pieces/Colorbound/bh.svg @@ -0,0 +1,97 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/bs.svg b/client/public/images/pieces/Colorbound/bs.svg new file mode 100644 index 00000000..034e0099 --- /dev/null +++ b/client/public/images/pieces/Colorbound/bs.svg @@ -0,0 +1,79 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/wa.svg b/client/public/images/pieces/Colorbound/wa.svg new file mode 100644 index 00000000..c194f12b --- /dev/null +++ b/client/public/images/pieces/Colorbound/wa.svg @@ -0,0 +1,97 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/wd.svg b/client/public/images/pieces/Colorbound/wd.svg new file mode 100644 index 00000000..eb9aa159 --- /dev/null +++ b/client/public/images/pieces/Colorbound/wd.svg @@ -0,0 +1,97 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/wh.svg b/client/public/images/pieces/Colorbound/wh.svg new file mode 100644 index 00000000..fa15c5dd --- /dev/null +++ b/client/public/images/pieces/Colorbound/wh.svg @@ -0,0 +1,62 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/client/public/images/pieces/Colorbound/ws.svg b/client/public/images/pieces/Colorbound/ws.svg new file mode 100644 index 00000000..40a7d2e9 --- /dev/null +++ b/client/public/images/pieces/Colorbound/ws.svg @@ -0,0 +1,145 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/components/UploadGame.vue b/client/src/components/UploadGame.vue index a94c39b7..b4e4d853 100644 --- a/client/src/components/UploadGame.vue +++ b/client/src/components/UploadGame.vue @@ -14,16 +14,16 @@ export default { name: "my-upload-game", methods: { uploadTrigger: function() { - document.getElementById("upload").click(); - }, - upload: function(e) { - const file = (e.target.files || e.dataTransfer.files)[0]; - var reader = new FileReader(); - reader.onloadend = ev => { - this.parseAndEmit(ev.currentTarget.result); - }; - reader.readAsText(file); - }, + document.getElementById("upload").click(); + }, + upload: function(e) { + const file = (e.target.files || e.dataTransfer.files)[0]; + var reader = new FileReader(); + reader.onloadend = ev => { + this.parseAndEmit(ev.currentTarget.result); + }; + reader.readAsText(file); + }, parseAndEmit: async function(pgn) { let game = { // Players potential ID and socket IDs are not searched diff --git a/client/src/translations/en.js b/client/src/translations/en.js index e8c1e5c1..c2636745 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -214,6 +214,7 @@ export const translations = { "Shoot pieces": "Shoot pieces", "Squares disappear": "Squares disappear", "Standard rules": "Standard rules", + "The colorbound clobberers": "The colorbound clobberers", "The end of the world": "The end of the world", "Transform an essay": "Transform an essay", "Two kings": "Two kings", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 85b458da..cb9b15de 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -214,6 +214,7 @@ export const translations = { "Shoot pieces": "Tirar de las piezas", "Squares disappear": "Las casillas desaparecen", "Standard rules": "Reglas estandar", + "The colorbound clobberers": "Los batidores unicolor", "The end of the world": "El fin del mundo", "Transform an essay": "Transformar un ensayo", "Two kings": "Dos reyes", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 493b5b5f..5b2fb42b 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -214,6 +214,7 @@ export const translations = { "Shoot pieces": "Tirez sur les pièces", "Squares disappear": "Les cases disparaissent", "Standard rules": "Règles usuelles", + "The colorbound clobberers": "Les tabasseurs unicolores", "The end of the world": "La fin du monde", "Transform an essay": "Transformer un essai", "Two kings": "Deux rois", diff --git a/client/src/translations/rules/Colorbound/en.pug b/client/src/translations/rules/Colorbound/en.pug new file mode 100644 index 00000000..218c3a6f --- /dev/null +++ b/client/src/translations/rules/Colorbound/en.pug @@ -0,0 +1,37 @@ +p.boxed + | Black pieces are replaced by a new army, where most pieces are quite + | colorbound. + +p Black pawns and king move as usual, but the other pieces do not: +ul + li Rook = bishop + dabbabah (D), + li Knight = wazir + alfil = "phoenix" (H), + li Bishop = ferz + alfil + dabbabah (A), + li Queen = bishop + knight = "princess" (S). +p. + The Ferz and Wazir move respectively like a single-step bishop, and + single-step rook. The Alfil and Dabbabah move respectively like a + double-step bishop and a double-step rook. They can both leap over a piece. + +figure.diagram-container + .diagram.diag12 + | fen:8/8/8/3d4/8/8/8/8 a8,b7,c6,e4,f3,g2,h1,c4,b3,a2,e6,f7,g8,f5,d3,b5,d7: + .diagram.diag22 + | fen:8/8/8/4h3/8/8/8/8 e6,f5,e4,d5,c7,g7,g3,c3: + figcaption. + Moves of the black rook on the left, and of the black knight on the right. + +p Pawns can be promoted into any piece (including those of the other team). + +p. + When castling large, the black king arrives on b8 and the a8 rook moves + to c8, to remain on the same color. + +h3 Source + +p + a(href="https://www.chessvariants.com/d.betza/chessvar/dan/colclob.html") + | The colorbound clobberers + |  on chessvariants.com. + +p Inventor: Ralph Betza (1996) diff --git a/client/src/translations/rules/Colorbound/es.pug b/client/src/translations/rules/Colorbound/es.pug new file mode 100644 index 00000000..6290f2db --- /dev/null +++ b/client/src/translations/rules/Colorbound/es.pug @@ -0,0 +1,42 @@ +p.boxed + | Las piezas negras son reemplazadas por un nuevo equipo, que incluye + | la mayoría de las piezas se estiraron para evolucionar en un solo color. + +p. + Los peones negros y el rey mantienen sus movimientos habituales, pero este + no es el caso para las otras piezas: +ul + li Torre = alfil + dabbabah (D), + li Caballo = wazir + alfil = "phoenix" (H), + li Alfil = ferz + elefante + dabbabah (A), + li Dama = alfil + caballo = "princesa" (S). +p. + El Ferz y el Wazir viajan respectivamente como un alfil y una torre, + pero de una casilla solamente. El Elefante y el Dabbabah se mueven + respectivamente de dos casillas como un alfil y una torre, + posiblemente saltando sobre una pieza. + +figure.diagram-container + .diagram.diag12 + | fen:8/8/8/3d4/8/8/8/8 a8,b7,c6,e4,f3,g2,h1,c4,b3,a2,e6,f7,g8,f5,d3,b5,d7: + .diagram.diag22 + | fen:8/8/8/4h3/8/8/8/8 e6,f5,e4,d5,c7,g7,g3,c3: + figcaption. + Jugadas de la torre negra a la izquierda y del caballo negro a la derecha. + +p. + Los peones pueden ser promovidos a cualquier piezas (incluidos las del + otro equipo) + +p. + Después del gran enroque, el rey negro llega en b8 y la torre a8 entra en c8, + para permanecer en el mismo color. + +h3 Fuente + +p + a(href="https://www.chessvariants.com/d.betza/chessvar/dan/colclob.html") + | Los "batidores unicolor" + |  en chessvariants.com. + +p Inventor: Ralph Betza (1996) diff --git a/client/src/translations/rules/Colorbound/fr.pug b/client/src/translations/rules/Colorbound/fr.pug new file mode 100644 index 00000000..8716bff8 --- /dev/null +++ b/client/src/translations/rules/Colorbound/fr.pug @@ -0,0 +1,42 @@ +p.boxed + | Les pièces noires sont remplacées par une nouvelle équipe, dont + | la plupart des pièces tendent à évoluer sur une seule couleur. + +p. + Les pions et le roi noirs conservent leur déplacements habituels, mais ce + n'est pas le cas des autres pièces : +ul + li Tour = fou + dabbabah (D), + li Cavalier = wazir + alfil = "phoenix" (H), + li Fou = ferz + alfil + dabbabah (A), + li Dame = fou + cavalier = "princesse" (S). +p. + Le Ferz et le Wazir se déplacent respectivement comme un fou et une tour + mais d'une seule case. L'Alfil et le Dabbabah se déplacent respectivement + de deux cases comme un fou et une tour, + en sautant éventuellement par dessus une pièce. + +figure.diagram-container + .diagram.diag12 + | fen:8/8/8/3d4/8/8/8/8 a8,b7,c6,e4,f3,g2,h1,c4,b3,a2,e6,f7,g8,f5,d3,b5,d7: + .diagram.diag22 + | fen:8/8/8/4h3/8/8/8/8 e6,f5,e4,d5,c7,g7,g3,c3: + figcaption. + Coups de la tour noire à gauche, et du cavalier noir à droite. + +p. + Les pions peuvent être promus en n'importe quelle pièce (incluant celles + de l'autre équipe). + +p. + Après le grand roque, le roi noir arrive en b8 et la tour a8 vient en c8, + afin de rester sur la même couleur. + +h3 Source + +p + a(href="https://www.chessvariants.com/d.betza/chessvar/dan/colclob.html") + | Les "tabasseurs unicolores" + |  sur chessvariants.com. + +p Inventeur : Ralph Betza (1996) diff --git a/client/src/variants/Cannibal.js b/client/src/variants/Cannibal.js index f7a95149..1a8a4518 100644 --- a/client/src/variants/Cannibal.js +++ b/client/src/variants/Cannibal.js @@ -88,7 +88,7 @@ export class CannibalRules extends ChessRules { return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1); } - // Stop at the first capture found (if any) + // Stop at the first capture found (if any) atLeastOneCapture() { const color = this.turn; const oppCol = V.GetOppCol(color); diff --git a/client/src/variants/Capture.js b/client/src/variants/Capture.js index 413356dd..19ce734e 100644 --- a/client/src/variants/Capture.js +++ b/client/src/variants/Capture.js @@ -6,7 +6,7 @@ export class CaptureRules extends ChessRules { return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1); } - // Stop at the first capture found (if any) + // Stop at the first capture found (if any) atLeastOneCapture() { const color = this.turn; const oppCol = V.GetOppCol(color); diff --git a/client/src/variants/Colorbound.js b/client/src/variants/Colorbound.js new file mode 100644 index 00000000..034f9575 --- /dev/null +++ b/client/src/variants/Colorbound.js @@ -0,0 +1,330 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +export class ColorboundRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { promotions: V.PIECES } + ); + } + + getPpath(b) { + if ([V.C_ROOK, V.C_KNIGHT, V.C_BISHOP, V.C_QUEEN].includes(b[1])) + return "Colorbound/" + b; + return b; + } + + static GenRandInitFen(randomness) { + if (randomness == 0) + return "dhaskahd/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -"; + + // Mapping white --> black (at least at start): + const piecesMap = { + 'r': 'd', + 'n': 'h', + 'b': 'a', + 'q': 's', + 'k': 'k' + }; + + let pieces = { w: new Array(8), b: new Array(8) }; + let flags = ""; + // Shuffle pieces on first (and last rank if randomness == 2) + for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w'].map(p => piecesMap[p]); + flags += flags; + break; + } + + // TODO: same code as in base_rules. Should extract and factorize? + + let positions = ArrayFun.range(8); + + let randIndex = 2 * randInt(4); + const bishop1Pos = positions[randIndex]; + let randIndex_tmp = 2 * randInt(4) + 1; + const bishop2Pos = positions[randIndex_tmp]; + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); + + randIndex = randInt(6); + const knight1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = randInt(5); + const knight2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + randIndex = randInt(4); + const queenPos = positions[randIndex]; + positions.splice(randIndex, 1); + + const rook1Pos = positions[0]; + const kingPos = positions[1]; + const rook2Pos = positions[2]; + + 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"; + if (c == 'b') pieces[c] = pieces[c].map(p => piecesMap[p]); + flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos); + } + // Add turn + flags + enpassant + return ( + pieces["b"].join("") + + "/8/pppppppp/8/8/8/PPPPPPPP/" + + pieces["w"].join("").toUpperCase() + + " w 0 " + flags + " -" + ); + } + + static get C_ROOK() { + return 'd'; + } + static get C_KNIGHT() { + return 'h'; + } + static get C_BISHOP() { + return 'a'; + } + static get C_QUEEN() { + return 's'; + } + + static get PIECES() { + return ( + ChessRules.PIECES.concat([V.C_ROOK, V.C_KINGHT, V.C_BISHOP, V.C_QUEEN]) + ); + } + + getPotentialMovesFrom([x, y]) { + switch (this.getPiece(x, y)) { + case V.C_ROOK: + return this.getPotentialC_rookMoves([x, y]); + case V.C_KNIGHT: + return this.getPotentialC_knightMoves([x, y]); + case V.C_BISHOP: + return this.getPotentialC_bishopMoves([x, y]); + case V.C_QUEEN: + return this.getPotentialC_queenMoves([x, y]); + default: + return super.getPotentialMovesFrom([x, y]); + } + return []; + } + + static get steps() { + return Object.assign( + {}, + ChessRules.steps, + { + // Dabbabah + 'd': [ + [-2, 0], + [0, -2], + [2, 0], + [0, 2] + ], + // Alfil + 'a': [ + [2, 2], + [2, -2], + [-2, 2], + [-2, -2] + ], + // Ferz + 'f': [ + [1, 1], + [1, -1], + [-1, 1], + [-1, -1] + ] + } + ); + } + + getPotentialC_rookMoves(sq) { + return ( + this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( + this.getSlideNJumpMoves(sq, V.steps['d'], "oneStep")) + ); + } + + getPotentialC_knightMoves(sq) { + return ( + this.getSlideNJumpMoves(sq, V.steps['a'], "oneStep").concat( + this.getSlideNJumpMoves(sq, V.steps[V.ROOK], "oneStep")) + ); + } + + getPotentialC_bishopMoves(sq) { + return ( + this.getSlideNJumpMoves(sq, V.steps['d'], "oneStep").concat( + this.getSlideNJumpMoves(sq, V.steps['a'], "oneStep")).concat( + this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep")) + ); + } + + getPotentialC_queenMoves(sq) { + return ( + this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) + ); + } + + // TODO: really find a way to avoid duolicating most of the castling code + // each time: here just the queenside castling squares change for black. + getCastleMoves([x, y]) { + const c = this.getColor(x, y); + if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) + return []; + + const oppCol = V.GetOppCol(c); + let moves = []; + let i = 0; + // King, then rook: + const finalSquares = [ + // Black castle long in an unusual way: + (c == 'w' ? [2, 3] : [1, 2]), + [V.size.y - 2, V.size.y - 3] + ]; + castlingCheck: for ( + let castleSide = 0; + castleSide < 2; + castleSide++ //large, then small + ) { + if (this.castleFlags[c][castleSide] >= V.size.y) continue; + + const rookPos = this.castleFlags[c][castleSide]; + const castlingPiece = this.getPiece(x, rookPos); + const finDist = finalSquares[castleSide][0] - y; + let step = finDist / Math.max(1, Math.abs(finDist)); + i = y; + do { + if ( + this.isAttacked([x, i], oppCol) || + (this.board[x][i] != V.EMPTY && + (this.getColor(x, i) != c || + ![V.KING, castlingPiece].includes(this.getPiece(x, i)))) + ) { + continue castlingCheck; + } + i += step; + } while (i != finalSquares[castleSide][0]); + + step = castleSide == 0 ? -1 : 1; + for (i = y + step; i != rookPos; i += step) { + if (this.board[x][i] != V.EMPTY) continue castlingCheck; + } + + for (i = 0; i < 2; i++) { + if ( + finalSquares[castleSide][i] != rookPos && + this.board[x][finalSquares[castleSide][i]] != V.EMPTY && + ( + this.getPiece(x, finalSquares[castleSide][i]) != V.KING || + this.getColor(x, finalSquares[castleSide][i]) != c + ) + ) { + continue castlingCheck; + } + } + + moves.push( + new Move({ + appear: [ + new PiPo({ + x: x, + y: finalSquares[castleSide][0], + p: V.KING, + c: c + }), + new PiPo({ + x: x, + y: finalSquares[castleSide][1], + p: castlingPiece, + c: c + }) + ], + vanish: [ + new PiPo({ x: x, y: y, p: V.KING, c: c }), + new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c }) + ], + end: + Math.abs(y - rookPos) <= 2 + ? { x: x, y: rookPos } + : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } + }) + ); + } + + return moves; + } + + isAttacked(sq, color) { + return ( + super.isAttacked(sq, color) || + this.isAttackedByC_rook(sq, color) || + this.isAttackedByC_knight(sq, color) || + this.isAttackedByC_bishop(sq, color) || + this.isAttackedByC_queen(sq, color) + ); + } + + isAttackedByC_rook(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.C_ROOK, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump( + sq, color, V.C_ROOK, V.steps['d'], "oneStep") + ); + } + + isAttackedByC_knight(sq, color) { + return ( + this.isAttackedBySlideNJump( + sq, color, V.C_KNIGHT, V.steps[V.ROOK], "oneStep") || + this.isAttackedBySlideNJump( + sq, color, V.C_KNIGHT, V.steps['a'], "oneStep") + ); + } + + isAttackedByC_bishop(sq, color) { + return ( + this.isAttackedBySlideNJump( + sq, color, V.C_BISHOP, V.steps['d'], "oneStep") || + this.isAttackedBySlideNJump( + sq, color, V.C_BISHOP, V.steps['a'], "oneStep") || + this.isAttackedBySlideNJump( + sq, color, V.C_BISHOP, V.steps['f'], "oneStep") + ); + } + + isAttackedByC_queen(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.C_QUEEN, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump( + sq, color, V.C_ROOK, V.steps[V.KNIGHT], "oneStep") + ); + } + + static get VALUES() { + return Object.assign( + {}, + ChessRules.VALUES, + { + d: 4, + h: 3, + a: 5, + s: 6 + } + ); + } +}; diff --git a/client/src/variants/Losers.js b/client/src/variants/Losers.js index daabbab4..d82e57d5 100644 --- a/client/src/variants/Losers.js +++ b/client/src/variants/Losers.js @@ -8,7 +8,7 @@ export class LosersRules extends ChessRules { return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1); } - // Stop at the first capture found (if any) + // Stop at the first capture found (if any) atLeastOneCapture() { const color = this.turn; const oppCol = V.GetOppCol(color); diff --git a/server/db/populate.sql b/server/db/populate.sql index 2fb1b0d0..74612223 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -26,6 +26,7 @@ insert or ignore into Variants (name, description) values ('Checkless', 'No-check mode'), ('Chess960', 'Standard rules'), ('Circular', 'Run forward'), + ('Colorbound', 'The colorbound clobberers'), ('Coregal', 'Two royal pieces'), ('Crazyhouse', 'Captures reborn'), ('Cylinder', 'Neverending rows'),