From 7d8bf63e973bbb9766ab3b4555d58e04f2641167 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 22 Feb 2020 18:59:12 +0100 Subject: [PATCH] Add EnPassant variant draft --- client/src/components/Board.vue | 2 +- client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + .../src/translations/rules/Antimatter/en.pug | 2 - .../src/translations/rules/Antimatter/es.pug | 2 - .../src/translations/rules/Antimatter/fr.pug | 2 - .../src/translations/rules/Enpassant/en.pug | 36 +++ .../src/translations/rules/Enpassant/es.pug | 39 +++ .../src/translations/rules/Enpassant/fr.pug | 39 +++ client/src/variants/Enpassant.js | 223 ++++++++++++++++++ server/db/populate.sql | 1 + 12 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 client/src/translations/rules/Enpassant/en.pug create mode 100644 client/src/translations/rules/Enpassant/es.pug create mode 100644 client/src/translations/rules/Enpassant/fr.pug create mode 100644 client/src/variants/Enpassant.js diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index bd2612d8..62680021 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -343,7 +343,7 @@ export default { // Next condition: classList.contains(piece) fails because of marks while (landing.tagName == "IMG") landing = landing.parentNode; if (this.start.id == landing.id) - //one or multi clicks on same piece + // One or multi clicks on same piece return; // OK: process move attempt, landing is a square node let endSquare = getSquareFromId(landing.id); diff --git a/client/src/translations/en.js b/client/src/translations/en.js index df3b1d22..f44b82f5 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -130,6 +130,7 @@ export const translations = { "Board upside down": "Board upside down", "Both sides of the mirror": "Both sides of the mirror", "Capture all of a kind": "Capture all of a kind", + "Capture en passant": "Capture en passant", "Captures reborn": "Captures reborn", "Change colors": "Change colors", "Dangerous collisions": "Dangerous collisions", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 5588cbb9..ea576c17 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -130,6 +130,7 @@ export const translations = { "Board upside down": "Tablero al revés", "Both sides of the mirror": "Ambos lados del espejo", "Capture all of a kind": "Capturar todo del mismo tipo", + "Capture en passant": "Capturar en passant", "Captures reborn": "Las capturas renacen", "Change colors": "Cambiar colores", "Dangerous collisions": "Colisiones peligrosas", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index ed302bae..0f09a092 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -130,6 +130,7 @@ export const translations = { "Board upside down": "Échiquier à l'envers", "Both sides of the mirror": "Les deux côté du miroir", "Capture all of a kind": "Capturez tout d'un même type", + "Capture en passant": "Capturer en passant", "Captures reborn": "Les captures renaissent", "Change colors": "Changer les couleurs", "Dangerous collisions": "Collisions dangeureuses", diff --git a/client/src/translations/rules/Antimatter/en.pug b/client/src/translations/rules/Antimatter/en.pug index 3c8e3337..b50aac15 100644 --- a/client/src/translations/rules/Antimatter/en.pug +++ b/client/src/translations/rules/Antimatter/en.pug @@ -15,8 +15,6 @@ figure.diagram-container | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR: figcaption After 1.b3 c5 2.Bb2 Nc6 -p This detail excepted, the orthodox chess rules apply. - h3 Source p diff --git a/client/src/translations/rules/Antimatter/es.pug b/client/src/translations/rules/Antimatter/es.pug index 60cf1ecb..b6adc808 100644 --- a/client/src/translations/rules/Antimatter/es.pug +++ b/client/src/translations/rules/Antimatter/es.pug @@ -15,8 +15,6 @@ figure.diagram-container | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR: figcaption Después de 1.b3 c5 2.Bb2 Nc6 -p Excepto por este detalle, se aplican las reglas del ajedrez ortodoxo. - h3 Fuente p diff --git a/client/src/translations/rules/Antimatter/fr.pug b/client/src/translations/rules/Antimatter/fr.pug index 1e2dbee1..7b1cdfae 100644 --- a/client/src/translations/rules/Antimatter/fr.pug +++ b/client/src/translations/rules/Antimatter/fr.pug @@ -15,8 +15,6 @@ figure.diagram-container | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR: figcaption After 1.b3 c5 2.Bb2 Nc6 -p Ce détail excepté, les règles des échecs orthodoxes s'appliquent. - h3 Source p diff --git a/client/src/translations/rules/Enpassant/en.pug b/client/src/translations/rules/Enpassant/en.pug new file mode 100644 index 00000000..7ec2cc2e --- /dev/null +++ b/client/src/translations/rules/Enpassant/en.pug @@ -0,0 +1,36 @@ +p.boxed + | All pieces can be captured en passant, by any piece. + +p More precisely: +ul + li. + A piece making a multistep move can be captured by an opponent's piece + guarding a square on its way; this capture is only possible right after + the move (same condition as for en passant pawns captures). + li. + Even if the capturer gets captured en passant on next turn, + the initial captured piece does not return to the board. + +p This is a generalisation of the pawn en passant capture, which is still possible. + +h3 Special moves + +ul + li. + Knights become knightriders, which may make multi knight-steps in the same direction. + For example in the standard initial position, the knightrider on g1 + can go to e5 or capture d7 in addition to the knight moves (see diagram below). + li The king can capture a piece en passant by making a knight move. + +figure.diagram-container + .diagram + | fen:rnbqk1nr/ppppppbp/6p1/8/8/2NP5/PPP1PPPP/R1BQKBNR h3,f3,e5,d7: + figcaption. + Possible knightrider moves after 1.d3 g6 2.Nc3 Bg7. + If 3.Nxd7, 3...Bxe5 e.p. is possible. + +h3 Source + +p + a(href="https://www.chessvariants.com/difftaking.dir/enpassant.html") En Passant chess + |  on chessvariants.com. diff --git a/client/src/translations/rules/Enpassant/es.pug b/client/src/translations/rules/Enpassant/es.pug new file mode 100644 index 00000000..1c501597 --- /dev/null +++ b/client/src/translations/rules/Enpassant/es.pug @@ -0,0 +1,39 @@ +p.boxed + | Todas las piezas se pueden capturar en passant, por cualquier pieza. + +p Más específicamente: +ul + li. + Se puede capturar una pieza que mueve varias casillas + por una pieza opuesta que controla un cuadrado en su camino; + esta captura solo es posible inmediatamente después del movimiento + (misma condición que para capturar peones en passant). + li. + Incluso si el capturador se encuentra capturado en el siguiente movimiento, + la pieza inicialmente capturada no vuelve al tablero. + +p Es una generalización de la captura en passant de los peones, que sigue siendo posible. + +h3 Movimientos especiales + +ul + li. + Los caballos se convierten en caballeros, capaces de realizar varias + saltos en la misma dirección. + Por ejemplo en la posición inicial, el caballero en g1 puede ir a e5 + o tomar d7 además de los movimientos de caballo. + li El rey puede capturar una pieza en passant haciendo un movimiento de caballo. + +figure.diagram-container + .diagram + | fen:rnbqk1nr/ppppppbp/6p1/8/8/2NP5/PPP1PPPP/R1BQKBNR h3,f3,e5,d7: + figcaption. + Posibles jugadas de caballero después de 1.d3 g6 2.Nc3 Bg7. + Si 3.Nxd7, 3...Bxe5 e.p. es posible. + +h3 Fuente + +p + | La + a(href="https://www.chessvariants.com/difftaking.dir/enpassant.html") variante En Passant + |  en chessvariants.com. diff --git a/client/src/translations/rules/Enpassant/fr.pug b/client/src/translations/rules/Enpassant/fr.pug new file mode 100644 index 00000000..6dd1e14f --- /dev/null +++ b/client/src/translations/rules/Enpassant/fr.pug @@ -0,0 +1,39 @@ +p.boxed + | Toutes les pièces peuvent être capturées en passant, par n'importe quelle pièce. + +p Plus précisément : +ul + li. + Une pièce effectuant un déplacement de plusieurs cases peut être capturée + par une pièce adverse contrôlant une case sur son chemin ; + cette capture n'est possible qu'immédiatement après le coup + (même condition que pour les captures de pions en passant). + li. + Même si le capturant se retrouve capturé au coup suivant, la pièce + initialement capturée ne revient pas sur l'échiquier. + +p C'est une généralisation de la prise en passant des pions, qui reste possible. + +h3 Coups spéciaux + +ul + li. + Les cavaliers deviennent des chevaliers, pouvant effectuer plusieurs + déplacements de cavalier dans la même direction. + Par exemple dans la position initiale, le chevalier en g1 peut aller en e5 + ou prendre d7 en plus des coups de cavalier. + li Le roi peut capturer une pièce en passant en effectuant un coup de cavalier. + +figure.diagram-container + .diagram + | fen:rnbqk1nr/ppppppbp/6p1/8/8/2NP5/PPP1PPPP/R1BQKBNR h3,f3,e5,d7: + figcaption. + Possibles coups de chevalier après 1.d3 g6 2.Nc3 Bg7. + Si 3.Nxd7, 3...Bxe5 e.p. est possible. + +h3 Source + +p + | La + a(href="https://www.chessvariants.com/difftaking.dir/enpassant.html") variante En Passant + |  sur chessvariants.com. diff --git a/client/src/variants/Enpassant.js b/client/src/variants/Enpassant.js new file mode 100644 index 00000000..50f97387 --- /dev/null +++ b/client/src/variants/Enpassant.js @@ -0,0 +1,223 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; + +export const VariantRules = class EnpassantRules extends ChessRules { + + static IsGoodEnpassant(enpassant) { + if (enpassant != "-") { + const squares = enpassant.split(","); + for (let sq of squares) { + const ep = V.SquareToCoords(sq); + if (isNaN(ep.x) || !V.OnBoard(ep)) return false; + } + } + return true; + } + + getEpSquare(moveOrSquare) { + if (!moveOrSquare) return undefined; + if (typeof moveOrSquare === "string") { + const square = moveOrSquare; + if (square == "-") return undefined; + let res = []; + square.split(",").forEach(sq => { + res.push(V.SquareToCoords(sq)); + }); + return res; + } + // Argument is a move: all intermediate squares are en-passant candidates, + // except if the moving piece is a king. + const move = moveOrSquare; + const piece = move.appear[0].p; + if (piece == V.KING || + ( + Math.abs(move.end.x-move.start.x) <= 1 && + Math.abs(move.end.y-move.start.y) <= 1 + ) + ) { + return undefined; + } + const delta = [move.end.x-move.start.x, move.end.y-move.start.y]; + let step = undefined; + if (piece == V.KNIGHT) { + const divisor = Math.min(Math.abs(delta[0]), Math.abs(delta[1])); + step = [delta[0]/divisor || 0, delta[1]/divisor || 0]; + } else { + step = [delta[0]/Math.abs(delta[0]) || 0, delta[1]/Math.abs(delta[1]) || 0]; + } + let res = []; + for ( + let [x,y] = [move.start.x+step[0],move.start.y+step[1]]; + x != move.end.x || y != move.end.y; + x += step[0], y += step[1] + ) { + res.push({x:x, y:y}); + } + // Add final square to know which piece is taken en passant: + res.push(move.end); + return res; + } + + getEnpassantFen() { + const L = this.epSquares.length; + if (!this.epSquares[L - 1]) return "-"; //no en-passant + let res = ""; + this.epSquares[L - 1].forEach(sq => { + res += V.CoordsToSquare(sq) + ","; + }); + return res.slice(0, -1); //remove last comma + } + + // TODO: this getPotentialPawnMovesFrom() is mostly duplicated: + // it could be split in "capture", "promotion", "enpassant"... + 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 firstRank = color == "w" ? sizeX - 1 : 0; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + const pawnColor = this.getColor(x, y); //can be different for checkered + + // NOTE: next condition is generally true (no pawn on last rank) + if (x + shiftX >= 0 && x + shiftX < sizeX) { + const finalPieces = + x + shiftX == lastRank + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.PAWN]; + // One square forward + if (this.board[x + shiftX][y] == V.EMPTY) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { + c: pawnColor, + p: piece + }) + ); + } + // Next condition because pawns on 1st rank can generally jump + if ( + [startRank, firstRank].includes(x) && + 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]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: pawnColor, + p: piece + }) + ); + } + } + } + } + + // En passant + const Lep = this.epSquares.length; + const squares = this.epSquares[Lep - 1]; + if (!!squares) { + const S = squares.length; + const taken = squares[S-1]; + const pipoV = new PiPo({ + x: taken.x, + y: taken.y, + p: this.getPiece(taken.x, taken.y), + c: this.getColor(taken.x, taken.y) + }); + [...Array(S-1).keys()].forEach(i => { + const sq = squares[i]; + if (sq.x == x + shiftX && Math.abs(sq.y - y) == 1) { + let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]); + enpassantMove.vanish.push(pipoV); + moves.push(enpassantMove); + } + }); + } + + return moves; + } + + // Remove the "onestep" condition: knight promote to knightrider: + + getPotentialKnightMoves(sq) { + return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]); + } + + isAttackedByKnight(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.KNIGHT, + V.steps[V.KNIGHT] + ); + } + + getPotentialMovesFrom([x, y]) { + let moves = super.getPotentialMovesFrom([x,y]); + // Add en-passant captures from this square: + const L = this.epSquares.length; + if (!this.epSquares[L - 1]) return moves; + const squares = this.epSquares[L - 1]; + const S = squares.length; + // Object describing the removed opponent's piece: + const pipoV = new PiPo({ + x: squares[S-1].x, + y: squares[S-1].y, + c: V.GetOppCol(this.turn), + p: this.getPiece(squares[S-1].x, squares[S-1].y) + }); + // Check if existing non-capturing moves could also capture en passant + moves.forEach(m => { + if ( + m.appear[0].p != V.PAWN && //special pawn case is handled elsewhere + m.vanish.length <= 1 && + [...Array(S-1).keys()].some(i => { + return m.end.x == squares[i].x && m.end.y == squares[i].y; + }) + ) { + m.vanish.push(pipoV); + } + }); + // Special case of the king knight's movement: + if (this.getPiece(x, y) == V.KING) { + V.steps[V.KNIGHT].forEach(step => { + const endX = x + step[0]; + const endY = y + step[1]; + if ( + V.OnBoard(endX, endY) && + [...Array(S-1).keys()].some(i => { + return endX == squares[i].x && endY == squares[i].y; + }) + ) { + let enpassantMove = this.getBasicMove([x, y], [endX, endY]); + enpassantMove.vanish.push(pipoV); + moves.push(enpassantMove); + } + }); + } + return moves; + } + + static get VALUES() { + return { + p: 1, + r: 5, + n: 4, + b: 3, + q: 9, + k: 1000 + }; + } +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 3fea093a..a9386d6a 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -12,6 +12,7 @@ insert or ignore into Variants (name,description) values ('Chess960', 'Standard rules'), ('Crazyhouse', 'Captures reborn'), ('Dark', 'In the shadow'), + ('Enpassant', 'Capture en passant'), ('Extinction', 'Capture all of a kind'), ('Grand', 'Big board'), ('Losers', 'Lose all pieces'), -- 2.44.0