1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
3 export class ShinobiRules
extends ChessRules
{
5 static get LoseOnRepetition() {
28 static IsGoodFlags(flags
) {
29 // Only black can castle
30 return !!flags
.match(/^[a-z]{2,2}$/);
36 .concat([V
.CAPTAIN
, V
.NINJA
, V
.DRAGON
, 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-2]{5,5}$/))
68 static ParseFen(fen
) {
69 const fenParts
= fen
.split(" ");
71 ChessRules
.ParseFen(fen
),
72 { reserve: fenParts
[5] }
76 // In hand initially: ninja, dragon, 2 x (monk, horse), lance, pawn.
77 static GenRandInitFen(options
) {
78 const baseFen
= ChessRules
.GenRandInitFen(options
);
79 const position
= baseFen
.substr(0, 43)
84 return position
+ " w 0 " + baseFen
.substr(48, 2) + " - 11211";
88 return super.getFen() + " " + this.getReserveFen();
92 return super.getFenForRepeat() + "_" + this.getReserveFen();
96 // TODO: can simplify other drops variants with this code:
97 return Object
.values(this.reserve
['w']).join("");
100 setOtherVariables(fen
) {
101 super.setOtherVariables(fen
);
103 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
106 [V
.NINJA
]: reserve
[0],
107 [V
.DRAGON
]: reserve
[1],
108 [V
.MONK
]: reserve
[2],
109 [V
.HORSE
]: reserve
[3],
110 [V
.LANCE
]: reserve
[4]
116 if (i
>= V
.size
.x
) return 'w';
117 return this.board
[i
][j
].charAt(0);
121 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
122 return this.board
[i
][j
].charAt(1);
125 static get RESERVE_PIECES() {
126 return [V
.NINJA
, V
.DRAGON
, V
.MONK
, V
.HORSE
, V
.LANCE
];
129 getReserveMoves([x
, y
]) {
130 // color == 'w', no drops for black.
131 const p
= V
.RESERVE_PIECES
[y
];
132 if (this.reserve
['w'][p
] == 0) return [];
134 for (let i
of [4, 5, 6, 7]) {
135 for (let j
= 0; j
< V
.size
.y
; j
++) {
136 if (this.board
[i
][j
] == V
.EMPTY
) {
147 start: { x: x
, y: y
},
157 static get MapUnpromoted() {
166 getPotentialMovesFrom([x
, y
]) {
168 // Reserves, outside of board: x == sizeX(+1)
169 if (this.turn
== 'b') return [];
170 return this.getReserveMoves([x
, y
]);
173 const piece
= this.getPiece(x
, y
);
175 if ([V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].includes(piece
))
176 return super.getPotentialMovesFrom(sq
);
178 case V
.KING: return this.getPotentialKingMoves(sq
);
179 case V
.CAPTAIN: return this.getPotentialCaptainMoves(sq
);
180 case V
.NINJA: return this.getPotentialNinjaMoves(sq
);
181 case V
.DRAGON: return this.getPotentialDragonMoves(sq
);
187 moves
= super.getPotentialPawnMoves(sq
);
190 moves
= this.getPotentialMonkMoves(sq
);
193 moves
= this.getPotentialHorseMoves(sq
);
196 moves
= this.getPotentialLanceMoves(sq
);
199 const promotionZone
= (this.turn
== 'w' ? [0, 1] : [7, 6]);
200 const promotedForm
= V
.MapUnpromoted
[piece
];
202 if (promotionZone
.includes(m
.end
.x
)) m
.appear
[0].p
= promotedForm
;
207 getPotentialKingMoves([x
, y
]) {
208 if (this.getColor(x
, y
) == 'b') return super.getPotentialKingMoves([x
, y
]);
209 // Clan doesn't castle:
210 return super.getSlideNJumpMoves(
212 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
217 getPotentialCaptainMoves(sq
) {
218 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
219 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
222 getPotentialNinjaMoves(sq
) {
224 super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
])
225 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"))
229 getPotentialDragonMoves(sq
) {
231 super.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
])
232 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], "oneStep"))
236 getPotentialMonkMoves(sq
) {
237 return super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], "oneStep");
240 getPotentialHorseMoves(sq
) {
241 return super.getSlideNJumpMoves(sq
, [ [-2, 1], [-2, -1] ], "oneStep");
244 getPotentialLanceMoves(sq
) {
245 return super.getSlideNJumpMoves(sq
, [ [-1, 0] ]);
248 isAttacked(sq
, color
) {
250 return (super.isAttacked(sq
, 'b') || this.isAttackedByCaptain(sq
, 'b'));
251 // Attacked by white:
253 super.isAttackedByKing(sq
, 'w') ||
254 super.isAttackedByPawn(sq
, 'w') ||
255 this.isAttackedByCaptain(sq
, 'w') ||
256 this.isAttackedByNinja(sq
, 'w') ||
257 this.isAttackedByDragon(sq
, 'w') ||
258 this.isAttackedByMonk(sq
, 'w') ||
259 this.isAttackedByHorse(sq
, 'w') ||
260 this.isAttackedByLance(sq
, 'w') ||
261 super.isAttackedByBishop(sq
, 'w') ||
262 super.isAttackedByKnight(sq
, 'w') ||
263 super.isAttackedByRook(sq
, 'w')
267 isAttackedByCaptain(sq
, color
) {
268 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
[V
.ROOK
]);
270 super.isAttackedBySlideNJump(sq
, color
, V
.CAPTAIN
, steps
, "oneStep")
274 isAttackedByNinja(sq
, color
) {
276 super.isAttackedBySlideNJump(sq
, color
, V
.NINJA
, V
.steps
[V
.BISHOP
]) ||
277 super.isAttackedBySlideNJump(
278 sq
, color
, V
.NINJA
, V
.steps
[V
.KNIGHT
], "oneStep")
282 isAttackedByDragon(sq
, color
) {
284 super.isAttackedBySlideNJump(sq
, color
, V
.DRAGON
, V
.steps
[V
.ROOK
]) ||
285 super.isAttackedBySlideNJump(
286 sq
, color
, V
.DRAGON
, V
.steps
[V
.BISHOP
], "oneStep")
290 isAttackedByMonk(sq
, color
) {
292 super.isAttackedBySlideNJump(
293 sq
, color
, V
.MONK
, V
.steps
[V
.BISHOP
], "oneStep")
297 isAttackedByHorse(sq
, color
) {
299 super.isAttackedBySlideNJump(
300 sq
, color
, V
.HORSE
, [ [2, 1], [2, -1] ], "oneStep")
304 isAttackedByLance(sq
, color
) {
305 return super.isAttackedBySlideNJump(sq
, color
, V
.LANCE
, [ [1, 0] ]);
309 let moves
= super.getAllPotentialMoves();
310 if (this.turn
== 'w') {
311 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
312 moves
= moves
.concat(
313 this.getReserveMoves([V
.size
.x
, i
])
317 return this.filterValid(moves
);
321 if (super.atLeastOneMove()) return true;
322 if (this.turn
== 'w') {
323 // Search one reserve move
324 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
325 let moves
= this.filterValid(
326 this.getReserveMoves([V
.size
.x
, i
])
328 if (moves
.length
> 0) return true;
334 updateCastleFlags(move, piece
) {
335 // Only black can castle:
337 if (piece
== V
.KING
&& move.appear
[0].c
== 'b')
338 this.castleFlags
['b'] = [8, 8];
340 move.start
.x
== firstRank
&&
341 this.castleFlags
['b'].includes(move.start
.y
)
343 const flagIdx
= (move.start
.y
== this.castleFlags
['b'][0] ? 0 : 1);
344 this.castleFlags
['b'][flagIdx
] = 8;
347 move.end
.x
== firstRank
&&
348 this.castleFlags
['b'].includes(move.end
.y
)
350 const flagIdx
= (move.end
.y
== this.castleFlags
['b'][0] ? 0 : 1);
351 this.castleFlags
['b'][flagIdx
] = 8;
356 super.postPlay(move);
358 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
359 const color
= move.appear
[0].c
;
360 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]--;
364 super.postUndo(move);
365 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
366 const color
= this.turn
;
367 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]++;
370 static get SEARCH_DEPTH() {
375 const color
= this.turn
;
376 const nodrawResult
= (color
== "w" ? "0-1" : "1-0");
377 const oppLastRank
= (color
== 'w' ? 7 : 0);
378 if (this.kingPos
[V
.GetOppCol(color
)][0] == oppLastRank
)
380 if (this.atLeastOneMove()) return "*";
384 static get VALUES() {
401 let evaluation
= super.evalPosition();
403 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
404 const p
= V
.RESERVE_PIECES
[i
];
405 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
411 if (move.vanish
.length
> 0) {
412 let notation
= super.getNotation(move);
413 if (move.vanish
[0].p
!= V
.PAWN
&& move.appear
[0].p
!= move.vanish
[0].p
)
414 notation
+= "=" + move.appear
[0].p
.toUpperCase();
419 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
420 return piece
+ "@" + V
.CoordsToSquare(move.end
);