From: Benjamin Auder Date: Thu, 18 Mar 2021 22:08:11 +0000 (+0100) Subject: Finalize Shinobi Chess X-Git-Url: https://git.auder.net/doc/html/assets/current/git-logo.png?a=commitdiff_plain;h=d5af4af2ac7d86bed9166916eb1610736647df0a;p=vchess.git Finalize Shinobi Chess --- diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index f20e7da6..799185e4 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -299,7 +299,8 @@ export default { (!myReserveTop && !!this.vr.reserve[playingColor]) ); // Center reserves, assuming same number of pieces for each side: - const nbReservePieces = myReservePiecesArray.length; + const nbReservePieces = + Math.max(myReservePiecesArray.length, oppReservePiecesArray.length); const marginLeft = ((100 - nbReservePieces * (100 / reserveSquareNb)) / 2) + "%"; if (hasReserveTop) { diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 555606b5..eb0c0bbd 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -167,6 +167,7 @@ export const translations = { "64 pieces on the board": "64 pieces on the board", "A Clockwork Orange": "A Clockwork Orange", "A pawns cloud": "A pawns cloud", + "A story of invasion": "A story of invasion", "A quantum story": "A quantum story", "A wizard in the corner": "A wizard in the corner", "Absorb powers": "Absorb powers", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index d64bda16..051978c6 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -167,6 +167,7 @@ export const translations = { "64 pieces on the board": "64 piezas en el tablero", "A Clockwork Orange": "Naranja Mecánica", "A pawns cloud": "Une nube de peones", + "A story of invasion": "Una historia de invasión", "A quantum story": "Una historia cuántica", "A wizard in the corner": "Un mago en la esquina", "Absorb powers": "Absorber poderes", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index c516cb21..5e92206d 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -167,6 +167,7 @@ export const translations = { "64 pieces on the board": "64 pièces sur l'échiquier", "A Clockwork Orange": "Orange Mécanique", "A pawns cloud": "Une nuée de pions", + "A story of invasion": "Une histoire d'invasion", "A quantum story": "Une histoire quantique", "A wizard in the corner": "Un sorcier dans le coin", "Absorb powers": "Absorber les pouvoirs", diff --git a/client/src/translations/rules/Shinobi/en.pug b/client/src/translations/rules/Shinobi/en.pug new file mode 100644 index 00000000..eac9a6f2 --- /dev/null +++ b/client/src/translations/rules/Shinobi/en.pug @@ -0,0 +1,68 @@ +p.boxed. + Different armies. + Most white pieces start "in hand", and promote when reaching 6th rank. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/3CK3: + figcaption Deterministic initial position. + +p. + Shinobi Chess is a chess variant designed in 2021 by Couch Tomato. + The chess army (the "Kingdom", black) has invaded the land of the Sakura + Clan ("pink"). While initially unprepared and having very few pieces + available, the Clan is very resourceful and can instantly recruit and + summon allies to defend at a minute's notice! + +p. + The Clan starts with almost all of its pieces in hand, and can drop them + on its side of the board (first 4 ranks) in lieu of moving a piece. + +p + | In addition to checkmate, + | one can win by bringing the king into the final rank ("campmate"). + br + | Stalemate and repetition are both losses. + +h3 New pieces + +p. + There are five new units unique to the Clan: Ninja, Samurai, Lances, + (Wooden) Horses, and Monks. Captains are a new piece available to both + sides, but only the Clan starts with one on the board. + Ninja, Samurai, and Captains do not promote (see below). + +p. + The Clan's king is called a Kage (K) and has a different symbol, but the + change is purely aesthetic and thematic: it behaves like an orthodox King. + +ul + li Captain (C) – Moves like a King. Pawns promote to a Captain. + li Ninja (J) = Knight + Bishop. + li Samurai (S) = Knight + Rook. + li Monk (M) – One-step bishop. + li. + Horse (H) – Moves only forward two squares, + and then one square to the side. + li Lance (L) – Moves only forward, like a unidirectional rook. + +p. + Clan minor pieces are considerably weak. However, with a little + resourcefulness, they can trap the stronger Kingdom pieces + and even win the battle. + +h3 Promotion + +p. + Pawns promote into Captains when reaching the 6th rank. + All minor Clan pieces also promote upon reaching the 6th rank (or beyond): +ul + li Monk into Bishop. + li Horse into Knight. + li Lance into Rook. + +p + | Clan Rooks and Bishops look different, but the changes are purely + | aesthetic. Note that the symbol on the Bishop is a + a(href="https://handwiki.org/wiki/Sauwastika") sauwastika + | , not a swastika. diff --git a/client/src/translations/rules/Shinobi/es.pug b/client/src/translations/rules/Shinobi/es.pug new file mode 100644 index 00000000..e3d66696 --- /dev/null +++ b/client/src/translations/rules/Shinobi/es.pug @@ -0,0 +1,71 @@ +p.boxed. + Diferentes ejércitos. La mayoría de las piezas blancas comienzan + "en la mano", y son promovidas cuando llegan a la 6ta fila. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/3CK3: + figcaption Posición inicial determinista. + +p. + Ajedrez Shinobi es una variación desarrollada por Couch Tomato en 2021. + El ejército ortodoxo (el "Reino", negras) invadió las tierras del Clan + Sakura ("rosa"). Aunque inicialmente no estaba preparado y con poca + recursos, el Clan no debe ser subestimado porque puede reclutar + nuevos aliados sin demora! + +p. + El Clan comienza con casi todas sus piezas en la mano y puede las + paracaídas en su lado del tablero (primeras 4 filas) en lugar de + mover una pieza. + +p + | Además del jaque mate, + | puedes ganar llevando al rey a la última fila ("mate de campamento"). + br + | Tanto el empate como la repetición pierden. + +h3 Piezas nuevas + +p. + Cinco nuevas unidades son exclusivas del Clan: Ninja, Samurai, Lances, + Jamelgos y Monjes. Los Capitanes son una nueva pieza disponible en ambos + lados, pero solo el Clan comienza con uno en el tablero. + Los Ninja, Samuráis y Capitanes no son promovidos (ver más abajo). + +p. + El rey del Clan se llama Kage (K) y tiene un símbolo diferente, pero el + cambio es puramente estético y temático: se comporta como un rey ortodoxo. + +ul + li Capitán (C) – Muévese como un rey. Los peones ascienden a Capitán. + li Ninja (J) = Caballo + Alfil. + li Samurái (S) = Caballo + Torre. + li Monje (M) – Alfil limitado a una casilla. + li Jamelgo (H) – Avanza dos espacios, luego una casilla al costado. + li. + Lanza (L) – Se mueve hacia adelante, + como una torre de un solo sentido. + +p. + Las piezas menores del Clan son considerablemente débiles. Sin embargo, con + un poco astutos, pueden atrapar piezas (más fuerte) del Reino, + e incluso ganar la victoria. + +h3 Promoción + +p. + Los peones son promovidos a Capitanes cuando alcanzan la sexta fila. + Todas las piezas menores del Clan también se promocionan una vez en + la sexta fila (o más): +ul + li Monje → Alfil. + li Jamelgo → Caballo. + li Lanza → Torre. + +p + | Las Torres y Alfiles del Clan son inusuales en apariencia, pero los + | cambios son puramente estéticos. + | Tenga en cuenta que el símbolo del alfil es un + a(href="https://handwiki.org/wiki/Sauwastika") sauwastika + | , no una swastika.. diff --git a/client/src/translations/rules/Shinobi/fr.pug b/client/src/translations/rules/Shinobi/fr.pug new file mode 100644 index 00000000..d1ba9506 --- /dev/null +++ b/client/src/translations/rules/Shinobi/fr.pug @@ -0,0 +1,75 @@ +p.boxed. + Armées différentes. La plupart des pièces blanches démarrent "en main", + et sont promues quand elles atteignent la 6eme rangée. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/3CK3: + figcaption Position initiale déterministe. + +p. + Les Échecs Shinobi sont une variante élaborée par Couch Tomato en 2021. + L'armée orthodoxe (le "Royaume", noirs) a envahi les terres du Clan Sakura + ("rose"). Bien qu'initialement non préparé et ne disposant que de peu de + ressources, le Clan ne doit pas être sous-estimé car il peut recruter de + nouveaux alliés sans délais ! + +p. + Le Clan démarre avec presque toutes ses pièces en main, et peut les + parachuter de son côté de l'échiquier (4 premières rangées) au lieu de + déplacer une pièce. + +p + | En plus du mat, + | on peut gagner en amenant le roi sur la dernière rangée ("mat de camp"). + br + | Le pat et la répétition de coups perdent tous les deux. + +h3 Nouvelles pièces + +p. + Cinq nouvelles unités sont uniques au Clan : les Ninja, Samurai, Lances, + Chevaux (de Bois), et Moines. Les Capitaines sont une nouvelle pièce + disponible des deux côtés, mais seul le Clan démarre avec un sur l'échiquier. + Les Ninja, Samourai et Capitaines ne sont pas promus (voir ci-dessous). + +p. + Le roi du Clan est appelé Kage (K), et a un symbole différent, mais le + changement est purement esthétique et thématique : il se comporte comme + un roi orthodoxe. + +ul + li. + Capitaine (C) – Se déplace comme un Roi. + Les pions se promeuvent en Capitaine. + li Ninja (J) = Cavalier + Fou. + li Samurai (S) = Cavalier + Tour. + li Moine (M) – Fou limité à une case. + li. + Cheval (H) – Se déplace vers l'avant de deux cases, + puis d'une case sur le coté. + li. + Lance (L) – Se déplace vers l'avant, + comme une tour unidirectionnelle. + +p. + Les pièces mineures du Clan sont considérablement faibles. Cependant, avec + un peu de ruse, elles peuvent piéger les pièces du Royaume – pourtant + plus fortes –et même remporter la victoire. + +h3 Promotion + +p. + Les pions sont promus en Capitaines quand ils atteignent la 6eme rangée. + Toutes les pièces mineures du Clan se promeuvent également une fois sur + la 6eme rangée (ou plus loin) : +ul + li Moine → Fou. + li Cheval → Cavalier. + li Lance → Tour. + +p + | Les Tours et Fous du Clan sont d'allures inhabituelles, mais les + changements sont purement esthétique. Notez que le symbole sur le Fou est un + a(href="https://handwiki.org/wiki/Sauwastika") sauwastika + | , pas un swastika. diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 6432c80e..d6c8f5ba 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -320,6 +320,7 @@ p Pieces can be drop on the board, either immediately or later in the game. "Madhouse", "Rampage", "Recycle", + "Shinobi", "Shogun", "Teleport" ] diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index ebf9d74c..32a83c8b 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -329,6 +329,7 @@ p. "Madhouse", "Rampage", "Recycle", + "Shinobi", "Shogun", "Teleport" ] diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 7eb09e90..f8fcb299 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -328,6 +328,7 @@ p. "Madhouse", "Rampage", "Recycle", + "Shinobi", "Shogun", "Teleport" ] diff --git a/client/src/variants/Shinobi.js b/client/src/variants/Shinobi.js index 5eed0d39..8f7f11f0 100644 --- a/client/src/variants/Shinobi.js +++ b/client/src/variants/Shinobi.js @@ -1,15 +1,10 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -import { ArrayFun } from "@/utils/array"; export class ShinobiRules extends ChessRules { - /* Would be unused: - static get PawnSpecs() { - return Object.assign( - { promotions: [V.PAWN] }, - ChessRules.PawnSpecs - ); - } */ + static get LoseOnRepetition() { + return true; + } static get CAPTAIN() { return 'c'; @@ -30,6 +25,11 @@ export class ShinobiRules extends ChessRules { return 'l'; } + static IsGoodFlags(flags) { + // Only black can castle + return !!flags.match(/^[a-z]{2,2}$/); + } + static get PIECES() { return ( ChessRules.PIECES @@ -46,6 +46,16 @@ export class ShinobiRules extends ChessRules { return "Shinobi/" + color + V.RESERVE_PIECES[index]; } + getFlagsFen() { + return this.castleFlags['b'].map(V.CoordToColumn).join(""); + } + + setFlags(fenflags) { + this.castleFlags = { 'b': [-1, -1] }; + for (let i = 0; i < 2; i++) + this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i)); + } + static IsGoodFen(fen) { if (!ChessRules.IsGoodFen(fen)) return false; const fenParsed = V.ParseFen(fen); @@ -63,13 +73,12 @@ export class ShinobiRules extends ChessRules { ); } - // In hand initially: another captain, a ninja + a samurai, - // and 2 x monk, horse, lance (TODO) + // In hand initially: captain, ninja, samurai + 2 x monk, horse, lance. static GenRandInitFen(randomness) { const baseFen = ChessRules.GenRandInitFen(Math.min(randomness, 1)); return ( - baseFen.substr(0, 33) + "3CK3 " + - "w 0 " + baseFen.substr(38, 2) + " - 111222" + baseFen.substr(0, 35) + "3CK3 " + + "w 0 " + baseFen.substr(48, 2) + " - 111222" ); } @@ -96,14 +105,14 @@ export class ShinobiRules extends ChessRules { [V.NINJA]: reserve[1], [V.SAMURAI]: reserve[2], [V.MONK]: reserve[3], - [V.HORSE]: reserve[4] + [V.HORSE]: reserve[4], [V.LANCE]: reserve[5] } }; } getColor(i, j) { - if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + if (i >= V.size.x) return 'w'; return this.board[i][j].charAt(0); } @@ -112,7 +121,6 @@ export class ShinobiRules extends ChessRules { return this.board[i][j].charAt(1); } - // Ordering on reserve pieces static get RESERVE_PIECES() { return [V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE]; } @@ -130,7 +138,7 @@ export class ShinobiRules extends ChessRules { new PiPo({ x: i, y: j, - c: color, + c: 'w', p: p }) ], @@ -163,7 +171,8 @@ export class ShinobiRules extends ChessRules { // Standard moves const piece = this.getPiece(x, y); const sq = [x, y]; - if (ChessRules.includes(piece)) return super.getPotentialMovesFrom(sq); + if ([V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(piece)) + return super.getPotentialMovesFrom(sq); switch (piece) { case V.KING: return super.getPotentialKingMoves(sq); case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); @@ -175,6 +184,7 @@ export class ShinobiRules extends ChessRules { // Unpromoted case V.PAWN: moves = super.getPotentialPawnMoves(sq); + break; case V.MONK: moves = this.getPotentialMonkMoves(sq); break; @@ -188,42 +198,50 @@ export class ShinobiRules extends ChessRules { const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]); const promotedForm = V.MapUnpromoted[piece]; moves.forEach(m => { - if (promotionZone.includes(m.end.x)) move.appear[0].p = promotedForm; + if (promotionZone.includes(m.end.x)) m.appear[0].p = promotedForm; }); return moves; } - getPotentialCaptainMoves([x, y]) { - } - - // TODO: adapt... - getPotentialNinjaMoves(sq) { - return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"); + getPotentialKingMoves([x, y]) { + if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]); + // Clan doesn't castle: + return super.getSlideNJumpMoves( + [x, y], + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); } - getPotentialSamuraiMoves(sq) { + getPotentialCaptainMoves(sq) { const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); return super.getSlideNJumpMoves(sq, steps, "oneStep"); } - getPotentialMonkMoves(sq) { + getPotentialNinjaMoves(sq) { + return ( + super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) + .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) + ); + } + + getPotentialSamuraiMoves(sq) { return ( super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) ); } + getPotentialMonkMoves(sq) { + return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"); + } + getPotentialHorseMoves(sq) { - const steps = - V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); - return super.getSlideNJumpMoves(sq, steps, "oneStep"); + return super.getSlideNJumpMoves(sq, [ [-2, 1], [-2, -1] ], "oneStep"); } getPotentialLanceMoves(sq) { - return ( - super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) - .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) - ); + return super.getSlideNJumpMoves(sq, [ [-1, 0] ]); } isAttacked(sq, color) { @@ -233,8 +251,8 @@ export class ShinobiRules extends ChessRules { return ( super.isAttackedByKing(sq, 'w') || this.isAttackedByCaptain(sq, 'w') || - this.isAttackedByNinja(sq, 'w') - this.isAttackedBySamurai(sq, 'w') + this.isAttackedByNinja(sq, 'w') || + this.isAttackedBySamurai(sq, 'w') || this.isAttackedByMonk(sq, 'w') || this.isAttackedByHorse(sq, 'w') || this.isAttackedByLance(sq, 'w') || @@ -247,78 +265,91 @@ export class ShinobiRules extends ChessRules { isAttackedByCaptain(sq, color) { const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]); return ( - super.isAttackedBySlideNJump(sq, color, V.DUCHESS, steps, "oneStep") + super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, "oneStep") ); } isAttackedByNinja(sq, color) { return ( + super.isAttackedBySlideNJump(sq, color, V.NINJA, V.steps[V.BISHOP]) || super.isAttackedBySlideNJump( - sq, color, V.DUCHESS, V.steps[V.BISHOP], "oneStep") + sq, color, V.NINJA, V.steps[V.KNIGHT], "oneStep") ); } isAttackedBySamurai(sq, color) { return ( - super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) || + super.isAttackedBySlideNJump(sq, color, V.SAMURAI, V.steps[V.ROOK]) || super.isAttackedBySlideNJump( - sq, color, V.MORTAR, V.steps[V.KNIGHT], "oneStep") + sq, color, V.SAMURAI, V.steps[V.KNIGHT], "oneStep") ); } isAttackedByMonk(sq, color) { - const steps = - V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); return ( - super.isAttackedBySlideNJump(sq, color, V.GENERAL, steps, "oneStep") + super.isAttackedBySlideNJump( + sq, color, V.MONK, V.steps[V.BISHOP], "oneStep") ); } isAttackedByHorse(sq, color) { return ( - super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP]) - || super.isAttackedBySlideNJump( - sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep") + sq, color, V.HORSE, [ [2, 1], [2, -1] ], "oneStep") ); } isAttackedByLance(sq, color) { - return ( - super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP]) - || - super.isAttackedBySlideNJump( - sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep") - ); + return super.isAttackedBySlideNJump(sq, color, V.LANCE, [ [1, 0] ]); } getAllValidMoves() { let moves = super.getAllPotentialMoves(); - const color = this.turn; - for (let i = 0; i < V.RESERVE_PIECES.length; i++) { - moves = moves.concat( - this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) - ); + if (this.turn == 'w') { + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + moves = moves.concat( + this.getReserveMoves([V.size.x, i]) + ); + } } return this.filterValid(moves); } atLeastOneMove() { - if (!super.atLeastOneMove()) { + if (super.atLeastOneMove()) return true; + if (this.turn == 'w') { // Search one reserve move for (let i = 0; i < V.RESERVE_PIECES.length; i++) { let moves = this.filterValid( - this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) + this.getReserveMoves([V.size.x, i]) ); if (moves.length > 0) return true; } - return false; } - return true; + return false; + } + + updateCastleFlags(move, piece) { + // Only black can castle: + const firstRank = 0; + if (piece == V.KING && move.appear[0].c == 'b') + this.castleFlags['b'] = [8, 8]; + else if ( + move.start.x == firstRank && + this.castleFlags['b'].includes(move.start.y) + ) { + const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1); + this.castleFlags['b'][flagIdx] = 8; + } + else if ( + move.end.x == firstRank && + this.castleFlags['b'].includes(move.end.y) + ) { + const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1); + this.castleFlags['b'][flagIdx] = 8; + } } - // TODO: only black can castle (see Orda) - postPlay(move) { super.postPlay(move); // Skip castle: @@ -334,21 +365,30 @@ export class ShinobiRules extends ChessRules { if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; } - /* static get SEARCH_DEPTH() { return 2; - } */ + } + + getCurrentScore() { + const color = this.turn; + const nodrawResult = (color == "w" ? "0-1" : "1-0"); + const oppLastRank = (color == 'w' ? 7 : 0); + if (this.kingPos[V.GetOppCol(color)][0] == oppLastRank) + return nodrawResult; + if (this.atLeastOneMove()) return "*"; + return nodrawResult; + } - // TODO: static get VALUES() { return ( Object.assign( { c: 4, - g: 5, - a: 7, - m: 7, - f: 2 + j: 7, + s: 8, + m: 2, + h: 2, + l: 2 }, ChessRules.VALUES ) @@ -357,11 +397,10 @@ export class ShinobiRules extends ChessRules { evalPosition() { let evaluation = super.evalPosition(); - // Add reserves: + // Add reserve: for (let i = 0; i < V.RESERVE_PIECES.length; i++) { const p = V.RESERVE_PIECES[i]; evaluation += this.reserve["w"][p] * V.VALUES[p]; - evaluation -= this.reserve["b"][p] * V.VALUES[p]; } return evaluation; } diff --git a/server/db/populate.sql b/server/db/populate.sql index 56347720..7872515f 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -146,6 +146,7 @@ insert or ignore into Variants (name, description) values ('Selfabsorb', 'Fusion pieces (v2)'), ('Shako', 'Non-conformism and utopia'), ('Shatranj', 'Ancient rules'), + ('Shinobi', 'A story of invasion'), ('Shogi', 'Japanese Chess'), ('Shogun', 'General''s Chess'), ('Sittuyin', 'Burmese Chess'),