--- /dev/null
+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.
--- /dev/null
+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..
--- /dev/null
+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.
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';
return 'l';
}
+ static IsGoodFlags(flags) {
+ // Only black can castle
+ return !!flags.match(/^[a-z]{2,2}$/);
+ }
+
static get PIECES() {
return (
ChessRules.PIECES
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);
);
}
- // 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"
);
}
[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);
}
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];
}
new PiPo({
x: i,
y: j,
- c: color,
+ c: 'w',
p: p
})
],
// 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);
// Unpromoted
case V.PAWN:
moves = super.getPotentialPawnMoves(sq);
+ break;
case V.MONK:
moves = this.getPotentialMonkMoves(sq);
break;
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) {
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') ||
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:
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
)
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;
}