Several small improvements + integrate options + first working 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], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
212 }
213
214 getPotentialCaptainMoves(sq) {
215 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
216 return super.getSlideNJumpMoves(sq, steps, 1);
217 }
218
219 getPotentialNinjaMoves(sq) {
220 return (
221 super.getSlideNJumpMoves(sq, V.steps[V.BISHOP])
222 .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1))
223 );
224 }
225
226 getPotentialDragonMoves(sq) {
227 return (
228 super.getSlideNJumpMoves(sq, V.steps[V.ROOK])
229 .concat(super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1))
230 );
231 }
232
233 getPotentialMonkMoves(sq) {
234 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1);
235 }
236
237 getPotentialHorseMoves(sq) {
238 return super.getSlideNJumpMoves(sq, [ [-2, 1], [-2, -1] ], 1);
239 }
240
241 getPotentialLanceMoves(sq) {
242 return super.getSlideNJumpMoves(sq, [ [-1, 0] ]);
243 }
244
245 isAttacked(sq, color) {
246 if (color == 'b')
247 return (super.isAttacked(sq, 'b') || this.isAttackedByCaptain(sq, 'b'));
248 // Attacked by white:
249 return (
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')
261 );
262 }
263
264 isAttackedByCaptain(sq, color) {
265 const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]);
266 return (
267 super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, 1)
268 );
269 }
270
271 isAttackedByNinja(sq, color) {
272 return (
273 super.isAttackedBySlideNJump(sq, color, V.NINJA, V.steps[V.BISHOP]) ||
274 super.isAttackedBySlideNJump(
275 sq, color, V.NINJA, V.steps[V.KNIGHT], 1)
276 );
277 }
278
279 isAttackedByDragon(sq, color) {
280 return (
281 super.isAttackedBySlideNJump(sq, color, V.DRAGON, V.steps[V.ROOK]) ||
282 super.isAttackedBySlideNJump(
283 sq, color, V.DRAGON, V.steps[V.BISHOP], 1)
284 );
285 }
286
287 isAttackedByMonk(sq, color) {
288 return (
289 super.isAttackedBySlideNJump(
290 sq, color, V.MONK, V.steps[V.BISHOP], 1)
291 );
292 }
293
294 isAttackedByHorse(sq, color) {
295 return (
296 super.isAttackedBySlideNJump(
297 sq, color, V.HORSE, [ [2, 1], [2, -1] ], 1)
298 );
299 }
300
301 isAttackedByLance(sq, color) {
302 return super.isAttackedBySlideNJump(sq, color, V.LANCE, [ [1, 0] ]);
303 }
304
305 getAllValidMoves() {
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])
311 );
312 }
313 }
314 return this.filterValid(moves);
315 }
316
317 atLeastOneMove() {
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])
324 );
325 if (moves.length > 0) return true;
326 }
327 }
328 return false;
329 }
330
331 updateCastleFlags(move, piece) {
332 // Only black can castle:
333 const firstRank = 0;
334 if (piece == V.KING && move.appear[0].c == 'b')
335 this.castleFlags['b'] = [8, 8];
336 else if (
337 move.start.x == firstRank &&
338 this.castleFlags['b'].includes(move.start.y)
339 ) {
340 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
341 this.castleFlags['b'][flagIdx] = 8;
342 }
343 else if (
344 move.end.x == firstRank &&
345 this.castleFlags['b'].includes(move.end.y)
346 ) {
347 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
348 this.castleFlags['b'][flagIdx] = 8;
349 }
350 }
351
352 postPlay(move) {
353 super.postPlay(move);
354 // Skip castle:
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]--;
358 }
359
360 postUndo(move) {
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]++;
365 }
366
367 static get SEARCH_DEPTH() {
368 return 2;
369 }
370
371 getCurrentScore() {
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)
376 return nodrawResult;
377 if (this.atLeastOneMove()) return "*";
378 return nodrawResult;
379 }
380
381 static get VALUES() {
382 return (
383 Object.assign(
384 {
385 c: 4,
386 j: 7,
387 d: 6,
388 m: 2,
389 h: 2,
390 l: 2
391 },
392 ChessRules.VALUES
393 )
394 );
395 }
396
397 evalPosition() {
398 let evaluation = super.evalPosition();
399 // Add reserve:
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];
403 }
404 return evaluation;
405 }
406
407 getNotation(move) {
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();
412 return notation;
413 }
414 // Drop:
415 const piece =
416 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
417 return piece + "@" + V.CoordsToSquare(move.end);
418 }
419
420 };