Attempt to improve scrolling on smartphone
[vchess.git] / client / src / variants / Cwda.js
1 import { ChessRules } from "@/base_rules";
2
3 export class CwdaRules extends ChessRules {
4
5 static get Options() {
6 return {
7 select: ChessRules.Options.select.concat([
8 {
9 label: "Army 1",
10 variable: "army1",
11 defaut: 'C',
12 options: [
13 { label: "Colorbound Clobberers", value: 'C' },
14 { label: "Nutty Knights", value: 'N' },
15 { label: "Remarkable Rookies", value: 'R' },
16 { label: "Fide", value: 'F' }
17 ]
18 },
19 {
20 label: "Army 2",
21 variable: "army2",
22 defaut: 'C',
23 options: [
24 { label: "Colorbound Clobberers", value: 'C' },
25 { label: "Nutty Knights", value: 'N' },
26 { label: "Remarkable Rookies", value: 'R' },
27 { label: "Fide", value: 'F' }
28 ]
29 }
30 ])
31 };
32 }
33
34 static AbbreviateOptions(opts) {
35 return opts["army1"] + opts["army2"];
36 }
37
38 static IsValidOptions(opts) {
39 // Both armies filled, avoid Fide vs Fide
40 return (
41 opts.army1 && opts.army2 &&
42 (opts.army1 != 'F' || opts.army2 != 'F')
43 );
44 }
45
46 getPpath(b) {
47 return (ChessRules.PIECES.includes(b[1]) ? "" : "Cwda/") + b;
48 }
49
50 static get PiecesMap() {
51 return {
52 // Colorbound Clobberers
53 'C': {
54 'r': 'd',
55 'n': 'w',
56 'b': 'f',
57 'q': 'c',
58 'k': 'm',
59 'p': 'z'
60 },
61 // Nutty Knights
62 'N': {
63 'r': 'g',
64 'n': 'i',
65 'b': 't',
66 'q': 'l',
67 'k': 'e',
68 'p': 'v'
69 },
70 // Remarkable Rookies
71 'R': {
72 'r': 's',
73 'n': 'y',
74 'b': 'h',
75 'q': 'o',
76 'k': 'a',
77 'p': 'u'
78 }
79 };
80 }
81
82 static GenRandInitFen(options) {
83 const baseFen = ChessRules.GenRandInitFen(options.randomness);
84 let blackLine = baseFen.substr(0, 8), blackPawns = "pppppppp";
85 if (options.army2 != 'F') {
86 blackLine = blackLine.split('')
87 .map(p => V.PiecesMap[options.army2][p]).join('');
88 blackPawns = V.PiecesMap[options.army2]['p'].repeat(8);
89 }
90 let whiteLine = baseFen.substr(35, 8), whitePawns = "PPPPPPPP";
91 if (options.army1 != 'F') {
92 whiteLine = whiteLine.split('')
93 .map(p => V.PiecesMap[options.army1][p.toLowerCase()])
94 .join('').toUpperCase();
95 whitePawns = V.PiecesMap[options.army1]['p'].toUpperCase().repeat(8);
96 }
97 return (
98 blackLine + "/" + blackPawns +
99 baseFen.substring(17, 26) +
100 whitePawns + "/" + whiteLine +
101 baseFen.substr(43) + " " + options.army1 + options.army2
102 );
103 }
104
105 setOtherVariables(fen) {
106 super.setOtherVariables(fen);
107 const armies = V.ParseFen(fen).armies;
108 this.army1 = armies.charAt(0);
109 this.army2 = armies.charAt(1);
110 }
111
112 scanKings(fen) {
113 this.kingPos = { w: [-1, -1], b: [-1, -1] };
114 const fenRows = V.ParseFen(fen).position.split("/");
115 for (let i = 0; i < fenRows.length; i++) {
116 let k = 0;
117 for (let j = 0; j < fenRows[i].length; j++) {
118 const newChar = fenRows[i].charAt(j);
119 if (['a', 'e', 'k', 'm'].includes(newChar))
120 this.kingPos["b"] = [i, k];
121 else if (['A', 'E', 'K', 'M'].includes(newChar))
122 this.kingPos["w"] = [i, k];
123 else {
124 const num = parseInt(fenRows[i].charAt(j), 10);
125 if (!isNaN(num)) k += num - 1;
126 }
127 k++;
128 }
129 }
130 }
131
132 static ParseFen(fen) {
133 return Object.assign(
134 { armies: fen.split(" ")[5] },
135 ChessRules.ParseFen(fen)
136 );
137 }
138
139 static IsGoodFen(fen) {
140 if (!ChessRules.IsGoodFen(fen)) return false;
141 const armies = V.ParseFen(fen).armies;
142 return (!!armies && armies.match(/^[CNRF]{2,2}$/));
143 }
144
145 getFen() {
146 return super.getFen() + " " + this.army1 + this.army2;
147 }
148
149 static get C_ROOK() {
150 return 'd';
151 }
152 static get C_KNIGHT() {
153 return 'w';
154 }
155 static get C_BISHOP() {
156 return 'f';
157 }
158 static get C_QUEEN() {
159 return 'c';
160 }
161 static get C_KING() {
162 return 'm';
163 }
164 static get C_PAWN() {
165 return 'z';
166 }
167 static get N_ROOK() {
168 return 'g';
169 }
170 static get N_KNIGHT() {
171 return 'i';
172 }
173 static get N_BISHOP() {
174 return 't';
175 }
176 static get N_QUEEN() {
177 return 'l';
178 }
179 static get N_KING() {
180 return 'e';
181 }
182 static get N_PAWN() {
183 return 'v';
184 }
185 static get R_ROOK() {
186 return 's';
187 }
188 static get R_KNIGHT() {
189 return 'y';
190 }
191 static get R_BISHOP() {
192 return 'h';
193 }
194 static get R_QUEEN() {
195 return 'o';
196 }
197 static get R_KING() {
198 return 'a';
199 }
200 static get R_PAWN() {
201 return 'u';
202 }
203
204 getPiece(x, y) {
205 const p = this.board[x][y][1];
206 if (['u', 'v', 'z'].includes(p)) return 'p';
207 if (['a', 'e', 'm'].includes(p)) return 'k';
208 return p;
209 }
210
211 static get PIECES() {
212 return ChessRules.PIECES.concat(
213 [
214 V.C_ROOK, V.C_KNIGHT, V.C_BISHOP, V.C_QUEEN, V.C_KING, V.C_PAWN,
215 V.N_ROOK, V.N_KNIGHT, V.N_BISHOP, V.N_QUEEN, V.N_KING, V.N_PAWN,
216 V.R_ROOK, V.R_KNIGHT, V.R_BISHOP, V.R_QUEEN, V.R_KING, V.R_PAWN
217 ]
218 );
219 }
220
221 getEpSquare(moveOrSquare) {
222 if (!moveOrSquare) return undefined; //TODO: necessary line?!
223 if (typeof moveOrSquare === "string") {
224 const square = moveOrSquare;
225 if (square == "-") return undefined;
226 return V.SquareToCoords(square);
227 }
228 // Argument is a move:
229 const move = moveOrSquare;
230 const s = move.start,
231 e = move.end;
232 if (
233 s.y == e.y &&
234 Math.abs(s.x - e.x) == 2 &&
235 ['p', 'u', 'v'].includes(move.appear[0].p)
236 ) {
237 return {
238 x: (s.x + e.x) / 2,
239 y: s.y
240 };
241 }
242 return undefined; //default
243 }
244
245 getPotentialMovesFrom(sq) {
246 switch (this.getPiece(sq[0], sq[1])) {
247 case V.C_ROOK: return this.getPotentialC_rookMoves(sq);
248 case V.C_KNIGHT: return this.getPotentialC_knightMoves(sq);
249 case V.C_BISHOP: return this.getPotentialC_bishopMoves(sq);
250 case V.C_QUEEN: return this.getPotentialC_queenMoves(sq);
251 case V.N_ROOK: return this.getPotentialN_rookMoves(sq);
252 case V.N_KNIGHT: return this.getPotentialN_knightMoves(sq);
253 case V.N_BISHOP: return this.getPotentialN_bishopMoves(sq);
254 case V.N_QUEEN: return this.getPotentialN_queenMoves(sq);
255 case V.R_ROOK: return this.getPotentialR_rookMoves(sq);
256 case V.R_KNIGHT: return this.getPotentialR_knightMoves(sq);
257 case V.R_BISHOP: return this.getPotentialR_bishopMoves(sq);
258 case V.R_QUEEN: return this.getPotentialR_queenMoves(sq);
259 case V.PAWN: {
260 // Can promote in anything from the two current armies
261 let promotions = [];
262 for (let army of ["army1", "army2"]) {
263 if (army == "army2" && this.army2 == this.army1) break;
264 switch (this[army]) {
265 case 'C': {
266 Array.prototype.push.apply(promotions,
267 [V.C_ROOK, V.C_KNIGHT, V.C_BISHOP, V.C_QUEEN]);
268 break;
269 }
270 case 'N': {
271 Array.prototype.push.apply(promotions,
272 [V.N_ROOK, V.N_KNIGHT, V.N_BISHOP, V.N_QUEEN]);
273 break;
274 }
275 case 'R': {
276 Array.prototype.push.apply(promotions,
277 [V.R_ROOK, V.R_KNIGHT, V.R_BISHOP, V.R_QUEEN]);
278 break;
279 }
280 case 'F': {
281 Array.prototype.push.apply(promotions,
282 [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]);
283 break;
284 }
285 }
286 }
287 return super.getPotentialPawnMoves(sq, promotions);
288 }
289 default: return super.getPotentialMovesFrom(sq);
290 }
291 return [];
292 }
293
294 static get steps() {
295 return Object.assign(
296 {
297 // Dabbabah
298 'd': [
299 [-2, 0],
300 [0, -2],
301 [2, 0],
302 [0, 2]
303 ],
304 // Alfil
305 'a': [
306 [2, 2],
307 [2, -2],
308 [-2, 2],
309 [-2, -2]
310 ],
311 // Ferz
312 'f': [
313 [1, 1],
314 [1, -1],
315 [-1, 1],
316 [-1, -1]
317 ],
318 // Wazir
319 'w': [
320 [-1, 0],
321 [0, -1],
322 [1, 0],
323 [0, 1]
324 ],
325 // Threeleaper
326 '$3': [
327 [-3, 0],
328 [0, -3],
329 [3, 0],
330 [0, 3]
331 ],
332 // Narrow knight
333 '$n': [
334 [-2, -1],
335 [-2, 1],
336 [2, -1],
337 [2, 1]
338 ]
339 },
340 ChessRules.steps,
341 );
342 }
343
344 getPotentialC_rookMoves(sq) {
345 return (
346 this.getSlideNJumpMoves(sq, V.steps.b).concat(
347 this.getSlideNJumpMoves(sq, V.steps.d, 1))
348 );
349 }
350
351 getPotentialC_knightMoves(sq) {
352 return (
353 this.getSlideNJumpMoves(sq, V.steps.a, 1).concat(
354 this.getSlideNJumpMoves(sq, V.steps.r, 1))
355 );
356 }
357
358 getPotentialC_bishopMoves(sq) {
359 return (
360 this.getSlideNJumpMoves(sq, V.steps.d, 1).concat(
361 this.getSlideNJumpMoves(sq, V.steps.a, 1)).concat(
362 this.getSlideNJumpMoves(sq, V.steps.b, 1))
363 );
364 }
365
366 getPotentialC_queenMoves(sq) {
367 return (
368 this.getSlideNJumpMoves(sq, V.steps.b).concat(
369 this.getSlideNJumpMoves(sq, V.steps.n, 1))
370 );
371 }
372
373 getPotentialN_rookMoves(sq) {
374 const c = this.turn;
375 const rookSteps = [ [0, -1], [0, 1], [c == 'w' ? -1 : 1, 0] ];
376 const backward = (c == 'w' ? 1 : -1);
377 const kingSteps = [ [backward, -1], [backward, 0], [backward, 1] ];
378 return (
379 this.getSlideNJumpMoves(sq, rookSteps).concat(
380 this.getSlideNJumpMoves(sq, kingSteps, 1))
381 );
382 }
383
384 getPotentialN_knightMoves(sq) {
385 return (
386 this.getSlideNJumpMoves(sq, V.steps.$n, 1).concat(
387 this.getSlideNJumpMoves(sq, V.steps.f, 1))
388 );
389 }
390
391 getPotentialN_bishopMoves(sq) {
392 const backward = (this.turn == 'w' ? 1 : -1);
393 const kingSteps = [
394 [0, -1], [0, 1], [backward, -1], [backward, 0], [backward, 1]
395 ];
396 const forward = -backward;
397 const knightSteps = [
398 [2*forward, -1], [2*forward, 1], [forward, -2], [forward, 2]
399 ];
400 return (
401 this.getSlideNJumpMoves(sq, knightSteps, 1).concat(
402 this.getSlideNJumpMoves(sq, kingSteps, 1))
403 );
404 }
405
406 getPotentialN_queenMoves(sq) {
407 const backward = (this.turn == 'w' ? 1 : -1);
408 const forward = -backward;
409 const kingSteps = [
410 [forward, -1], [forward, 1],
411 [backward, -1], [backward, 0], [backward, 1]
412 ];
413 const knightSteps = [
414 [2*forward, -1], [2*forward, 1], [forward, -2], [forward, 2]
415 ];
416 const rookSteps = [ [0, -1], [0, 1], [forward, 0] ];
417 return (
418 this.getSlideNJumpMoves(sq, rookSteps).concat(
419 this.getSlideNJumpMoves(sq, kingSteps, 1)).concat(
420 this.getSlideNJumpMoves(sq, knightSteps, 1))
421 );
422 }
423
424 getPotentialR_rookMoves(sq) {
425 return this.getSlideNJumpMoves(sq, V.steps.r, 4);
426 }
427
428 getPotentialR_knightMoves(sq) {
429 return (
430 this.getSlideNJumpMoves(sq, V.steps.d, 1).concat(
431 this.getSlideNJumpMoves(sq, V.steps.w, 1))
432 );
433 }
434
435 getPotentialR_bishopMoves(sq) {
436 return (
437 this.getSlideNJumpMoves(sq, V.steps.d, 1).concat(
438 this.getSlideNJumpMoves(sq, V.steps.f, 1)).concat(
439 this.getSlideNJumpMoves(sq, V.steps.$3, 1))
440 );
441 }
442
443 getPotentialR_queenMoves(sq) {
444 return (
445 this.getSlideNJumpMoves(sq, V.steps.r).concat(
446 this.getSlideNJumpMoves(sq, V.steps.n, 1))
447 );
448 }
449
450 getCastleMoves([x, y]) {
451 const color = this.getColor(x, y);
452 let finalSquares = [ [2, 3], [V.size.y - 2, V.size.y - 3] ];
453 if (
454 (color == 'w' && this.army1 == 'C') ||
455 (color == 'b' && this.army2 == 'C')
456 ) {
457 // Colorbound castle long in an unusual way:
458 finalSquares[0] = [1, 2];
459 }
460 return super.getCastleMoves([x, y], finalSquares);
461 }
462
463 isAttacked(sq, color) {
464 if (super.isAttackedByPawn(sq, color) || super.isAttackedByKing(sq, color))
465 return true;
466 for (let army of ['C', 'N', 'R', 'F']) {
467 if (
468 [this.army1, this.army2].includes(army) &&
469 (
470 this["isAttackedBy" + army + "_rook"](sq, color) ||
471 this["isAttackedBy" + army + "_knight"](sq, color) ||
472 this["isAttackedBy" + army + "_bishop"](sq, color) ||
473 this["isAttackedBy" + army + "_queen"](sq, color)
474 )
475 ) {
476 return true;
477 }
478 }
479 return false;
480 }
481
482 isAttackedByC_rook(sq, color) {
483 return (
484 this.isAttackedBySlideNJump(sq, color, V.C_ROOK, V.steps.b) ||
485 this.isAttackedBySlideNJump(sq, color, V.C_ROOK, V.steps.d, 1)
486 );
487 }
488
489 isAttackedByC_knight(sq, color) {
490 return (
491 this.isAttackedBySlideNJump(sq, color, V.C_KNIGHT, V.steps.r, 1) ||
492 this.isAttackedBySlideNJump(sq, color, V.C_KNIGHT, V.steps.a, 1)
493 );
494 }
495
496 isAttackedByC_bishop(sq, color) {
497 return (
498 this.isAttackedBySlideNJump(sq, color, V.C_BISHOP, V.steps.d, 1) ||
499 this.isAttackedBySlideNJump(sq, color, V.C_BISHOP, V.steps.a, 1) ||
500 this.isAttackedBySlideNJump(sq, color, V.C_BISHOP, V.steps.f, 1)
501 );
502 }
503
504 isAttackedByC_queen(sq, color) {
505 return (
506 this.isAttackedBySlideNJump(sq, color, V.C_QUEEN, V.steps.b) ||
507 this.isAttackedBySlideNJump(sq, color, V.C_QUEEN, V.steps.n, 1)
508 );
509 }
510
511 isAttackedByN_rook(sq, color) {
512 const rookSteps = [ [0, -1], [0, 1], [color == 'w' ? 1 : -1, 0] ];
513 const backward = (color == 'w' ? -1 : 1);
514 const kingSteps = [ [backward, -1], [backward, 0], [backward, 1] ];
515 return (
516 this.isAttackedBySlideNJump(sq, color, V.N_ROOK, rookSteps) ||
517 this.isAttackedBySlideNJump(sq, color, V.N_ROOK, kingSteps, 1)
518 );
519 }
520
521 isAttackedByN_knight(sq, color) {
522 return (
523 this.isAttackedBySlideNJump(sq, color, V.N_KNIGHT, V.steps.$n, 1) ||
524 this.isAttackedBySlideNJump(sq, color, V.N_KNIGHT, V.steps.f, 1)
525 );
526 }
527
528 isAttackedByN_bishop(sq, color) {
529 const backward = (color == 'w' ? -1 : 1);
530 const kingSteps = [
531 [0, -1], [0, 1], [backward, -1], [backward, 0], [backward, 1]
532 ];
533 const forward = -backward;
534 const knightSteps = [
535 [2*forward, -1], [2*forward, 1], [forward, -2], [forward, 2]
536 ];
537 return (
538 this.isAttackedBySlideNJump(sq, color, V.N_BISHOP, knightSteps, 1) ||
539 this.isAttackedBySlideNJump(sq, color, V.N_BISHOP, kingSteps, 1)
540 );
541 }
542
543 isAttackedByN_queen(sq, color) {
544 const backward = (color == 'w' ? -1 : 1);
545 const forward = -backward;
546 const kingSteps = [
547 [forward, -1], [forward, 1],
548 [backward, -1], [backward, 0], [backward, 1]
549 ];
550 const knightSteps = [
551 [2*forward, -1], [2*forward, 1], [forward, -2], [forward, 2]
552 ];
553 const rookSteps = [ [0, -1], [0, 1], [forward, 0] ];
554 return (
555 this.isAttackedBySlideNJump(sq, color, V.N_QUEEN, knightSteps, 1) ||
556 this.isAttackedBySlideNJump(sq, color, V.N_QUEEN, kingSteps, 1) ||
557 this.isAttackedBySlideNJump(sq, color, V.N_QUEEN, rookSteps)
558 );
559 }
560
561 isAttackedByR_rook(sq, color) {
562 return this.isAttackedBySlideNJump(sq, color, V.R_ROOK, V.steps.r, 4);
563 }
564
565 isAttackedByR_knight(sq, color) {
566 return (
567 this.isAttackedBySlideNJump(sq, color, V.R_KNIGHT, V.steps.d, 1) ||
568 this.isAttackedBySlideNJump(sq, color, V.R_KNIGHT, V.steps.w, 1)
569 );
570 }
571
572 isAttackedByR_bishop(sq, color) {
573 return (
574 this.isAttackedBySlideNJump(sq, color, V.R_BISHOP, V.steps.d, 1) ||
575 this.isAttackedBySlideNJump(sq, color, V.R_BISHOP, V.steps.f, 1) ||
576 this.isAttackedBySlideNJump(sq, color, V.R_BISHOP, V.steps.$3, 1)
577 );
578 }
579
580 isAttackedByR_queen(sq, color) {
581 return (
582 this.isAttackedBySlideNJump(sq, color, V.R_QUEEN, V.steps.r) ||
583 this.isAttackedBySlideNJump(sq, color, V.R_QUEEN, V.steps.n, 1)
584 );
585 }
586
587 // [HACK] So that the function above works also on Fide army:
588 isAttackedByF_rook(sq, color) {
589 return super.isAttackedByRook(sq, color);
590 }
591 isAttackedByF_knight(sq, color) {
592 return super.isAttackedByKnight(sq, color);
593 }
594 isAttackedByF_bishop(sq, color) {
595 return super.isAttackedByBishop(sq, color);
596 }
597 isAttackedByF_queen(sq, color) {
598 return super.isAttackedByQueen(sq, color);
599 }
600
601 postPlay(move) {
602 const c = V.GetOppCol(this.turn);
603 const piece = move.appear[0].p;
604 // Update king position + flags
605 if (['k', 'a', 'e', 'm'].includes(piece)) {
606 this.kingPos[c][0] = move.appear[0].x;
607 this.kingPos[c][1] = move.appear[0].y;
608 this.castleFlags[c] = [V.size.y, V.size.y];
609 }
610 // Next call is still required because the king may eat an opponent's rook
611 // TODO: castleFlags will be turned off twice then.
612 super.updateCastleFlags(move, piece);
613 }
614
615 postUndo(move) {
616 // (Potentially) Reset king position
617 const c = this.getColor(move.start.x, move.start.y);
618 const piece = move.appear[0].p;
619 if (['k', 'a', 'e', 'm'].includes(piece))
620 this.kingPos[c] = [move.start.x, move.start.y];
621 }
622
623 static get VALUES() {
624 return Object.assign(
625 {
626 d: 4,
627 w: 3,
628 f: 5,
629 c: 7,
630 g: 4,
631 i: 3,
632 t: 4,
633 l: 7,
634 s: 4,
635 y: 3,
636 h: 4,
637 o: 8
638 },
639 ChessRules.VALUES
640 );
641 }
642
643 static get SEARCH_DEPTH() {
644 return 2;
645 }
646
647 getNotation(move) {
648 let notation = super.getNotation(move);
649 if (['u', 'v', 'z'].includes(move.appear[0].p))
650 notation = notation.slice(0, -2);
651 return notation;
652 }
653
654 };