1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
3 export class ShinobiRules
extends ChessRules
{
5 static get LoseOnRepetition() {
15 static get SAMURAI() {
28 static IsGoodFlags(flags
) {
29 // Only black can castle
30 return !!flags
.match(/^[a-z]{2,2}$/);
36 .concat([V
.CAPTAIN
, V
.NINJA
, V
.SAMURAI
, V
.MONK
, V
.HORSE
, V
.LANCE
])
41 if (b
[0] == 'b' && b
[1] != 'c') return b
;
42 return "Shinobi/" + b
;
45 getReservePpath(index
, color
) {
46 return "Shinobi/" + color
+ V
.RESERVE_PIECES
[index
];
50 return this.castleFlags
['b'].map(V
.CoordToColumn
).join("");
54 this.castleFlags
= { 'b': [-1, -1] };
55 for (let i
= 0; i
< 2; i
++)
56 this.castleFlags
['b'][i
] = V
.ColumnToCoord(fenflags
.charAt(i
));
59 static IsGoodFen(fen
) {
60 if (!ChessRules
.IsGoodFen(fen
)) return false;
61 const fenParsed
= V
.ParseFen(fen
);
63 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{5,5}$/))
68 static ParseFen(fen
) {
69 const fenParts
= fen
.split(" ");
71 ChessRules
.ParseFen(fen
),
72 { reserve: fenParts
[5] }
76 // In hand initially: ninja, samurai + 2 x monk, horse, lance.
77 static GenRandInitFen(randomness
) {
78 const baseFen
= ChessRules
.GenRandInitFen(Math
.min(randomness
, 1));
80 baseFen
.substr(0, 35) + "3CK3 " +
81 "w 0 " + baseFen
.substr(48, 2) + " - 11222"
86 return super.getFen() + " " + this.getReserveFen();
90 return super.getFenForRepeat() + "_" + this.getReserveFen();
94 // TODO: can simplify other drops variants with this code:
95 return Object
.values(this.reserve
['w']).join("");
98 setOtherVariables(fen
) {
99 super.setOtherVariables(fen
);
101 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
104 [V
.NINJA
]: reserve
[0],
105 [V
.SAMURAI
]: reserve
[1],
106 [V
.MONK
]: reserve
[2],
107 [V
.HORSE
]: reserve
[3],
108 [V
.LANCE
]: reserve
[4]
114 if (i
>= V
.size
.x
) return 'w';
115 return this.board
[i
][j
].charAt(0);
119 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
120 return this.board
[i
][j
].charAt(1);
123 static get RESERVE_PIECES() {
124 return [V
.NINJA
, V
.SAMURAI
, V
.MONK
, V
.HORSE
, V
.LANCE
];
127 getReserveMoves([x
, y
]) {
128 // color == 'w', no drops for black.
129 const p
= V
.RESERVE_PIECES
[y
];
130 if (this.reserve
['w'][p
] == 0) return [];
132 for (let i
of [4, 5, 6, 7]) {
133 for (let j
= 0; j
< V
.size
.y
; j
++) {
134 if (this.board
[i
][j
] == V
.EMPTY
) {
145 start: { x: x
, y: y
},
155 static get MapUnpromoted() {
164 getPotentialMovesFrom([x
, y
]) {
166 // Reserves, outside of board: x == sizeX(+1)
167 if (this.turn
== 'b') return [];
168 return this.getReserveMoves([x
, y
]);
171 const piece
= this.getPiece(x
, y
);
173 if ([V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].includes(piece
))
174 return super.getPotentialMovesFrom(sq
);
176 case V
.KING: return this.getPotentialKingMoves(sq
);
177 case V
.CAPTAIN: return this.getPotentialCaptainMoves(sq
);
178 case V
.NINJA: return this.getPotentialNinjaMoves(sq
);
179 case V
.SAMURAI: return this.getPotentialSamuraiMoves(sq
);
185 moves
= super.getPotentialPawnMoves(sq
);
188 moves
= this.getPotentialMonkMoves(sq
);
191 moves
= this.getPotentialHorseMoves(sq
);
194 moves
= this.getPotentialLanceMoves(sq
);
197 const promotionZone
= (this.turn
== 'w' ? [0, 1, 2] : [5, 6, 7]);
198 const promotedForm
= V
.MapUnpromoted
[piece
];
200 if (promotionZone
.includes(m
.end
.x
)) m
.appear
[0].p
= promotedForm
;
205 getPotentialKingMoves([x
, y
]) {
206 if (this.getColor(x
, y
) == 'b') return super.getPotentialKingMoves([x
, y
]);
207 // Clan doesn't castle:
208 return super.getSlideNJumpMoves(
210 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
215 getPotentialCaptainMoves(sq
) {
216 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
217 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
220 getPotentialNinjaMoves(sq
) {
222 super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
])
223 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"))
227 getPotentialSamuraiMoves(sq
) {
229 super.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
])
230 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"))
234 getPotentialMonkMoves(sq
) {
235 return super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], "oneStep");
238 getPotentialHorseMoves(sq
) {
239 return super.getSlideNJumpMoves(sq
, [ [-2, 1], [-2, -1] ], "oneStep");
242 getPotentialLanceMoves(sq
) {
243 return super.getSlideNJumpMoves(sq
, [ [-1, 0] ]);
246 isAttacked(sq
, color
) {
248 return (super.isAttacked(sq
, 'b') || this.isAttackedByCaptain(sq
, 'b'));
249 // Attacked by white:
251 super.isAttackedByKing(sq
, 'w') ||
252 super.isAttackedByPawn(sq
, 'w') ||
253 this.isAttackedByCaptain(sq
, 'w') ||
254 this.isAttackedByNinja(sq
, 'w') ||
255 this.isAttackedBySamurai(sq
, 'w') ||
256 this.isAttackedByMonk(sq
, 'w') ||
257 this.isAttackedByHorse(sq
, 'w') ||
258 this.isAttackedByLance(sq
, 'w') ||
259 super.isAttackedByBishop(sq
, 'w') ||
260 super.isAttackedByKnight(sq
, 'w') ||
261 super.isAttackedByRook(sq
, 'w')
265 isAttackedByCaptain(sq
, color
) {
266 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
[V
.ROOK
]);
268 super.isAttackedBySlideNJump(sq
, color
, V
.CAPTAIN
, steps
, "oneStep")
272 isAttackedByNinja(sq
, color
) {
274 super.isAttackedBySlideNJump(sq
, color
, V
.NINJA
, V
.steps
[V
.BISHOP
]) ||
275 super.isAttackedBySlideNJump(
276 sq
, color
, V
.NINJA
, V
.steps
[V
.KNIGHT
], "oneStep")
280 isAttackedBySamurai(sq
, color
) {
282 super.isAttackedBySlideNJump(sq
, color
, V
.SAMURAI
, V
.steps
[V
.ROOK
]) ||
283 super.isAttackedBySlideNJump(
284 sq
, color
, V
.SAMURAI
, V
.steps
[V
.KNIGHT
], "oneStep")
288 isAttackedByMonk(sq
, color
) {
290 super.isAttackedBySlideNJump(
291 sq
, color
, V
.MONK
, V
.steps
[V
.BISHOP
], "oneStep")
295 isAttackedByHorse(sq
, color
) {
297 super.isAttackedBySlideNJump(
298 sq
, color
, V
.HORSE
, [ [2, 1], [2, -1] ], "oneStep")
302 isAttackedByLance(sq
, color
) {
303 return super.isAttackedBySlideNJump(sq
, color
, V
.LANCE
, [ [1, 0] ]);
307 let moves
= super.getAllPotentialMoves();
308 if (this.turn
== 'w') {
309 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
310 moves
= moves
.concat(
311 this.getReserveMoves([V
.size
.x
, i
])
315 return this.filterValid(moves
);
319 if (super.atLeastOneMove()) return true;
320 if (this.turn
== 'w') {
321 // Search one reserve move
322 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
323 let moves
= this.filterValid(
324 this.getReserveMoves([V
.size
.x
, i
])
326 if (moves
.length
> 0) return true;
332 updateCastleFlags(move, piece
) {
333 // Only black can castle:
335 if (piece
== V
.KING
&& move.appear
[0].c
== 'b')
336 this.castleFlags
['b'] = [8, 8];
338 move.start
.x
== firstRank
&&
339 this.castleFlags
['b'].includes(move.start
.y
)
341 const flagIdx
= (move.start
.y
== this.castleFlags
['b'][0] ? 0 : 1);
342 this.castleFlags
['b'][flagIdx
] = 8;
345 move.end
.x
== firstRank
&&
346 this.castleFlags
['b'].includes(move.end
.y
)
348 const flagIdx
= (move.end
.y
== this.castleFlags
['b'][0] ? 0 : 1);
349 this.castleFlags
['b'][flagIdx
] = 8;
354 super.postPlay(move);
356 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
357 const color
= move.appear
[0].c
;
358 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]--;
362 super.postUndo(move);
363 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
364 const color
= this.turn
;
365 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]++;
368 static get SEARCH_DEPTH() {
373 const color
= this.turn
;
374 const nodrawResult
= (color
== "w" ? "0-1" : "1-0");
375 const oppLastRank
= (color
== 'w' ? 7 : 0);
376 if (this.kingPos
[V
.GetOppCol(color
)][0] == oppLastRank
)
378 if (this.atLeastOneMove()) return "*";
382 static get VALUES() {
399 let evaluation
= super.evalPosition();
401 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
402 const p
= V
.RESERVE_PIECES
[i
];
403 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
409 if (move.vanish
.length
> 0) return super.getNotation(move);
412 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
413 return piece
+ "@" + V
.CoordsToSquare(move.end
);