1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
4 export class ShinobiRules
extends ChessRules
{
7 static get PawnSpecs() {
9 { promotions: [V.PAWN] },
14 static get CAPTAIN() {
20 static get SAMURAI() {
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
];
49 static IsGoodFen(fen
) {
50 if (!ChessRules
.IsGoodFen(fen
)) return false;
51 const fenParsed
= V
.ParseFen(fen
);
53 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{6,6}$/))
58 static ParseFen(fen
) {
59 const fenParts
= fen
.split(" ");
61 ChessRules
.ParseFen(fen
),
62 { reserve: fenParts
[5] }
66 // In hand initially: another captain, a ninja + a samurai,
67 // and 2 x monk, horse, lance (TODO)
68 static GenRandInitFen(randomness
) {
69 const baseFen
= ChessRules
.GenRandInitFen(Math
.min(randomness
, 1));
71 baseFen
.substr(0, 33) + "3CK3 " +
72 "w 0 " + baseFen
.substr(38, 2) + " - 111222"
77 return super.getFen() + " " + this.getReserveFen();
81 return super.getFenForRepeat() + "_" + this.getReserveFen();
85 // TODO: can simplify other drops variants with this code:
86 return Object
.values(this.reserve
['w']).join("");
89 setOtherVariables(fen
) {
90 super.setOtherVariables(fen
);
92 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
95 [V
.CAPTAIN
]: reserve
[0],
96 [V
.NINJA
]: reserve
[1],
97 [V
.SAMURAI
]: reserve
[2],
100 [V
.LANCE
]: reserve
[5]
106 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
107 return this.board
[i
][j
].charAt(0);
111 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
112 return this.board
[i
][j
].charAt(1);
115 // Ordering on reserve pieces
116 static get RESERVE_PIECES() {
117 return [V
.CAPTAIN
, V
.NINJA
, V
.SAMURAI
, V
.MONK
, V
.HORSE
, V
.LANCE
];
120 getReserveMoves([x
, y
]) {
121 // color == 'w', no drops for black.
122 const p
= V
.RESERVE_PIECES
[y
];
123 if (this.reserve
['w'][p
] == 0) return [];
125 for (let i
of [4, 5, 6, 7]) {
126 for (let j
= 0; j
< V
.size
.y
; j
++) {
127 if (this.board
[i
][j
] == V
.EMPTY
) {
138 start: { x: x
, y: y
},
148 static get MapUnpromoted() {
157 getPotentialMovesFrom([x
, y
]) {
159 // Reserves, outside of board: x == sizeX(+1)
160 if (this.turn
== 'b') return [];
161 return this.getReserveMoves([x
, y
]);
164 const piece
= this.getPiece(x
, y
);
166 if (ChessRules
.includes(piece
)) return super.getPotentialMovesFrom(sq
);
168 case V
.KING: return super.getPotentialKingMoves(sq
);
169 case V
.CAPTAIN: return this.getPotentialCaptainMoves(sq
);
170 case V
.NINJA: return this.getPotentialNinjaMoves(sq
);
171 case V
.SAMURAI: return this.getPotentialSamuraiMoves(sq
);
177 moves
= super.getPotentialPawnMoves(sq
);
179 moves
= this.getPotentialMonkMoves(sq
);
182 moves
= this.getPotentialHorseMoves(sq
);
185 moves
= this.getPotentialLanceMoves(sq
);
188 const promotionZone
= (this.turn
== 'w' ? [0, 1, 2] : [5, 6, 7]);
189 const promotedForm
= V
.MapUnpromoted
[piece
];
191 if (promotionZone
.includes(m
.end
.x
)) move.appear
[0].p
= promotedForm
;
196 getPotentialCaptainMoves([x
, y
]) {
200 getPotentialNinjaMoves(sq
) {
201 return super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], "oneStep");
204 getPotentialSamuraiMoves(sq
) {
205 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
206 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
209 getPotentialMonkMoves(sq
) {
211 super.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
])
212 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"))
216 getPotentialHorseMoves(sq
) {
218 V
.steps
[V
.BISHOP
].concat(V
.steps
[V
.ROOK
]).concat(V
.steps
[V
.KNIGHT
]);
219 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
222 getPotentialLanceMoves(sq
) {
224 super.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
])
225 .concat(super.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"))
229 isAttacked(sq
, color
) {
231 return (super.isAttacked(sq
, 'b') || this.isAttackedByCaptain(sq
, 'b'));
232 // Attacked by white:
234 super.isAttackedByKing(sq
, 'w') ||
235 this.isAttackedByCaptain(sq
, 'w') ||
236 this.isAttackedByNinja(sq
, 'w')
237 this.isAttackedBySamurai(sq
, 'w')
238 this.isAttackedByMonk(sq
, 'w') ||
239 this.isAttackedByHorse(sq
, 'w') ||
240 this.isAttackedByLance(sq
, 'w') ||
241 super.isAttackedByBishop(sq
, 'w') ||
242 super.isAttackedByKnight(sq
, 'w') ||
243 super.isAttackedByRook(sq
, 'w')
247 isAttackedByCaptain(sq
, color
) {
248 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
[V
.ROOK
]);
250 super.isAttackedBySlideNJump(sq
, color
, V
.DUCHESS
, steps
, "oneStep")
254 isAttackedByNinja(sq
, color
) {
256 super.isAttackedBySlideNJump(
257 sq
, color
, V
.DUCHESS
, V
.steps
[V
.BISHOP
], "oneStep")
261 isAttackedBySamurai(sq
, color
) {
263 super.isAttackedBySlideNJump(sq
, color
, V
.MORTAR
, V
.steps
[V
.ROOK
]) ||
264 super.isAttackedBySlideNJump(
265 sq
, color
, V
.MORTAR
, V
.steps
[V
.KNIGHT
], "oneStep")
269 isAttackedByMonk(sq
, color
) {
271 V
.steps
[V
.BISHOP
].concat(V
.steps
[V
.ROOK
]).concat(V
.steps
[V
.KNIGHT
]);
273 super.isAttackedBySlideNJump(sq
, color
, V
.GENERAL
, steps
, "oneStep")
277 isAttackedByHorse(sq
, color
) {
279 super.isAttackedBySlideNJump(sq
, color
, V
.ARCHBISHOP
, V
.steps
[V
.BISHOP
])
281 super.isAttackedBySlideNJump(
282 sq
, color
, V
.ARCHBISHOP
, V
.steps
[V
.KNIGHT
], "oneStep")
286 isAttackedByLance(sq
, color
) {
288 super.isAttackedBySlideNJump(sq
, color
, V
.ARCHBISHOP
, V
.steps
[V
.BISHOP
])
290 super.isAttackedBySlideNJump(
291 sq
, color
, V
.ARCHBISHOP
, V
.steps
[V
.KNIGHT
], "oneStep")
296 let moves
= super.getAllPotentialMoves();
297 const color
= this.turn
;
298 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
299 moves
= moves
.concat(
300 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
303 return this.filterValid(moves
);
307 if (!super.atLeastOneMove()) {
308 // Search one reserve move
309 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
310 let moves
= this.filterValid(
311 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
313 if (moves
.length
> 0) return true;
320 // TODO: only black can castle (see Orda)
323 super.postPlay(move);
325 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
326 const color
= move.appear
[0].c
;
327 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]--;
331 super.postUndo(move);
332 if (move.vanish
.length
== 2 && move.appear
.length
== 2) return;
333 const color
= this.turn
;
334 if (move.vanish
.length
== 0) this.reserve
[color
][move.appear
[0].p
]++;
338 static get SEARCH_DEPTH() {
343 static get VALUES() {
359 let evaluation
= super.evalPosition();
361 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
362 const p
= V
.RESERVE_PIECES
[i
];
363 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
364 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
370 if (move.vanish
.length
> 0) return super.getNotation(move);
373 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
374 return piece
+ "@" + V
.CoordsToSquare(move.end
);