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(
211 [x
, y
], V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1);
214 getPotentialCaptainMoves(sq
) {
215 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
216 return super.getSlideNJumpMoves(sq
, steps
, 1);
219 getPotentialNinjaMoves(sq
) {
221 super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
])
222 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], 1))
226 getPotentialDragonMoves(sq
) {
228 super.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
])
229 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 1))
233 getPotentialMonkMoves(sq
) {
234 return super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 1);
237 getPotentialHorseMoves(sq
) {
238 return super.getSlideNJumpMoves(sq
, [ [-2, 1], [-2, -1] ], 1);
241 getPotentialLanceMoves(sq
) {
242 return super.getSlideNJumpMoves(sq
, [ [-1, 0] ]);
245 isAttacked(sq
, color
) {
247 return (super.isAttacked(sq
, 'b') || this.isAttackedByCaptain(sq
, 'b'));
248 // Attacked by white:
250 super.isAttackedByKing(sq
, 'w') ||
251 super.isAttackedByPawn(sq
, 'w') ||
252 this.isAttackedByCaptain(sq
, 'w') ||
253 this.isAttackedByNinja(sq
, 'w') ||
254 this.isAttackedByDragon(sq
, 'w') ||
255 this.isAttackedByMonk(sq
, 'w') ||
256 this.isAttackedByHorse(sq
, 'w') ||
257 this.isAttackedByLance(sq
, 'w') ||
258 super.isAttackedByBishop(sq
, 'w') ||
259 super.isAttackedByKnight(sq
, 'w') ||
260 super.isAttackedByRook(sq
, 'w')
264 isAttackedByCaptain(sq
, color
) {
265 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
[V
.ROOK
]);
267 super.isAttackedBySlideNJump(sq
, color
, V
.CAPTAIN
, steps
, 1)
271 isAttackedByNinja(sq
, color
) {
273 super.isAttackedBySlideNJump(sq
, color
, V
.NINJA
, V
.steps
[V
.BISHOP
]) ||
274 super.isAttackedBySlideNJump(
275 sq
, color
, V
.NINJA
, V
.steps
[V
.KNIGHT
], 1)
279 isAttackedByDragon(sq
, color
) {
281 super.isAttackedBySlideNJump(sq
, color
, V
.DRAGON
, V
.steps
[V
.ROOK
]) ||
282 super.isAttackedBySlideNJump(
283 sq
, color
, V
.DRAGON
, V
.steps
[V
.BISHOP
], 1)
287 isAttackedByMonk(sq
, color
) {
289 super.isAttackedBySlideNJump(
290 sq
, color
, V
.MONK
, V
.steps
[V
.BISHOP
], 1)
294 isAttackedByHorse(sq
, color
) {
296 super.isAttackedBySlideNJump(
297 sq
, color
, V
.HORSE
, [ [2, 1], [2, -1] ], 1)
301 isAttackedByLance(sq
, color
) {
302 return super.isAttackedBySlideNJump(sq
, color
, V
.LANCE
, [ [1, 0] ]);
306 let moves
= super.getAllPotentialMoves();
307 if (this.turn
== 'w') {
308 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
309 moves
= moves
.concat(
310 this.getReserveMoves([V
.size
.x
, i
])
314 return this.filterValid(moves
);
318 if (super.atLeastOneMove()) return true;
319 if (this.turn
== 'w') {
320 // Search one reserve move
321 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
322 let moves
= this.filterValid(
323 this.getReserveMoves([V
.size
.x
, i
])
325 if (moves
.length
> 0) return true;
331 updateCastleFlags(move, piece
) {
332 // Only black can castle:
334 if (piece
== V
.KING
&& move.appear
[0].c
== 'b')
335 this.castleFlags
['b'] = [8, 8];
337 move.start
.x
== firstRank
&&
338 this.castleFlags
['b'].includes(move.start
.y
)
340 const flagIdx
= (move.start
.y
== this.castleFlags
['b'][0] ? 0 : 1);
341 this.castleFlags
['b'][flagIdx
] = 8;
344 move.end
.x
== firstRank
&&
345 this.castleFlags
['b'].includes(move.end
.y
)
347 const flagIdx
= (move.end
.y
== this.castleFlags
['b'][0] ? 0 : 1);
348 this.castleFlags
['b'][flagIdx
] = 8;
353 super.postPlay(move);
355 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
356 const color
= move.appear
[0].c
;
357 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]--;
361 super.postUndo(move);
362 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
363 const color
= this.turn
;
364 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]++;
367 static get SEARCH_DEPTH() {
372 const color
= this.turn
;
373 const nodrawResult
= (color
== "w" ? "0-1" : "1-0");
374 const oppLastRank
= (color
== 'w' ? 7 : 0);
375 if (this.kingPos
[V
.GetOppCol(color
)][0] == oppLastRank
)
377 if (this.atLeastOneMove()) return "*";
381 static get VALUES() {
398 let evaluation
= super.evalPosition();
400 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
401 const p
= V
.RESERVE_PIECES
[i
];
402 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
408 if (move.vanish
.length
> 0) {
409 let notation
= super.getNotation(move);
410 if (move.vanish
[0].p
!= V
.PAWN
&& move.appear
[0].p
!= move.vanish
[0].p
)
411 notation
+= "=" + move.appear
[0].p
.toUpperCase();
416 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
417 return piece
+ "@" + V
.CoordsToSquare(move.end
);