From b90120e062404b8c656d4f38e66727df8a7e1c5a Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 12 Jan 2021 17:18:29 +0100 Subject: [PATCH] Add Spartan Chess --- TODO | 1 - client/public/images/pieces/Spartan/bc.svg | 1 + client/public/images/pieces/Spartan/bg.svg | 1 + client/public/images/pieces/Spartan/bl.svg | 1 + client/public/images/pieces/Spartan/bw.svg | 1 + client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + client/src/translations/rules/Spartan/en.pug | 74 +++- client/src/translations/rules/Spartan/es.pug | 78 +++- client/src/translations/rules/Spartan/fr.pug | 78 +++- client/src/translations/variants/en.pug | 1 + client/src/translations/variants/es.pug | 1 + client/src/translations/variants/fr.pug | 1 + client/src/variants/Colorbound.js | 15 +- client/src/variants/Spartan.js | 401 +++++++++++++++++++ server/db/populate.sql | 1 + 17 files changed, 644 insertions(+), 14 deletions(-) create mode 120000 client/public/images/pieces/Spartan/bc.svg create mode 120000 client/public/images/pieces/Spartan/bg.svg create mode 120000 client/public/images/pieces/Spartan/bl.svg create mode 120000 client/public/images/pieces/Spartan/bw.svg create mode 100644 client/src/variants/Spartan.js diff --git a/TODO b/TODO index 0bd4ab0d..2f72e33e 100644 --- a/TODO +++ b/TODO @@ -8,7 +8,6 @@ http://history.chess.free.fr/rollerball.htm https://www.pychess.org/variant/shogun https://www.chessvariants.com/other.dir/fugue.html -https://www.chessvariants.com/rules/spartan-chess https://www.chessvariants.com/incinf.dir/bario.html https://www.chessvariants.com/index/listcomments.php?order=DESC&itemid=Bario https://www.bario-chess-checkers-chessphotography-spaceart.de/ diff --git a/client/public/images/pieces/Spartan/bc.svg b/client/public/images/pieces/Spartan/bc.svg new file mode 120000 index 00000000..f7661a29 --- /dev/null +++ b/client/public/images/pieces/Spartan/bc.svg @@ -0,0 +1 @@ +../br.svg \ No newline at end of file diff --git a/client/public/images/pieces/Spartan/bg.svg b/client/public/images/pieces/Spartan/bg.svg new file mode 120000 index 00000000..a41e488c --- /dev/null +++ b/client/public/images/pieces/Spartan/bg.svg @@ -0,0 +1 @@ +../Schess/be.svg \ No newline at end of file diff --git a/client/public/images/pieces/Spartan/bl.svg b/client/public/images/pieces/Spartan/bl.svg new file mode 120000 index 00000000..dfaa0688 --- /dev/null +++ b/client/public/images/pieces/Spartan/bl.svg @@ -0,0 +1 @@ +../bb.svg \ No newline at end of file diff --git a/client/public/images/pieces/Spartan/bw.svg b/client/public/images/pieces/Spartan/bw.svg new file mode 120000 index 00000000..3a672eb8 --- /dev/null +++ b/client/public/images/pieces/Spartan/bw.svg @@ -0,0 +1 @@ +../Schess/bh.svg \ No newline at end of file diff --git a/client/src/translations/en.js b/client/src/translations/en.js index bfd3f2ea..6dcbab1f 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -289,6 +289,7 @@ export const translations = { "Shared pieces (v2)": "Shared pieces (v2)", "Shogi 5 x 5": "Shogi 5 x 5", "Shoot pieces": "Shoot pieces", + "Spartan versus Persians": "Spartan versus Persians", "Squares disappear": "Squares disappear", "Squat last rank": "Squat last rank", "Standard rules": "Standard rules", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 03be9bbf..6f6181f4 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -289,6 +289,7 @@ export const translations = { "Shared pieces (v2)": "Piezas compartidas (v2)", "Shogi 5 x 5": "Shogi 5 x 5", "Shoot pieces": "Tirar de las piezas", + "Spartan versus Persians": "Espartanos contra Persas", "Squares disappear": "Las casillas desaparecen", "Squat last rank": "Ocupa la última fila", "Standard rules": "Reglas estandar", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 22f333af..e4e3c34c 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -289,6 +289,7 @@ export const translations = { "Shared pieces (v2)": "Pièces partagées (v2)", "Shogi 5 x 5": "Shogi 5 x 5", "Shoot pieces": "Tirez sur les pièces", + "Spartan versus Persians": "Spartiates contre Perses", "Squares disappear": "Les cases disparaissent", "Squat last rank": "Occupez la dernière rangée", "Standard rules": "Règles usuelles", diff --git a/client/src/translations/rules/Spartan/en.pug b/client/src/translations/rules/Spartan/en.pug index 21203baa..25205d15 100644 --- a/client/src/translations/rules/Spartan/en.pug +++ b/client/src/translations/rules/Spartan/en.pug @@ -1 +1,73 @@ -p.boxed TODO +p.boxed + | Two different armies: Persians versus Spartians. + +p. + The Black side represents the Spartans and the White the Persians. + The Persians pawns and pieces follow the rules of orthodox chess. + The Spartans have two Kings and with the exception of their Kings, + every Spartan playing piece moves in a non-orthodox fashion. + +figure.diagram-container + .diagram + | fen:lgkcckwl/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR: + figcaption Initial deterministic position. + +p Approximative correspondances +table + tr + th White + th Black + tr + td Rook + td Captain (on d8 and e8) + tr + td Bishop + td Lieutenant (on a8 and h8) + tr + td King + td Kings (on c8 and f8) + tr + td Pawn + td Hoplite +p. + Queen and Knights don't have counterparts on black side, but, + the General on b8 and the Warlord on g8 are strong pieces. + +h3 Movements of black pieces + +ul + li. + Hoplite (Pawn) = Berolina pawn, with an extra option on its initial rank: + it can jump over an obstacle for the 2-squares advance. + li Captain = Rook limited to 2 squares maximum. + li. + Lieutenant = Bishop limted to two squares maximum. + It can also move horizontally by one square, without capturing. + li General = Rook + King + li Warlord = Bishop + Knight + +h3 Some details + +p The game essentially follow usual chess rules, with a few exceptions: +ul + li. + A black (Spartian) King can go or remain under check, and even be captured, + as long as another king is still on the board. + li. + White (Persians) win either if only one checkmated black king remains, + or, if they can checkmate both kings at the same time. + li. + Pawns promote in a piece of their army. Hoplite can additionally + be promoted into King if only one remains. + +p No en passant captures, and Black cannot castle. + +h3 More information + +p + | See the + a(href="https://www.chessvariants.com/rules/spartan-chess") + | chessvariants page + | . + +p Inventor: Steven Streetman (2010) diff --git a/client/src/translations/rules/Spartan/es.pug b/client/src/translations/rules/Spartan/es.pug index 21203baa..bb3b41da 100644 --- a/client/src/translations/rules/Spartan/es.pug +++ b/client/src/translations/rules/Spartan/es.pug @@ -1 +1,77 @@ -p.boxed TODO +p.boxed + | Dos ejércitos diferentes: los persas contra los espartanos. + +p. + El campo negro representa a los espartanos y el blanco a los persas. + Los peones y piezas persas siguen las reglas del ajedrez ortodoxo. + Los espartanos tienen dos Reyes y, a excepción de este último, + cada unidad espartana se mueve de una manera poco ortodoxa. + +figure.diagram-container + .diagram + | fen:lgkcckwl/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR: + figcaption Posición inicial determinista. + +p Coincidencias aproximadas +table + tr + th Blancas + th Negras + tr + td Torre + td Capitán (en d8 y e8) + tr + td Alfil + td Teniente (en a8 y h8) + tr + td Rey + td Reyes (en c8 y f8) + tr + td Peón + td Hoplita +p. + Dama y Caballo no tienen contrapartes del lado negro, pero, + el general en b8 y el "Señor de la Guerra" (Warlord) en g8 + son dos piezas bastante fuertes. + +h3 Movimientos de piezas negras + +ul + li. + Hoplite (Peón) = peón Berolina, con una opción adicional de su + fila inicial: puede saltar un obstáculo para + el avance de dos casillas. + li Capitán = Torre limitada a dos casillas como máximo. + li. + Teniente = Loco limitado a dos casillas como máximo. + También puede moverse horizontalmente un cuadrado, sin capturar. + li General = Torre + Rey + li Warlord = Alfil + Caballo + +h3 Algunos detalles + +p. + El juego sigue esencialmente las reglas habituales del ajedrez, + con algunas excepciones: +ul + li. + Un Rey negro (espartano) puede ir o permanecer en jaque, e incluso ser + capturado, siempre que quede otro rey en el tablero. + li. + Las blancas (persas) ganan si hacen jaque mate al único rey negro + restante, o si pueden matar a ambos reyes al mismo tiempo. + li. + Los peones se promocionan a una pieza de su ejército. Los hoplitas pueden + además, ser promovidos a Rey si solo hay uno presente. + +p No hay capturas en passant, y las negras no pueden enrocar. + +h3 Más información + +p + | Ver la + a(href="https://www.chessvariants.com/rules/spartan-chess") + | página chessvariants + | . + +p Inventor: Steven Streetman (2010) diff --git a/client/src/translations/rules/Spartan/fr.pug b/client/src/translations/rules/Spartan/fr.pug index 21203baa..399a1858 100644 --- a/client/src/translations/rules/Spartan/fr.pug +++ b/client/src/translations/rules/Spartan/fr.pug @@ -1 +1,77 @@ -p.boxed TODO +p.boxed + | Deux différentes armées : les Perses contre les Spartiates. + +p. + Le camp noir représente les spartiates, et le blanc les perses. + Les pions et pièces perses suivent les règles des échecs orthodoxes. + Les spartiates ont deux Rois et à l'exception de ces derniers, + chaque unité spartiate se déplace de façon non-orthodoxe. + +figure.diagram-container + .diagram + | fen:lgkcckwl/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR: + figcaption Position initiale déterministe. + +p Correspondances approximatives +table + tr + th Blancs + th Noirs + tr + td Tour + td Capitaine (sur d8 et e8) + tr + td Fou + td Lieutenant (sur a8 et h8) + tr + td Roi + td Rois (sur c8 et f8) + tr + td Pion + td Hoplite +p. + Dame et Cavalier n'ont pas de contreparties côté noir, mais, + le Général en b8 ainsi que le "Chef de Guerre" (Warlord) en g8 + sont deux pièces plutôt fortes. + +h3 Déplacements des pièces noires + +ul + li. + Hoplite (Pion) = pion Berolina, avec une option supplémentaire depuis sa + rangée initiale : il peut sauter par dessus un obstacle pour + l'avancée de deux cases. + li Capitaine = Tour limitée à deux cases maximum. + li. + Lieutenant = Fou limité à deux cases maximum. + Il peut aussi se mouvoir horizontalement d'une case, sans capturer. + li Général = Tour + Roi + li Warlord = Fou + Cavalier + +h3 Quelques détails + +p. + La partie suit essentiellement les règles usuelles des échecs, + à quelques exceptions près : +ul + li. + Un Roi noir (spartiate) peut aller ou rester en échec, et même être + capturé, du moment qu'il reste un autre roi sur l'échiquier. + li. + Les blancs (perses) gagnent s'ils matent le seul roi noir restant, ou, + s'ils peuvent mater les deux rois en même temps. + li. + Les pions se promeuvent en une pièce de leur armée. Les hoplites peuvent + en outre être promus en Roi si un seul est présent. + +p Pas de prises en passant, et les noirs ne peuvent roquer. + +h3 Plus d'information + +p + | Voir la + a(href="https://www.chessvariants.com/rules/spartan-chess") + | page chessvariants + | . + +p Inventeur : Steven Streetman (2010) diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 6e7d42ad..5421bfa3 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -419,6 +419,7 @@ p. "Parachute", "Relayup", "Screen", + "Spartan", "Squatter", "Takenmake", "Titan", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 2b767403..143d6212 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -430,6 +430,7 @@ p. "Parachute", "Relayup", "Screen", + "Spartan", "Squatter", "Takenmake", "Titan", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 431a725b..27af18d7 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -429,6 +429,7 @@ p. "Parachute", "Relayup", "Screen", + "Spartan", "Squatter", "Takenmake", "Titan", diff --git a/client/src/variants/Colorbound.js b/client/src/variants/Colorbound.js index 85f81ef3..90e54dc4 100644 --- a/client/src/variants/Colorbound.js +++ b/client/src/variants/Colorbound.js @@ -63,16 +63,11 @@ export class ColorboundRules extends ChessRules { 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]); + 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 []; } diff --git a/client/src/variants/Spartan.js b/client/src/variants/Spartan.js new file mode 100644 index 00000000..5b0ccecb --- /dev/null +++ b/client/src/variants/Spartan.js @@ -0,0 +1,401 @@ +import { ChessRules } from "@/base_rules"; + +export class SpartanRules extends ChessRules { + + static get HasEnpassant() { + return false; + } + + static IsGoodFlags(flags) { + // Only white can castle + return !!flags.match(/^[a-z]{2,2}$/); + } + + getPpath(b) { + if ([V.LIEUTENANT, V.GENERAL, V.CAPTAIN, V.WARLORD].includes(b[1])) + return "Spartan/" + b; + return b; + } + + static GenRandInitFen(randomness) { + if (randomness == 0) + return "lgkcckwl/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ah"; + + // Mapping white --> black (first knight --> general; TODO): + const piecesMap = { + 'r': 'c', + 'n': 'w', + 'b': 'l', + 'q': 'k', + 'k': 'k', + 'g': 'g' + }; + + const baseFen = ChessRules.GenRandInitFen(randomness).replace('n', 'g'); + return ( + baseFen.substr(0, 8).split('').map(p => piecesMap[p]).join('') + + baseFen.substr(8) + ); + } + + getFlagsFen() { + return this.castleFlags['w'].map(V.CoordToColumn).join(""); + } + + setFlags(fenflags) { + this.castleFlags = { 'w': [-1, -1] }; + for (let i = 0; i < 2; i++) + this.castleFlags['w'][i] = V.ColumnToCoord(fenflags.charAt(i)); + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + let kings = { "k": 0, "K": 0 }; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (['K','k'].includes(row[i])) kings[row[i]]++; + if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num) || num <= 0) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + // Both kings should be on board. One for white, 1 or 2 for black. + if (kings['K'] != 1 || ![1, 2].includes(kings['k'])) return false; + return true; + } + + scanKings(fen) { + // Scan white king only: + this.kingPos = { w: [-1, -1] }; + const fenRows = V.ParseFen(fen).position.split("/"); + for (let i = 0; i < fenRows.length; i++) { + let k = 0; + for (let j = 0; j < fenRows[i].length; j++) { + switch (fenRows[i].charAt(j)) { + case "K": + this.kingPos["w"] = [i, k]; + break; + default: { + const num = parseInt(fenRows[i].charAt(j), 10); + if (!isNaN(num)) k += num - 1; + } + } + k++; + } + } + } + + static get LIEUTENANT() { + return 'l'; + } + static get GENERAL() { + return 'g'; + } + static get CAPTAIN() { + return 'c'; + } + static get WARLORD() { + return 'w'; + } + + static get PIECES() { + return ( + ChessRules.PIECES.concat([V.LIEUTENANT, V.GENERAL, V.CAPTAIN, V.WARLORD]) + ); + } + + getPotentialMovesFrom([x, y]) { + if (this.getColor(x, y) == 'w') return super.getPotentialMovesFrom([x, y]); + switch (this.getPiece(x, y)) { + case V.PAWN: { + const kings = this.getKingsPos(); + const moves = this.getPotentialHopliteMoves([x, y]); + if (kings.length == 1) return moves; + return moves.filter(m => m.appear[0].p != V.KING); + } + case V.KING: return this.getPotentialSpartanKingMoves([x, y]); + case V.LIEUTENANT: return this.getPotentialLieutenantMoves([x, y]); + case V.GENERAL: return this.getPotentialGeneralMoves([x, y]); + case V.CAPTAIN: return this.getPotentialCaptainMoves([x, y]); + case V.WARLORD: return this.getPotentialWarlordMoves([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] + ] + } + ); + } + + getPotentialSpartanKingMoves(sq) { + // No castle: + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return super.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + getPotentialHopliteMoves([x, y]) { + // Berolina pawn, with initial jumping option + let moves = []; + if (x == 6) { + const finalPieces = + [V.LIEUTENANT, V.GENERAL, V.CAPTAIN, V.KING, V.WARLORD]; + for (let shiftY of [-1, 0, 1]) { + const [i, j] = [7, y + shiftY]; + if ( + V.OnBoard(i, j) && + ( + (shiftY != 0 && this.board[i][j] == V.EMPTY) || + (shiftY == 0 && this.getColor(i, j) == 'w') + ) + ) { + for (let p of finalPieces) + moves.push(this.getBasicMove([x, y], [i, j], { c: 'b', p: p })); + } + } + } + else { + for (let shiftY of [-1, 0, 1]) { + const [i, j] = [x + 1, y + shiftY]; + if ( + V.OnBoard(i, j) && + ( + (shiftY != 0 && this.board[i][j] == V.EMPTY) || + (shiftY == 0 && this.getColor(i, j) == 'w') + ) + ) { + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + // Add initial 2 squares jumps: + if (x == 1) { + for (let shiftY of [-2, 2]) { + const [i, j] = [3, y + shiftY]; + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + } + return moves; + } + + getPotentialLieutenantMoves([x, y]) { + let moves = []; + for (let shiftY of [-1, 1]) { + const [i, j] = [x, y + shiftY]; + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [i, j])); + } + const steps = V.steps[V.BISHOP].concat(V.steps['a']); + Array.prototype.push.apply( + moves, + super.getSlideNJumpMoves([x, y], steps, "oneStep") + ); + return moves; + } + + getPotentialCaptainMoves([x, y]) { + const steps = V.steps[V.ROOK].concat(V.steps['d']); + return super.getSlideNJumpMoves([x, y], steps, "oneStep") + } + + getPotentialGeneralMoves([x, y]) { + return ( + super.getSlideNJumpMoves([x, y], V.steps[V.BISHOP], "oneStep") + .concat(super.getSlideNJumpMoves([x, y], V.steps[V.ROOK])) + ); + } + + getPotentialWarlordMoves([x, y]) { + return ( + super.getSlideNJumpMoves([x, y], V.steps[V.KNIGHT], "oneStep") + .concat(super.getSlideNJumpMoves([x, y], V.steps[V.BISHOP])) + ); + } + + isAttacked(sq, color) { + if (color == 'w') return super.isAttacked(sq, 'w'); + return ( + this.isAttackedByHoplite(sq) || + super.isAttackedByKing(sq, 'b') || + this.isAttackedByLieutenant(sq) || + this.isAttackedByGeneral(sq) || + this.isAttackedByCaptain(sq) || + this.isAttackedByWarlord(sq) + ); + } + + isAttackedByHoplite(sq) { + return super.isAttackedBySlideNJump(sq, 'b', V.PAWN, [[-1,0]], "oneStep"); + } + + isAttackedByLieutenant(sq) { + const steps = V.steps[V.BISHOP].concat(V.steps['a']); + return ( + super.isAttackedBySlideNJump(sq, 'b', V.LIEUTENANT, steps, "oneStep") + ); + } + + isAttackedByCaptain(sq) { + const steps = V.steps[V.ROOK].concat(V.steps['d']); + return super.isAttackedBySlideNJump(sq, 'b', V.CAPTAIN, steps, "oneStep"); + } + + isAttackedByGeneral(sq) { + return ( + super.isAttackedBySlideNJump( + sq, 'b', V.GENERAL, V.steps[V.BISHOP], "oneStep") || + super.isAttackedBySlideNJump(sq, 'b', V.GENERAL, V.steps[V.ROOK]) + ); + } + + isAttackedByWarlord(sq) { + return ( + super.isAttackedBySlideNJump(sq, 'b', V.GENERAL, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep") || + super.isAttackedBySlideNJump(sq, 'b', V.GENERAL, V.steps[V.ROOK]) + ); + } + + updateCastleFlags(move, piece) { + // Only white can castle: + const firstRank = 7; + if (piece == V.KING && move.appear[0].c == 'w') + this.castleFlags['w'] = [8, 8]; + else if ( + move.start.x == firstRank && + this.castleFlags['w'].includes(move.start.y) + ) { + const flagIdx = (move.start.y == this.castleFlags['w'][0] ? 0 : 1); + this.castleFlags['w'][flagIdx] = 8; + } + else if ( + move.end.x == firstRank && + this.castleFlags['w'].includes(move.end.y) + ) { + const flagIdx = (move.end.y == this.castleFlags['w'][0] ? 0 : 1); + this.castleFlags['w'][flagIdx] = 8; + } + } + + postPlay(move) { + if (move.vanish[0].c == 'w') super.postPlay(move); + } + + postUndo(move) { + if (move.vanish[0].c == 'w') super.postUndo(move); + } + + getKingsPos() { + let kings = []; + 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) == 'b' && + this.getPiece(i, j) == V.KING + ) { + kings.push({ x: i, y: j }); + } + } + } + return kings; + } + + getCheckSquares() { + if (this.turn == 'w') return super.getCheckSquares(); + const kings = this.getKingsPos(); + let res = []; + for (let i of [0, 1]) { + if ( + kings.length >= i+1 && + super.isAttacked([kings[i].x, kings[i].y], 'w') + ) { + res.push([kings[i].x, kings[i].y]); + } + } + return res; + } + + filterValid(moves) { + if (moves.length == 0) return []; + const color = moves[0].vanish[0].c; + if (color == 'w') return super.filterValid(moves); + // Black moves: check if both kings under attack + // If yes, moves must remove at least one attack. + const kings = this.getKingsPos(); + return moves.filter(m => { + this.play(m); + let attacks = 0; + for (let k of kings) { + const curKingPos = + this.board[k.x][k.y] == V.EMPTY + ? [m.appear[0].x, m.appear[0].y] //king moved + : [k.x, k.y] + if (super.isAttacked(curKingPos, 'w')) attacks++; + else break; //no need to check further + } + this.undo(m); + return ( + (kings.length == 2 && attacks <= 1) || + (kings.length == 1 && attacks == 0) + ); + }); + } + + getCurrentScore() { + if (this.turn == 'w') return super.getCurrentScore(); + if (super.atLeastOneMove()) return "*"; + // Count kings on board + const kings = this.getKingsPos(); + if ( + super.isAttacked([kings[0].x, kings[0].y], 'w') || + (kings.length == 2 && super.isAttacked([kings[1].x, kings[1].y], 'w')) + ) { + return "1-0"; + } + return "1/2"; //stalemate + } + + static get VALUES() { + return Object.assign( + {}, + ChessRules.VALUES, + { + l: 3, + g: 7, + c: 3, + w: 7 + } + ); + } + + static get SEARCH_DEPTH() { + return 2; + } + +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 043c8a8f..2510ace0 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -128,6 +128,7 @@ insert or ignore into Variants (name, description) values ('Shatranj', 'Ancient rules'), ('Shogi', 'Japanese Chess'), ('Sittuyin', 'Burmese Chess'), + ('Spartan', 'Spartan versus Persians'), ('Squatter', 'Squat last rank'), ('Suicide', 'Lose all pieces'), ('Suction', 'Attract opposite king'), -- 2.44.0