Fix Musketeer, Joker, Shinobi. Start draft of Cwda
[vchess.git] / client / src / variants / Shinobi.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export class ShinobiRules extends ChessRules {
4
5 static get LoseOnRepetition() {
6 return true;
7 }
8
9 static get CAPTAIN() {
10 return 'c';
11 }
12 static get NINJA() {
13 return 'j';
14 }
15 static get DRAGON() {
16 return 'd';
17 }
18 static get MONK() {
19 return 'm';
20 }
21 static get HORSE() {
22 return 'h';
23 }
24 static get LANCE() {
25 return 'l';
26 }
27
28 static IsGoodFlags(flags) {
29 // Only black can castle
30 return !!flags.match(/^[a-z]{2,2}$/);
31 }
32
33 static get PIECES() {
34 return (
35 ChessRules.PIECES
36 .concat([V.CAPTAIN, V.NINJA, V.DRAGON, V.MONK, V.HORSE, V.LANCE])
37 );
38 }
39
40 getPpath(b) {
41 if (b[0] == 'b' && b[1] != 'c') return b;
42 return "Shinobi/" + b;
43 }
44
45 getReservePpath(index, color) {
46 return "Shinobi/" + color + V.RESERVE_PIECES[index];
47 }
48
49 getFlagsFen() {
50 return this.castleFlags['b'].map(V.CoordToColumn).join("");
51 }
52
53 setFlags(fenflags) {
54 this.castleFlags = { 'b': [-1, -1] };
55 for (let i = 0; i < 2; i++)
56 this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i));
57 }
58
59 static IsGoodFen(fen) {
60 if (!ChessRules.IsGoodFen(fen)) return false;
61 const fenParsed = V.ParseFen(fen);
62 // 5) Check reserve
63 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-2]{5,5}$/))
64 return false;
65 return true;
66 }
67
68 static ParseFen(fen) {
69 const fenParts = fen.split(" ");
70 return Object.assign(
71 ChessRules.ParseFen(fen),
72 { reserve: fenParts[5] }
73 );
74 }
75
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)
80 .replace('Q', 'C')
81 .replace(/B/g, '1')
82 .replace(/R/g, 'L')
83 .replace(/N/g, 'H');
84 return position + " w 0 " + baseFen.substr(48, 2) + " - 11211";
85 }
86
87 getFen() {
88 return super.getFen() + " " + this.getReserveFen();
89 }
90
91 getFenForRepeat() {
92 return super.getFenForRepeat() + "_" + this.getReserveFen();
93 }
94
95 getReserveFen() {
96 // TODO: can simplify other drops variants with this code:
97 return Object.values(this.reserve['w']).join("");
98 }
99
100 setOtherVariables(fen) {
101 super.setOtherVariables(fen);
102 const reserve =
103 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
104 this.reserve = {
105 w: {
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]
111 }
112 };
113 }
114
115 getColor(i, j) {
116 if (i >= V.size.x) return 'w';
117 return this.board[i][j].charAt(0);
118 }
119
120 getPiece(i, j) {
121 if (i >= V.size.x) return V.RESERVE_PIECES[j];
122 return this.board[i][j].charAt(1);
123 }
124
125 static get RESERVE_PIECES() {
126 return [V.NINJA, V.DRAGON, V.MONK, V.HORSE, V.LANCE];
127 }
128
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 [];
133 let moves = [];
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) {
137 let mv = new Move({
138 appear: [
139 new PiPo({
140 x: i,
141 y: j,
142 c: 'w',
143 p: p
144 })
145 ],
146 vanish: [],
147 start: { x: x, y: y },
148 end: { x: i, y: j }
149 });
150 moves.push(mv);
151 }
152 }
153 }
154 return moves;
155 }
156
157 static get MapUnpromoted() {
158 return {
159 m: 'b',
160 h: 'n',
161 l: 'r',
162 p: 'c'
163 };
164 }
165
166 getPotentialMovesFrom([x, y]) {
167 if (x >= V.size.x) {
168 // Reserves, outside of board: x == sizeX(+1)
169 if (this.turn == 'b') return [];
170 return this.getReserveMoves([x, y]);
171 }
172 // Standard moves
173 const piece = this.getPiece(x, y);
174 const sq = [x, y];
175 if ([V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(piece))
176 return super.getPotentialMovesFrom(sq);
177 switch (piece) {
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);
182 }
183 let moves = [];
184 switch (piece) {
185 // Unpromoted
186 case V.PAWN:
187 moves = super.getPotentialPawnMoves(sq);
188 break;
189 case V.MONK:
190 moves = this.getPotentialMonkMoves(sq);
191 break;
192 case V.HORSE:
193 moves = this.getPotentialHorseMoves(sq);
194 break;
195 case V.LANCE:
196 moves = this.getPotentialLanceMoves(sq);
197 break;
198 }
199 const promotionZone = (this.turn == 'w' ? [0, 1] : [7, 6]);
200 const promotedForm = V.MapUnpromoted[piece];
201 moves.forEach(m => {
202 if (promotionZone.includes(m.end.x)) m.appear[0].p = promotedForm;
203 });
204 return moves;
205 }
206
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],
212 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
213 "oneStep"
214 );
215 }
216
217 getPotentialCaptainMoves(sq) {
218 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
219 return super.getSlideNJumpMoves(sq, steps, "oneStep");
220 }
221
222 getPotentialNinjaMoves(sq) {
223 return (
224 super.getSlideNJumpMoves(sq, V.steps[V.BISHOP])
225 .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
226 );
227 }
228
229 getPotentialDragonMoves(sq) {
230 return (
231 super.getSlideNJumpMoves(sq, V.steps[V.ROOK])
232 .concat(super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"))
233 );
234 }
235
236 getPotentialMonkMoves(sq) {
237 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep");
238 }
239
240 getPotentialHorseMoves(sq) {
241 return super.getSlideNJumpMoves(sq, [ [-2, 1], [-2, -1] ], "oneStep");
242 }
243
244 getPotentialLanceMoves(sq) {
245 return super.getSlideNJumpMoves(sq, [ [-1, 0] ]);
246 }
247
248 isAttacked(sq, color) {
249 if (color == 'b')
250 return (super.isAttacked(sq, 'b') || this.isAttackedByCaptain(sq, 'b'));
251 // Attacked by white:
252 return (
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')
264 );
265 }
266
267 isAttackedByCaptain(sq, color) {
268 const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]);
269 return (
270 super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, "oneStep")
271 );
272 }
273
274 isAttackedByNinja(sq, color) {
275 return (
276 super.isAttackedBySlideNJump(sq, color, V.NINJA, V.steps[V.BISHOP]) ||
277 super.isAttackedBySlideNJump(
278 sq, color, V.NINJA, V.steps[V.KNIGHT], "oneStep")
279 );
280 }
281
282 isAttackedByDragon(sq, color) {
283 return (
284 super.isAttackedBySlideNJump(sq, color, V.DRAGON, V.steps[V.ROOK]) ||
285 super.isAttackedBySlideNJump(
286 sq, color, V.DRAGON, V.steps[V.BISHOP], "oneStep")
287 );
288 }
289
290 isAttackedByMonk(sq, color) {
291 return (
292 super.isAttackedBySlideNJump(
293 sq, color, V.MONK, V.steps[V.BISHOP], "oneStep")
294 );
295 }
296
297 isAttackedByHorse(sq, color) {
298 return (
299 super.isAttackedBySlideNJump(
300 sq, color, V.HORSE, [ [2, 1], [2, -1] ], "oneStep")
301 );
302 }
303
304 isAttackedByLance(sq, color) {
305 return super.isAttackedBySlideNJump(sq, color, V.LANCE, [ [1, 0] ]);
306 }
307
308 getAllValidMoves() {
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])
314 );
315 }
316 }
317 return this.filterValid(moves);
318 }
319
320 atLeastOneMove() {
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])
327 );
328 if (moves.length > 0) return true;
329 }
330 }
331 return false;
332 }
333
334 updateCastleFlags(move, piece) {
335 // Only black can castle:
336 const firstRank = 0;
337 if (piece == V.KING && move.appear[0].c == 'b')
338 this.castleFlags['b'] = [8, 8];
339 else if (
340 move.start.x == firstRank &&
341 this.castleFlags['b'].includes(move.start.y)
342 ) {
343 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
344 this.castleFlags['b'][flagIdx] = 8;
345 }
346 else if (
347 move.end.x == firstRank &&
348 this.castleFlags['b'].includes(move.end.y)
349 ) {
350 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
351 this.castleFlags['b'][flagIdx] = 8;
352 }
353 }
354
355 postPlay(move) {
356 super.postPlay(move);
357 // Skip castle:
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]--;
361 }
362
363 postUndo(move) {
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]++;
368 }
369
370 static get SEARCH_DEPTH() {
371 return 2;
372 }
373
374 getCurrentScore() {
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)
379 return nodrawResult;
380 if (this.atLeastOneMove()) return "*";
381 return nodrawResult;
382 }
383
384 static get VALUES() {
385 return (
386 Object.assign(
387 {
388 c: 4,
389 j: 7,
390 d: 6,
391 m: 2,
392 h: 2,
393 l: 2
394 },
395 ChessRules.VALUES
396 )
397 );
398 }
399
400 evalPosition() {
401 let evaluation = super.evalPosition();
402 // Add reserve:
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];
406 }
407 return evaluation;
408 }
409
410 getNotation(move) {
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();
415 return notation;
416 }
417 // Drop:
418 const piece =
419 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
420 return piece + "@" + V.CoordsToSquare(move.end);
421 }
422
423 };