From: Benjamin Auder Date: Fri, 15 Jan 2021 00:17:22 +0000 (+0100) Subject: Add Shogun Chess X-Git-Url: https://git.auder.net/doc/html/app_dev.php/scripts/img/cross.svg?a=commitdiff_plain;h=73fbcfc85f7ff7d5bfc1aadd0f9fd392f71f7861;p=vchess.git Add Shogun Chess --- diff --git a/client/public/images/pieces/Shogun/SOURCE b/client/public/images/pieces/Shogun/SOURCE new file mode 100644 index 00000000..9de37884 --- /dev/null +++ b/client/public/images/pieces/Shogun/SOURCE @@ -0,0 +1,2 @@ +https://github.com/gbtami/pychess-variants/tree/master/static/images/pieces/shogun/blue +By Couch Tomato diff --git a/client/public/images/pieces/Shogun/bM.svg b/client/public/images/pieces/Shogun/bM.svg new file mode 100644 index 00000000..e2221950 --- /dev/null +++ b/client/public/images/pieces/Shogun/bM.svg @@ -0,0 +1,174 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/ba.svg b/client/public/images/pieces/Shogun/ba.svg new file mode 100644 index 00000000..75c0b043 --- /dev/null +++ b/client/public/images/pieces/Shogun/ba.svg @@ -0,0 +1,366 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/bb.svg b/client/public/images/pieces/Shogun/bb.svg new file mode 120000 index 00000000..dfaa0688 --- /dev/null +++ b/client/public/images/pieces/Shogun/bb.svg @@ -0,0 +1 @@ +../bb.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/bc.svg b/client/public/images/pieces/Shogun/bc.svg new file mode 100644 index 00000000..95043bc7 --- /dev/null +++ b/client/public/images/pieces/Shogun/bc.svg @@ -0,0 +1,199 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/bf.svg b/client/public/images/pieces/Shogun/bf.svg new file mode 100644 index 00000000..7e05d5b5 --- /dev/null +++ b/client/public/images/pieces/Shogun/bf.svg @@ -0,0 +1,109 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/bg.svg b/client/public/images/pieces/Shogun/bg.svg new file mode 100644 index 00000000..0d0678a9 --- /dev/null +++ b/client/public/images/pieces/Shogun/bg.svg @@ -0,0 +1,255 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/bk.svg b/client/public/images/pieces/Shogun/bk.svg new file mode 100644 index 00000000..73c588ec --- /dev/null +++ b/client/public/images/pieces/Shogun/bk.svg @@ -0,0 +1,110 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/bn.svg b/client/public/images/pieces/Shogun/bn.svg new file mode 120000 index 00000000..63b24583 --- /dev/null +++ b/client/public/images/pieces/Shogun/bn.svg @@ -0,0 +1 @@ +../bn.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/bp.svg b/client/public/images/pieces/Shogun/bp.svg new file mode 120000 index 00000000..b3603243 --- /dev/null +++ b/client/public/images/pieces/Shogun/bp.svg @@ -0,0 +1 @@ +../bp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/bq.svg b/client/public/images/pieces/Shogun/bq.svg new file mode 100644 index 00000000..44678e90 --- /dev/null +++ b/client/public/images/pieces/Shogun/bq.svg @@ -0,0 +1,122 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/br.svg b/client/public/images/pieces/Shogun/br.svg new file mode 120000 index 00000000..f7661a29 --- /dev/null +++ b/client/public/images/pieces/Shogun/br.svg @@ -0,0 +1 @@ +../br.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/wa.svg b/client/public/images/pieces/Shogun/wa.svg new file mode 100644 index 00000000..b72430ce --- /dev/null +++ b/client/public/images/pieces/Shogun/wa.svg @@ -0,0 +1,269 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wb.svg b/client/public/images/pieces/Shogun/wb.svg new file mode 120000 index 00000000..5197a2e7 --- /dev/null +++ b/client/public/images/pieces/Shogun/wb.svg @@ -0,0 +1 @@ +../wb.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/wc.svg b/client/public/images/pieces/Shogun/wc.svg new file mode 100644 index 00000000..030c22e6 --- /dev/null +++ b/client/public/images/pieces/Shogun/wc.svg @@ -0,0 +1,162 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wf.svg b/client/public/images/pieces/Shogun/wf.svg new file mode 100644 index 00000000..1cfb24f7 --- /dev/null +++ b/client/public/images/pieces/Shogun/wf.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wg.svg b/client/public/images/pieces/Shogun/wg.svg new file mode 100644 index 00000000..90a0cf43 --- /dev/null +++ b/client/public/images/pieces/Shogun/wg.svg @@ -0,0 +1,189 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wk.svg b/client/public/images/pieces/Shogun/wk.svg new file mode 100644 index 00000000..345954ea --- /dev/null +++ b/client/public/images/pieces/Shogun/wk.svg @@ -0,0 +1,86 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wm.svg b/client/public/images/pieces/Shogun/wm.svg new file mode 100644 index 00000000..e73a47ce --- /dev/null +++ b/client/public/images/pieces/Shogun/wm.svg @@ -0,0 +1,173 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wn.svg b/client/public/images/pieces/Shogun/wn.svg new file mode 120000 index 00000000..6051b0ef --- /dev/null +++ b/client/public/images/pieces/Shogun/wn.svg @@ -0,0 +1 @@ +../wn.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/wp.svg b/client/public/images/pieces/Shogun/wp.svg new file mode 120000 index 00000000..3a15545c --- /dev/null +++ b/client/public/images/pieces/Shogun/wp.svg @@ -0,0 +1 @@ +../wp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Shogun/wq.svg b/client/public/images/pieces/Shogun/wq.svg new file mode 100644 index 00000000..b5343f3c --- /dev/null +++ b/client/public/images/pieces/Shogun/wq.svg @@ -0,0 +1,86 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Shogun/wr.svg b/client/public/images/pieces/Shogun/wr.svg new file mode 120000 index 00000000..6a8519b3 --- /dev/null +++ b/client/public/images/pieces/Shogun/wr.svg @@ -0,0 +1 @@ +../wr.svg \ No newline at end of file diff --git a/client/public/variants/Shogun/Promotions.png b/client/public/variants/Shogun/Promotions.png new file mode 100644 index 00000000..0bcab410 --- /dev/null +++ b/client/public/variants/Shogun/Promotions.png @@ -0,0 +1 @@ +#$# git-fat d23fcfb7e4255ae6acf730983c4a88997da42e51 102584 diff --git a/client/src/translations/en.js b/client/src/translations/en.js index bf58f253..9608ef18 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -215,6 +215,7 @@ export const translations = { "Friendly pieces": "Friendly pieces", "In the shadow": "In the shadow", "Interweaved colorbound teams": "Interweaved colorbound teams", + "General's Chess": "General's Chess", "Geometric Chess": "Geometric Chess", "Get strong at self-mate": "Get strong at self-mate", "Give three checks": "Give three checks", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index f9a9d99a..980f6f92 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -215,6 +215,7 @@ export const translations = { "Friendly pieces": "Piezas amistosas", "In the shadow": "En la sombra", "Interweaved colorbound teams": "Equipos unicolores entrelazados", + "General's Chess": "Ajedrez de los Generales", "Geometric Chess": "Ajedrez geométrico", "Get strong at self-mate": "Progreso en mates asistidos", "Give three checks": "Dar tres jaques", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 101436d6..5b704056 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -215,6 +215,7 @@ export const translations = { "Friendly pieces": "Pièces amies", "In the shadow": "Dans l'ombre", "Interweaved colorbound teams": "Équipes unicolores entremêlées", + "General's Chess": "Échecs des Généraux", "Geometric Chess": "Échecs géométriques", "Get strong at self-mate": "Progressez en mats aidés", "Give three checks": "Donnez trois échecs", diff --git a/client/src/translations/rules/Shogun/en.pug b/client/src/translations/rules/Shogun/en.pug index 21203baa..92ff37f3 100644 --- a/client/src/translations/rules/Shogun/en.pug +++ b/client/src/translations/rules/Shogun/en.pug @@ -1 +1,41 @@ -p.boxed TODO +p.boxed + | Each piece has a promoted form. Captured pieces can be dropped later. + +p + | Shogun Chess is a chess variant designed in 2019-2020 by Couch Tomato. + | It is explained, and playable + a(href="https://www.pychess.org/variant/shogun") on pychess-variants + | . + +figure + img.img-center(src="/variants/Shogun/Promotions.png") + figcaption.text-center. + Pieces and their promoted forms, with movements summary. + +h3 General rules + +ul + li. + The three farthest ranks are the promotion zone. + Every starting piece except the king and queen may promote by moving into + the promotion zone or moving from within the promotion zone. + li. + However, only one of each major piece (queen, mortar, archbishop, or + general) can be out on each side at a time. + li. + Captured pieces may be dropped back into the board as your own piece. + Pieces can be dropped anywhere within the first 5 ranks. + li. + When promoted pieces are captured, they revert to their unpromoted side. + This is the only way that a queen becomes a duchess. + +p Note: pawns cannot promote if capturing en passant. + +h3 (New) Pieces + +ul + li Archbishop (A) = Bishop + Knight. + li Mortar (M) = Rook + Knight. + li General (G) = King (non-royal) + Knight. + li Captain (C) = King (non-royal). + li Duchess (F) = Ferz (one step diagonally). diff --git a/client/src/translations/rules/Shogun/es.pug b/client/src/translations/rules/Shogun/es.pug index 21203baa..bac69b5f 100644 --- a/client/src/translations/rules/Shogun/es.pug +++ b/client/src/translations/rules/Shogun/es.pug @@ -1 +1,43 @@ -p.boxed TODO +p.boxed + | Cada pieza tiene una forma promocionada. + | Las piezas capturadas se pueden lanzar en paracaídas más tarde. + +p + | Shogun Chess es una variante desarrollada en 2019-2020 por Couch Tomato. + | Se explican y se puede jugar + a(href="https://www.pychess.org/variant/shogun") en pychess-variants + | . + +figure + img.img-center(src="/variants/Shogun/Promotions.png") + figcaption.text-center. + Piezas y sus formas promocionadas, con resumen de movimientos. + +h3 Reglas generales + +ul + li. + Las últimas tres filas definen el área de promoción. + Cada pieza inicial, excepto el rey y la reina, puede ser + promovido moviéndose hacia o desde el área de promoción. + li. + Sin embargo, solo una de las piezas principales (dama, mortero, arzobispo + o general) pueden estar presentes en el tablero para cada lado. + li. + Las piezas capturadas cambian de color y luego se pueden lanzar en + paracaídas en el tablero, en cualquier lugar de las primeras 5 filas. + li. + Cuando se captura una pieza promocionada, vuelve a su forma no + promocionada. Esta es la única manera de + que una dama se convierta en duquesa. + +p Nota: los peones no pueden promocionarse mediante una captura en passant. + +h3 (Nuevas) Piezas + +ul + li Arzobispo (A) = Alfil + Caballo. + li Mortero (M) = Torre + Caballo. + li General (G) = Rey (no real) + Caballo. + li Capitán (C) = Rey (no real). + li Duquesa (F) = Ferz (un paso en diagonal). diff --git a/client/src/translations/rules/Shogun/fr.pug b/client/src/translations/rules/Shogun/fr.pug index 21203baa..c989f725 100644 --- a/client/src/translations/rules/Shogun/fr.pug +++ b/client/src/translations/rules/Shogun/fr.pug @@ -1 +1,42 @@ -p.boxed TODO +p.boxed + | Chaque pièce a une forme promue. + | Les pièces capturées peuvent être parachutées plus tard. + +p + | Les Échecs Shogun sont une variante élaborée en 2019-2020 par Couch Tomato. + | Ils sont expliqué, et jouables + a(href="https://www.pychess.org/variant/shogun") sur pychess-variants + | . + +figure + img.img-center(src="/variants/Shogun/Promotions.png") + figcaption.text-center. + Pièces et leurs formes promues, avec résumé des déplacements. + +h3 Règles générales + +ul + li. + Les trois dernières rangées définissent la zone de promotion. + Chaque pièce initiale à l'exception du roi et de la dame peuvent être + promue en se déplaçant vers ou depuis la zone de promotion. + li. + Cependant, une seule des pièces majeures (dame, mortier, achevêque ou + général) peut être présente sur l'échiquier pour chaque camp. + li. + Les pièces capturées changent de couleur puis peuvent être parachutées + sur l'échiquier, n'importe où sur les 5 premières rangées. + li. + Quand une pièce promue est capturée, elle reprend sa forme non promue. + C'est la seule façon pour une dame de devenir une duchesse. + +p Note : les pions ne peuvent pas se promouvoir via une capture en passant. + +h3 (Nouvelles) Pièces + +ul + li Archevêque (A) = Fou + Cavalier. + li Mortier (M) = Tour + Cavalier. + li Général (G) = Roi (non royal) + Cavalier. + li Capitaine (C) = Roi (non royal). + li Duchesse (F) = Ferz (un pas en diagonale). diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 55c778d6..af4c5505 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", + "Shogun", "Teleport" ] ul diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 7a3dea34..b6386175 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -329,6 +329,7 @@ p. "Madhouse", "Rampage", "Recycle", + "Shogun", "Teleport" ] ul diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 9673a53e..9ce01601 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -328,6 +328,7 @@ p. "Madhouse", "Rampage", "Recycle", + "Shogun", "Teleport" ] ul diff --git a/client/src/variants/Shogun.js b/client/src/variants/Shogun.js new file mode 100644 index 00000000..1a20d329 --- /dev/null +++ b/client/src/variants/Shogun.js @@ -0,0 +1,436 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; + +export class ShogunRules extends ChessRules { + + static get CAPTAIN() { + return 'c'; + } + static get GENERAL() { + return 'g'; + } + static get ARCHBISHOP() { + return 'a'; + } + static get MORTAR() { + return 'm'; + } + static get DUCHESS() { + return 'f'; + } + + static get PIECES() { + return ( + ChessRules.PIECES + .concat([V.CAPTAIN, V.GENERAL, V.ARCHBISHOP, V.MORTAR, V.DUCHESS]) + ); + } + + getPpath(b) { + return "Shogun/" + b; + } + + getReservePpath(index, color) { + return "Shogun/" + color + V.RESERVE_PIECES[index]; + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // 5) Check reserves + if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/)) + return false; + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { reserve: fenParts[5] } + ); + } + + static GenRandInitFen(randomness) { + return ChessRules.GenRandInitFen(randomness) + " 0000000000"; + } + + getFen() { + return super.getFen() + " " + this.getReserveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getReserveFen(); + } + + getReserveFen() { + let counts = new Array(10); + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]]; + counts[5 + i] = this.reserve["b"][V.RESERVE_PIECES[i]]; + } + return counts.join(""); + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + // Also init reserves (used by the interface to show landable pieces) + const reserve = + V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); + this.reserve = { + w: { + [V.PAWN]: reserve[0], + [V.ROOK]: reserve[1], + [V.KNIGHT]: reserve[2], + [V.BISHOP]: reserve[3], + [V.DUCHESS]: reserve[4] + }, + b: { + [V.PAWN]: reserve[5], + [V.ROOK]: reserve[6], + [V.KNIGHT]: reserve[7], + [V.BISHOP]: reserve[8], + [V.DUCHESS]: reserve[9] + } + }; + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; + return this.board[i][j].charAt(1); + } + + // Ordering on reserve pieces + static get RESERVE_PIECES() { + return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.DUCHESS]; + } + + getReserveMoves([x, y]) { + const color = this.turn; + const iZone = (color == 'w' ? [3, 4, 5, 6, 7] : [0, 1, 2, 3, 4]); + const p = V.RESERVE_PIECES[y]; + if (this.reserve[color][p] == 0) return []; + let moves = []; + for (let i of iZone) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] == V.EMPTY) { + let mv = new Move({ + appear: [ + new PiPo({ + x: i, + y: j, + c: color, + p: p + }) + ], + vanish: [], + start: { x: x, y: y }, //a bit artificial... + end: { x: i, y: j } + }); + moves.push(mv); + } + } + } + return moves; + } + + static get MapUnpromoted() { + return { + f: 'q', + r: 'm', + b: 'a', + p: 'c', + n: 'g' + }; + } + + getPotentialMovesFrom([x, y]) { + if (x >= V.size.x) + // Reserves, outside of board: x == sizeX(+1) + return this.getReserveMoves([x, y]); + // Standard moves + const piece = this.getPiece(x, y); + const sq = [x, y]; + if (piece == V.KING) return super.getPotentialKingMoves(sq); + let moves = []; + switch (piece) { + // Unpromoted + case V.PAWN: + return this.getPotentialPawnMoves(sq); + case V.ROOK: + moves = super.getPotentialRookMoves(sq); + break; + case V.KNIGHT: + moves = super.getPotentialKnightMoves(sq); + break; + case V.BISHOP: + moves = super.getPotentialBishopMoves(sq); + break; + case V.DUCHESS: + moves = this.getPotentialDuchessMoves(sq); + break; + } + if ([V.ROOK, V.KNIGHT, V.BISHOP, V.DUCHESS].includes(piece)) { + let extraMoves = []; + // Check that no promoted form is already on board: + const promotedForm = V.MapUnpromoted[piece]; + const c = this.turn; + if ( + this.board.some(b => + b.some(cell => + cell[0] == c && cell[1] == promotedForm) + ) + ) { + return moves; + } + const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]); + moves.forEach(m => { + if ( + promotionZone.includes(m.end.x) || + promotionZone.includes(m.start.x) + ) { + let newMove = JSON.parse(JSON.stringify(m)); + newMove.appear[0].p = promotedForm; + extraMoves.push(newMove); + } + }); + return moves.concat(extraMoves); + } + switch (piece) { + // Promoted + case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); + case V.MORTAR: return this.getPotentialMortarMoves(sq); + case V.GENERAL: return this.getPotentialGeneralMoves(sq); + case V.ARCHBISHOP: return this.getPotentialArchbishopMoves(sq); + case V.QUEEN: return super.getPotentialQueenMoves(sq); + } + return []; //never reached + } + + getPotentialPawnMoves([x, y]) { + // NOTE: apply promotion freely, but not on en-passant + const c = this.turn; + const oppCol = V.GetOppCol(c); + const forward = (c == 'w' ? -1 : 1); + const initialRank = (c == 'w' ? 6 : 1); + let moves = []; + // Pawn push + let [i, j] = [x + forward, y]; + if (this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + if (x == initialRank && this.board[i + forward][j] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [i + forward, j])); + } + // Captures + for (let shiftY of [-1, 1]) { + [i, j] = [x + forward, y + shiftY]; + if ( + V.OnBoard(i, j) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol + ) { + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + let extraMoves = []; + const promotionZone = (this.turn == 'w' ? [1, 2] : [5, 6]); + const lastRank = (c == 'w' ? 0 : 7); + moves.forEach(m => { + if (m.end.x == lastRank) + // Force promotion + m.appear[0].p = V.CAPTAIN; + else if (promotionZone.includes(m.end.x)) { + let newMove = JSON.parse(JSON.stringify(m)); + newMove.appear[0].p = V.CAPTAIN; + extraMoves.push(newMove); + } + }); + return ( + moves.concat(extraMoves) + .concat(super.getEnpassantCaptures([x, y], forward)) + ); + } + + getPotentialDuchessMoves(sq) { + return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"); + } + + getPotentialCaptainMoves(sq) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return super.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + getPotentialMortarMoves(sq) { + return ( + super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) + .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) + ); + } + + getPotentialGeneralMoves(sq) { + const steps = + V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); + return super.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + getPotentialArchbishopMoves(sq) { + return ( + super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) + .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) + ); + } + + isAttacked(sq, color) { + return ( + super.isAttacked(sq, color) || + this.isAttackedByDuchess(sq, color) || + this.isAttackedByCaptain(sq, color) || + this.isAttackedByMortar(sq, color) || + this.isAttackedByGeneral(sq, color) || + this.isAttackedByArchbishop(sq, color) + ); + } + + isAttackedByDuchess(sq, color) { + return ( + super.isAttackedBySlideNJump( + sq, color, V.DUCHESS, V.steps[V.BISHOP], "oneStep") + ); + } + + isAttackedByCaptain(sq, color) { + const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]); + return ( + super.isAttackedBySlideNJump(sq, color, V.DUCHESS, steps, "oneStep") + ); + } + + isAttackedByMortar(sq, color) { + return ( + super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) || + super.isAttackedBySlideNJump( + sq, color, V.MORTAR, V.steps[V.KNIGHT], "oneStep") + ); + } + + isAttackedByGeneral(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") + ); + } + + isAttackedByArchbishop(sq, color) { + return ( + super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP]) + || + super.isAttackedBySlideNJump( + sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep") + ); + } + + 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]) + ); + } + return this.filterValid(moves); + } + + atLeastOneMove() { + if (!super.atLeastOneMove()) { + // 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]) + ); + if (moves.length > 0) return true; + } + return false; + } + return true; + } + + static get MapPromoted() { + return { + q: 'f', + m: 'r', + a: 'b', + c: 'p', + g: 'n' + }; + } + + getUnpromotedForm(piece) { + if (Object.keys(V.MapPromoted).includes(piece)) + return V.MapPromoted[piece]; + return piece; + } + + postPlay(move) { + super.postPlay(move); + // Skip castle: + if (move.vanish.length == 2 && move.appear.length == 2) return; + const color = move.appear[0].c; + if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; + else if (move.vanish.length == 2) + this.reserve[color][this.getUnpromotedForm(move.vanish[1].p)]++; + } + + postUndo(move) { + super.postUndo(move); + if (move.vanish.length == 2 && move.appear.length == 2) return; + const color = this.turn; + if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; + else if (move.vanish.length == 2) + this.reserve[color][this.getUnpromotedForm(move.vanish[1].p)]--; + } + + static get SEARCH_DEPTH() { + return 2; + } + + static get VALUES() { + return ( + Object.assign( + { + c: 4, + g: 5, + a: 7, + m: 7, + f: 2 + }, + ChessRules.VALUES + ) + ); + } + + evalPosition() { + let evaluation = super.evalPosition(); + // Add reserves: + 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; + } + + getNotation(move) { + if (move.vanish.length > 0) return super.getNotation(move); + // Rebirth: + const piece = + move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; + return piece + "@" + V.CoordsToSquare(move.end); + } + +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 3a5e5171..b2740327 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -129,6 +129,7 @@ insert or ignore into Variants (name, description) values ('Shako', 'Non-conformism and utopia'), ('Shatranj', 'Ancient rules'), ('Shogi', 'Japanese Chess'), + ('Shogun', 'General''s Chess'), ('Sittuyin', 'Burmese Chess'), ('Spartan', 'Spartan versus Persians'), ('Squatter1', 'Squat last rank (v1)'),