From a930dd712643df1d673bdc2b777b5912b9a41584 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 2 May 2020 19:58:25 +0200 Subject: [PATCH] Add Pacifist1 & 2. Missing french + spanish translations for now --- .../src/translations/rules/Pacifist1/en.pug | 67 +++++ .../src/translations/rules/Pacifist1/es.pug | 0 .../src/translations/rules/Pacifist1/fr.pug | 0 .../src/translations/rules/Pacifist2/en.pug | 33 +++ .../src/translations/rules/Pacifist2/es.pug | 0 .../src/translations/rules/Pacifist2/fr.pug | 0 client/src/variants/Benedict.js | 7 +- client/src/variants/Pacifist1.js | 263 ++++++++++++++++++ client/src/variants/Pacifist2.js | 58 ++++ 9 files changed, 423 insertions(+), 5 deletions(-) create mode 100644 client/src/translations/rules/Pacifist1/en.pug create mode 100644 client/src/translations/rules/Pacifist1/es.pug create mode 100644 client/src/translations/rules/Pacifist1/fr.pug create mode 100644 client/src/translations/rules/Pacifist2/en.pug create mode 100644 client/src/translations/rules/Pacifist2/es.pug create mode 100644 client/src/translations/rules/Pacifist2/fr.pug create mode 100644 client/src/variants/Pacifist1.js create mode 100644 client/src/variants/Pacifist2.js diff --git a/client/src/translations/rules/Pacifist1/en.pug b/client/src/translations/rules/Pacifist1/en.pug new file mode 100644 index 00000000..ca2f8fb9 --- /dev/null +++ b/client/src/translations/rules/Pacifist1/en.pug @@ -0,0 +1,67 @@ +p.boxed + | No captures. Pieces under attack insufficiently defended change color. + +p. + Conversion occurs when the amount of persuasion exceeds the + amount of support. Pieces persuade and support according to + their standard attack moves. Opposing pieces persuade, friendly support. + +p. + For example on the following diagram, 1.Nf5 attacks g7. + Since the bishop is undefended, it turns white. Same for the pawn e7. + Then, the rook f8 is converted. This allows to convert both the knight + on d8 and the bishop on g8. Finally, the pawns f7 and h7 are under attack + and thus also change color. + +figure.diagram-container + .diagram.diag12 + | fen:rkqn1rb1/ppppppbp/P7/1n4p1/8/4N3/1PPPPPPP/RNK1BBQR: + .diagram.diag22 + | fen:rkqN1RB1/ppppPPBP/P7/1n3Np1/8/8/1PPPPPPP/RNK1BBQR: + figcaption Before and after 1.Nf5 + +p. + The game ends when a king is converted, and the side cannot convert a king + at the next turn. Note that kings may be exchanged, as on the following + diagram: 1...g6 would convert back the original black king on e7, but + 1...Nb6 is also possible, turning the white one on a4 into black. + +figure.diagram-container + .diagram.diag12 + | fen:nbr1b1qr/ppppKppp/7n/4pN2/K3P3/P7/QPPP1PPP/B1R1RBN1: + .diagram.diag22 + | fen:1br1b1qr/ppppKppp/1n5n/4pN2/k3P3/P7/QPPP1PPP/B1R1RBN1: + figcaption before and after 1...Nb6 + +p. + On this final illustration, the king cannot be converted at next turn: + white wins. + +figure.diagram-container + .diagram.diag12 + | fen:1rknn1br/p1pp1p1p/3bp1p1/1p1q2n1/5p2/2P4B/pP1PP1PP/B1RKR1QN: + .diagram.diag22 + | fen:1RKNN1br/P1PP1p1p/1Q1Bp1p1/1P1q2n1/5P2/2P4B/pP1PP1PP/B1RKR2N: + figcaption Before and after 1.Qb6# + +p Notes: +ul + li. + It is possible for pieces to persuade or support through other + friendly pieces. For example, rooks and queens in the same orthogonal + or bishops and queens in the same diagonal. + li. + The Pawn is forbidden to move through a cell which is overly persuaded + during its initial two-step move. + li. + A King is allowed to move to cells which are being persuaded as + long as the level of persuasion is not greater than its support. + +h3 Source + +p + | Adapted from + a(href="https://www.chessvariants.com/rules/pacifist-chess") Pacifist Chess + |  description on chessvariants.com. + +p Inventor: Larry L. Smith (2007) diff --git a/client/src/translations/rules/Pacifist1/es.pug b/client/src/translations/rules/Pacifist1/es.pug new file mode 100644 index 00000000..e69de29b diff --git a/client/src/translations/rules/Pacifist1/fr.pug b/client/src/translations/rules/Pacifist1/fr.pug new file mode 100644 index 00000000..e69de29b diff --git a/client/src/translations/rules/Pacifist2/en.pug b/client/src/translations/rules/Pacifist2/en.pug new file mode 100644 index 00000000..d4543621 --- /dev/null +++ b/client/src/translations/rules/Pacifist2/en.pug @@ -0,0 +1,33 @@ +p.boxed + | Pas de captures. + | Les pièces attaquées insuffisamment défendues changent de couleur. + +p + | Same as + a(href="/#/variants/Pacifist1") Pacifist1 + | , but persuasion and support are determined by the sum of pieces values + | attacking a square. For example on the following diagram, + | 1...Qa7 converts many white pieces because the rook is worth more than the + | knight defending e1, so the king turns black. The king then turns all + | surrounding pieces black, then c2 changes color because it is attacked by + | the bishop on d1. Finally the pawn on c2 attacks the rook, which converts + | the bishop on a1 and then the b2 pawn with the help of the newly converted + | bishop. + | In Pacifist1, only the rook would change color. + +figure.diagram-container + .diagram.diag12 + | fen:qrbnkb1n/1pppp1rp/p7/5Pp1/5PPP/3N3Q/PPPPP3/BR1BK1RN: + .diagram.diag22 + | fen:1rbnkb1n/qpppp1rp/p7/5Pp1/5PPP/3N3Q/Ppppp3/br1bk1rN: + figcaption Before and after 1...Qa7 + +p The pieces values are taken as follows: +ul + li Pawn: 1 + li Rook: 5 + li Knight: 3 + li Bishop: 3 + li Queen: 9 + li King: 1000 +p So the king converts anything next to him, unless the other king is around. diff --git a/client/src/translations/rules/Pacifist2/es.pug b/client/src/translations/rules/Pacifist2/es.pug new file mode 100644 index 00000000..e69de29b diff --git a/client/src/translations/rules/Pacifist2/fr.pug b/client/src/translations/rules/Pacifist2/fr.pug new file mode 100644 index 00000000..e69de29b diff --git a/client/src/variants/Benedict.js b/client/src/variants/Benedict.js index 4ca5292d..039aaeaf 100644 --- a/client/src/variants/Benedict.js +++ b/client/src/variants/Benedict.js @@ -135,13 +135,10 @@ export class BenedictRules extends ChessRules { // Stop at the first move found atLeastOneMove() { const color = this.turn; - const oppCol = V.GetOppCol(color); 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 && this.getColor(i, j) != oppCol) { - const moves = this.getPotentialMovesFrom([i, j]); - if (moves.length > 0) - return true; + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { + if (this.getPotentialMovesFrom([i, j]).length > 0) return true; } } } diff --git a/client/src/variants/Pacifist1.js b/client/src/variants/Pacifist1.js new file mode 100644 index 00000000..234b02a8 --- /dev/null +++ b/client/src/variants/Pacifist1.js @@ -0,0 +1,263 @@ +import { ChessRules } from "@/base_rules"; + +export class Pacifist1Rules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { canCapture: false } + ); + } + + static get HasEnpassant() { + return false; + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + let kingsCount = 0; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (['K','k'].includes(row[i])) kingsCount++; + if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i]); + if (isNaN(num)) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + // Both kings should be on board. May be of the same color. + if (kingsCount != 2) return false; + return true; + } + + scanKings(fen) { + // Kings may be swapped, so they are not tracked (no kingPos) + this.INIT_COL_KING = { w: -1, b: -1 }; + const fenRows = V.ParseFen(fen).position.split("/"); + const startRow = { 'w': V.size.x - 1, 'b': 0 }; + 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": + this.INIT_COL_KING["b"] = k; + break; + case "K": + this.INIT_COL_KING["w"] = k; + break; + default: { + const num = parseInt(fenRows[i].charAt(j)); + if (!isNaN(num)) k += num - 1; + } + } + k++; + } + } + } + + // Sum white pieces attacking a square, and remove black pieces count. + sumAttacks([x, y]) { + const getSign = (color) => { + return (color == 'w' ? 1 : -1); + }; + let res = 0; + // Knights: + V.steps[V.KNIGHT].forEach(s => { + const [i, j] = [x + s[0], y + s[1]]; + if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KNIGHT) + res += getSign(this.getColor(i, j)); + }); + // Kings: + V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => { + const [i, j] = [x + s[0], y + s[1]]; + if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KING) + res += getSign(this.getColor(i, j)); + }); + // Pawns: + for (let c of ['w', 'b']) { + for (let shift of [-1, 1]) { + const sign = getSign(c); + const [i, j] = [x + sign, y + shift]; + if ( + V.OnBoard(i, j) && + this.getPiece(i, j) == V.PAWN && + this.getColor(i, j) == c + ) { + res += sign; + } + } + } + // Other pieces (sliders): + V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => { + let [i, j] = [x + s[0], y + s[1]]; + let compatible = [V.QUEEN]; + compatible.push(s[0] == 0 || s[1] == 0 ? V.ROOK : V.BISHOP); + let firstCol = undefined; + while (V.OnBoard(i, j)) { + if (this.board[i][j] != V.EMPTY) { + if (!(compatible.includes(this.getPiece(i, j)))) break; + const colIJ = this.getColor(i, j); + if (!firstCol) firstCol = colIJ; + if (colIJ == firstCol) res += getSign(colIJ); + else break; + } + i += s[0]; + j += s[1]; + } + }); + return res; + } + + getPotentialMovesFrom([x, y]) { + let moves = super.getPotentialMovesFrom([x, y]); + const color = this.turn; + const oppCol = V.GetOppCol(color); + if (this.getPiece(x, y) == V.PAWN) { + // Pawns cannot move 2 squares if the intermediate is overly persuaded + moves = moves.filter(m => { + if (Math.abs(m.end.x - m.start.x) == 2) { + const [i, j] = [(m.start.x + m.end.x) / 2, y]; + const persuasion = this.sumAttacks([i, j]); + return ( + color == 'w' && persuasion >= 0 || + color == 'b' && persuasion <= 0 + ); + } + return true; + }); + } + // Potentially flipped (opp) pieces + let targets = []; + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol) + targets.push([i, j]); + } + } + moves.forEach(m => { + // Start persuading other pieces: loop until nothing changes + V.PlayOnBoard(this.board, m); + while (true) { + let persuaded = []; + targets.forEach(t => { + if (this.getColor(t[0], t[1]) == oppCol) { + const sqAttacks = this.sumAttacks([t[0], t[1]]); + if ( + (oppCol == 'w' && sqAttacks < 0) || + (oppCol == 'b' && sqAttacks > 0) + ) { + persuaded.push(t); + } + } + }); + if (persuaded.length == 0) break; + persuaded.forEach(p => { + this.board[p[0]][p[1]] = color + this.getPiece(p[0], p[1]); + }); + } + V.UndoOnBoard(this.board, m); + // Reset pieces colors + adjust move (flipped pieces) + targets.forEach(t => { + if (this.getColor(t[0], t[1]) == color) { + const piece = this.getPiece(t[0], t[1]); + m.appear.push({ x: t[0], y: t[1], c: color, p: piece }); + m.vanish.push({ x: t[0], y: t[1], c: oppCol, p: piece }); + this.board[t[0]][t[1]] = oppCol + piece; + } + }); + }); + return moves; + } + + getSlideNJumpMoves([x, y], steps, oneStep) { + let moves = []; + outerLoop: for (let loop = 0; loop < steps.length; loop++) { + const step = steps[loop]; + let i = x + step[0]; + let j = y + step[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + if (oneStep) continue outerLoop; + i += step[0]; + j += step[1]; + } + // No captures + } + return moves; + } + + underCheck(color) { + // Find the king(s) and determine if it (both) is persuaded. + // If yes, "under check" + let kingPos = []; + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if (this.getPiece(i, j) == V.KING && this.getColor(i, j) == color) + kingPos.push([i, j]); + } + } + return kingPos.every(kp => { + const persuasion = this.sumAttacks(kp); + return ( + (color == 'w' && persuasion < 0) || + (color == 'b' && persuasion > 0) + ); + }); + } + + filterValid(moves) { + const fmoves = super.filterValid(moves); + const color = this.turn; + // If the king isn't here, only moves persuading a king are valid + const kingHere = this.board.some(b => + b.some(cell => cell[0] == color && cell[1] == V.KING) + ); + if (!kingHere) { + return ( + fmoves.filter(m => m.appear.some(a => a.c == color && a.p == V.KING)) + ); + } + return fmoves; + } + + getCheckSquares() { + // There are not really "checks": just color change + return []; + } + + getCurrentScore() { + const color = this.turn; + // TODO: if no king of turn color, and no move to get one, then it's lost + // otherwise 1/2 if no moves, or "*" + const kingHere = this.board.some(b => + b.some(cell => cell[0] == color && cell[1] == V.KING) + ); + if (kingHere) { + if (this.atLeastOneMove()) return "*"; + return "1/2"; + } + // No king was found: try to convert one + const moves = this.getAllValidMoves(); + return ( + moves.some(m => m.appear.some(a => a.c == color && a.p == V.KING)) + ? "*" + : (color == 'w' ? "0-1" : "1-0") + ); + } + + postPlay(move) { + this.updateCastleFlags(move, move.vanish[0].p); + } + + postUndo() {} + + static get SEARCH_DEPTH() { + return 1; + } +}; diff --git a/client/src/variants/Pacifist2.js b/client/src/variants/Pacifist2.js new file mode 100644 index 00000000..e8128024 --- /dev/null +++ b/client/src/variants/Pacifist2.js @@ -0,0 +1,58 @@ +import { Pacifist1Rules } from "@/variants/Pacifist1"; + +export class Pacifist2Rules extends Pacifist1Rules { + // Sum values of white pieces attacking a square, + // and remove (sum of) black pieces values. + sumAttacks([x, y]) { + const getSign = (color) => { + return (color == 'w' ? 1 : -1); + }; + let res = 0; + // Knights: + V.steps[V.KNIGHT].forEach(s => { + const [i, j] = [x + s[0], y + s[1]]; + if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KNIGHT) + res += getSign(this.getColor(i, j)) * V.VALUES[V.KNIGHT]; + }); + // Kings: + V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => { + const [i, j] = [x + s[0], y + s[1]]; + if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KING) + res += getSign(this.getColor(i, j)) * V.VALUES[V.KING]; + }); + // Pawns: + for (let c of ['w', 'b']) { + for (let shift of [-1, 1]) { + const sign = getSign(c); + const [i, j] = [x + sign, y + shift]; + if ( + V.OnBoard(i, j) && + this.getPiece(i, j) == V.PAWN && + this.getColor(i, j) == c + ) { + res += sign * V.VALUES[V.PAWN]; + } + } + } + // Other pieces (sliders): + V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => { + let [i, j] = [x + s[0], y + s[1]]; + let compatible = [V.QUEEN]; + compatible.push(s[0] == 0 || s[1] == 0 ? V.ROOK : V.BISHOP); + let firstCol = undefined; + while (V.OnBoard(i, j)) { + if (this.board[i][j] != V.EMPTY) { + const pieceIJ = this.getPiece(i, j); + if (!(compatible.includes(pieceIJ))) break; + const colIJ = this.getColor(i, j); + if (!firstCol) firstCol = colIJ; + if (colIJ == firstCol) res += getSign(colIJ) * V.VALUES[pieceIJ]; + else break; + } + i += s[0]; + j += s[1]; + } + }); + return res; + } +}; -- 2.44.0