Fix Pandemonium (pawn drop)
[vchess.git] / client / src / variants / Pandemonium.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class PandemoniumRules extends ChessRules {
5
6 static get PawnSpecs() {
7 return Object.assign(
8 {},
9 ChessRules.PawnSpecs,
10 {
11 threeSquares: true,
12 promotions: [V.GILDING]
13 }
14 );
15 }
16
17 static get GILDING() {
18 return "g";
19 }
20
21 static get SCEPTER() {
22 return "s";
23 }
24
25 static get HORSE() {
26 return "h";
27 }
28
29 static get DRAGON() {
30 return "d";
31 }
32
33 static get CARDINAL() {
34 return "c";
35 }
36
37 static get WHOLE() {
38 return "w";
39 }
40
41 static get MARSHAL() {
42 return "m";
43 }
44
45 static get APRICOT() {
46 return "a";
47 }
48
49 static get PIECES() {
50 return (
51 ChessRules.PIECES.concat([
52 V.GILDING, V.SCEPTER, V.HORSE, V.DRAGON,
53 V.CARDINAL, V.WHOLE, V.MARSHAL, V.APRICOT])
54 );
55 }
56
57 getPpath(b) {
58 const prefix = (ChessRules.PIECES.includes(b[1]) ? "" : "Pandemonium/");
59 return prefix + b;
60 }
61
62 static get size() {
63 return { x: 10, y: 10};
64 }
65
66 getColor(i, j) {
67 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
68 return this.board[i][j].charAt(0);
69 }
70
71 getPiece(i, j) {
72 if (i >= V.size.x) return V.RESERVE_PIECES[j];
73 return this.board[i][j].charAt(1);
74 }
75
76 setOtherVariables(fen) {
77 super.setOtherVariables(fen);
78 // Sub-turn is useful only at first move...
79 this.subTurn = 1;
80 // Also init reserves (used by the interface to show landable pieces)
81 const reserve =
82 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
83 this.reserve = {
84 w: {
85 [V.PAWN]: reserve[0],
86 [V.ROOK]: reserve[1],
87 [V.KNIGHT]: reserve[2],
88 [V.BISHOP]: reserve[3],
89 [V.QUEEN]: reserve[4],
90 [V.CARDINAL]: reserve[5],
91 [V.MARSHAL]: reserve[6],
92 },
93 b: {
94 [V.PAWN]: reserve[7],
95 [V.ROOK]: reserve[8],
96 [V.KNIGHT]: reserve[9],
97 [V.BISHOP]: reserve[10],
98 [V.QUEEN]: reserve[11],
99 [V.CARDINAL]: reserve[12],
100 [V.MARSHAL]: reserve[13]
101 }
102 };
103 }
104
105 static IsGoodEnpassant(enpassant) {
106 if (enpassant != "-") {
107 const squares = enpassant.split(",");
108 if (squares.length > 2) return false;
109 for (let sq of squares) {
110 if (!sq.match(/[a-j0-9]/)) return false;
111 }
112 }
113 return true;
114 }
115
116 static IsGoodFen(fen) {
117 if (!ChessRules.IsGoodFen(fen)) return false;
118 const fenParsed = V.ParseFen(fen);
119 // Check reserves
120 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{14,14}$/))
121 return false;
122 return true;
123 }
124
125 static ParseFen(fen) {
126 const fenParts = fen.split(" ");
127 return Object.assign(
128 ChessRules.ParseFen(fen),
129 { reserve: fenParts[5] }
130 );
131 }
132
133 getFen() {
134 return super.getFen() + " " + this.getReserveFen();
135 }
136
137 getFenForRepeat() {
138 return super.getFenForRepeat() + "_" + this.getReserveFen();
139 }
140
141 getReserveFen() {
142 let counts = new Array(14);
143 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
144 counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]];
145 counts[7 + i] = this.reserve["b"][V.RESERVE_PIECES[i]];
146 }
147 return counts.join("");
148 }
149
150 setFlags(fenflags) {
151 // white a-castle, h-castle, king pos, then same for black.
152 this.castleFlags = { w: [-1, -1, -1], b: [-1, -1, -1] };
153 for (let i = 0; i < 6; i++) {
154 this.castleFlags[i < 3 ? "w" : "b"][i % 3] =
155 V.ColumnToCoord(fenflags.charAt(i));
156 }
157 }
158
159 static GenRandInitFen(randomness) {
160 // No randomization here for now (but initial setup choice)
161 return (
162 "rnbqkmcbnr/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/RNBQKMCBNR " +
163 "w 0 ajeaje - 00000000000000"
164 );
165 // TODO later: randomization too --> 2 bishops, not next to each other.
166 // then knights next to bishops. Then other pieces (...).
167 }
168
169 getEnpassantFen() {
170 const L = this.epSquares.length;
171 if (!this.epSquares[L - 1]) return "-"; //no en-passant
172 let res = "";
173 this.epSquares[L - 1].forEach(sq => {
174 res += V.CoordsToSquare(sq) + ",";
175 });
176 return res.slice(0, -1); //remove last comma
177 }
178
179 getEpSquare(moveOrSquare) {
180 if (!moveOrSquare) return undefined;
181 if (typeof moveOrSquare === "string") {
182 const square = moveOrSquare;
183 if (square == "-") return undefined;
184 let res = [];
185 square.split(",").forEach(sq => {
186 res.push(V.SquareToCoords(sq));
187 });
188 return res;
189 }
190 // Argument is a move:
191 const move = moveOrSquare;
192 const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x];
193 if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) {
194 const step = (ex - sx) / Math.abs(ex - sx);
195 let res = [{
196 x: sx + step,
197 y: sy
198 }];
199 if (sx + 2 * step != ex) {
200 // 3-squares jump
201 res.push({
202 x: sx + 2 * step,
203 y: sy
204 });
205 }
206 return res;
207 }
208 return undefined; //default
209 }
210
211 getReservePpath(index, color) {
212 const p = V.RESERVE_PIECES[index];
213 const prefix = (ChessRules.PIECES.includes(p) ? "" : "Pandemonium/");
214 return prefix + color + p;;
215 }
216
217 // Ordering on reserve pieces
218 static get RESERVE_PIECES() {
219 return (
220 [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.CARDINAL, V.MARSHAL]
221 );
222 }
223
224 getReserveMoves([x, y]) {
225 const color = this.turn;
226 const oppCol = V.GetOppCol(color);
227 const p = V.RESERVE_PIECES[y];
228 if (this.reserve[color][p] == 0) return [];
229 const bounds = (p == V.PAWN ? [1, V.size.x - 1] : [0, V.size.x]);
230 let moves = [];
231 for (let i = bounds[0]; i < bounds[1]; i++) {
232 for (let j = 0; j < V.size.y; j++) {
233 if (this.board[i][j] == V.EMPTY) {
234 let mv = new Move({
235 appear: [
236 new PiPo({
237 x: i,
238 y: j,
239 c: color,
240 p: p
241 })
242 ],
243 vanish: [],
244 start: { x: x, y: y }, //a bit artificial...
245 end: { x: i, y: j }
246 });
247 if (p == V.PAWN) {
248 // Do not drop on checkmate:
249 this.play(mv);
250 const res = (
251 this.underCheck(oppCol) && !this.atLeastOneMove("noReserve")
252 );
253 this.undo(mv);
254 if (res) continue;
255 }
256 moves.push(mv);
257 }
258 }
259 }
260 return moves;
261 }
262
263 static get PromoteMap() {
264 return {
265 r: 'd',
266 n: 's',
267 b: 'h',
268 c: 'w',
269 m: 'a'
270 };
271 }
272
273 getPotentialMovesFrom([x, y]) {
274 const c = this.getColor(x, y);
275 const oppCol = V.GetOppCol(c);
276 if (this.movesCount <= 1) {
277 if (this.kingPos[c][0] == x && this.kingPos[c][1] == y) {
278 // Pass (if setup is ok)
279 return [
280 new Move({
281 appear: [],
282 vanish: [],
283 start: { x: this.kingPos[c][0], y: this.kingPos[c][1] },
284 end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] }
285 })
286 ];
287 }
288 const firstRank = (this.movesCount == 0 ? 9 : 0);
289 // TODO: initDestFile currently hardcoded for deterministic setup
290 const initDestFile = new Map([[1, 2], [8, 7]]);
291 // Only option is knight --> bishop swap:
292 if (
293 x == firstRank &&
294 !!initDestFile.get(y) &&
295 this.getPiece(x, y) == V.KNIGHT
296 ) {
297 const destFile = initDestFile.get(y);
298 return [
299 new Move({
300 appear: [
301 new PiPo({
302 x: x,
303 y: destFile,
304 c: c,
305 p: V.KNIGHT
306 }),
307 new PiPo({
308 x: x,
309 y: y,
310 c: c,
311 p: V.BISHOP
312 })
313 ],
314 vanish: [
315 new PiPo({
316 x: x,
317 y: y,
318 c: c,
319 p: V.KNIGHT
320 }),
321 new PiPo({
322 x: x,
323 y: destFile,
324 c: c,
325 p: V.BISHOP
326 })
327 ],
328 start: { x: x, y: y },
329 end: { x: x, y: destFile }
330 })
331 ];
332 }
333 return [];
334 }
335 // Normal move (after initial setup)
336 if (x >= V.size.x) return this.getReserveMoves(x, y);
337 const p = this.getPiece(x, y);
338 const sq = [x, y];
339 let moves = [];
340 if (ChessRules.PIECES.includes(p))
341 moves = super.getPotentialMovesFrom(sq);
342 if ([V.GILDING, V.APRICOT, V.WHOLE].includes(p))
343 moves = super.getPotentialQueenMoves(sq);
344 switch (p) {
345 case V.SCEPTER:
346 moves = this.getPotentialScepterMoves(sq);
347 break;
348 case V.HORSE:
349 moves = this.getPotentialHorseMoves(sq);
350 break;
351 case V.DRAGON:
352 moves = this.getPotentialDragonMoves(sq);
353 break;
354 case V.CARDINAL:
355 moves = this.getPotentialCardinalMoves(sq);
356 break;
357 case V.MARSHAL:
358 moves = this.getPotentialMarshalMoves(sq);
359 break;
360 }
361 // Maybe apply promotions:
362 if (Object.keys(V.PromoteMap).includes(p)) {
363 const promoted = V.PromoteMap[p];
364 const lastRank = (c == 'w' ? 0 : 9);
365 let promotions = [];
366 moves.forEach(m => {
367 if (m.start.x == lastRank || m.end.x == lastRank) {
368 let pMove = JSON.parse(JSON.stringify(m));
369 pMove.appear[0].p = promoted;
370 promotions.push(pMove);
371 }
372 });
373 Array.prototype.push.apply(moves, promotions);
374 }
375 return moves;
376 }
377
378 getPotentialPawnMoves([x, y]) {
379 const color = this.turn;
380 const shiftX = V.PawnSpecs.directions[color];
381 let moves = [];
382 if (this.board[x + shiftX][y] == V.EMPTY) {
383 this.addPawnMoves([x, y], [x + shiftX, y], moves);
384 if ((color == 'w' && x >= V.size.x - 3) || (color == 'b' && x <= 3)) {
385 if (this.board[x + 2 * shiftX][y] == V.EMPTY) {
386 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
387 if (
388 (
389 (color == 'w' && x >= V.size.x - 2) ||
390 (color == 'b' && x <= 2)
391 )
392 &&
393 this.board[x + 3 * shiftX][y] == V.EMPTY
394 ) {
395 moves.push(this.getBasicMove([x, y], [x + 3 * shiftX, y]));
396 }
397 }
398 }
399 }
400 for (let shiftY of [-1, 1]) {
401 if (y + shiftY >= 0 && y + shiftY < V.size.y) {
402 if (
403 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
404 this.canTake([x, y], [x + shiftX, y + shiftY])
405 ) {
406 this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves);
407 }
408 }
409 }
410 Array.prototype.push.apply(
411 moves,
412 this.getEnpassantCaptures([x, y], shiftX)
413 );
414 return moves;
415 }
416
417 getPotentialMarshalMoves(sq) {
418 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
419 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
420 );
421 }
422
423 getPotentialCardinalMoves(sq) {
424 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
425 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
426 );
427 }
428
429 getPotentialScepterMoves(sq) {
430 const steps =
431 V.steps[V.KNIGHT].concat(V.steps[V.BISHOP]).concat(V.steps[V.ROOK]);
432 return this.getSlideNJumpMoves(sq, steps, "oneStep");
433 }
434
435 getPotentialHorseMoves(sq) {
436 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
437 this.getSlideNJumpMoves(sq, V.steps[V.ROOK], "oneStep"));
438 }
439
440 getPotentialDragonMoves(sq) {
441 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
442 this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"));
443 }
444
445 getEnpassantCaptures([x, y], shiftX) {
446 const Lep = this.epSquares.length;
447 const epSquare = this.epSquares[Lep - 1];
448 let moves = [];
449 if (!!epSquare) {
450 for (let epsq of epSquare) {
451 // TODO: some redundant checks
452 if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) {
453 let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]);
454 // WARNING: the captured pawn may be diagonally behind us,
455 // if it's a 3-squares jump and we take on 1st passing square
456 const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX;
457 enpassantMove.vanish.push({
458 x: px,
459 y: epsq.y,
460 p: "p",
461 c: this.getColor(px, epsq.y)
462 });
463 moves.push(enpassantMove);
464 }
465 }
466 }
467 return moves;
468 }
469
470 getPotentialKingMoves(sq) {
471 // Initialize with normal moves
472 let moves = this.getSlideNJumpMoves(
473 sq,
474 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
475 "oneStep"
476 );
477 const c = this.turn;
478 if (
479 this.castleFlags[c][0] < V.size.y ||
480 this.castleFlags[c][1] < V.size.y
481 ) {
482 moves = moves.concat(this.getCastleMoves(sq));
483 }
484 return moves;
485 }
486
487 getCastleMoves([x, y]) {
488 const c = this.getColor(x, y);
489 if (
490 ((c == 'w' && x == 9) || (c == 'b' && x == 0)) &&
491 y == this.castleFlags[c][2]
492 ) {
493 const finalSquares = [
494 [1, 2],
495 [7, 6]
496 ];
497 return super.getCastleMoves([x, y], finalSquares, false, [V.ROOK]);
498 }
499 return [];
500 }
501
502 isAttacked(sq, color) {
503 return (
504 this.isAttackedByPawn(sq, color) ||
505 this.isAttackedByRook(sq, color) ||
506 this.isAttackedByKnight(sq, color) ||
507 this.isAttackedByBishop(sq, color) ||
508 this.isAttackedByKing(sq, color) ||
509 this.isAttackedByQueens(sq, color) ||
510 this.isAttackedByScepter(sq, color) ||
511 this.isAttackedByDragon(sq, color) ||
512 this.isAttackedByHorse(sq, color) ||
513 this.isAttackedByMarshal(sq, color) ||
514 this.isAttackedByCardinal(sq, color)
515 );
516 }
517
518 isAttackedByQueens([x, y], color) {
519 // pieces: because queen = gilding = whole = apricot
520 const pieces = [V.QUEEN, V.GILDING, V.WHOLE, V.APRICOT];
521 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
522 for (let step of steps) {
523 let rx = x + step[0],
524 ry = y + step[1];
525 while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY) {
526 rx += step[0];
527 ry += step[1];
528 }
529 if (
530 V.OnBoard(rx, ry) &&
531 this.board[rx][ry] != V.EMPTY &&
532 pieces.includes(this.getPiece(rx, ry)) &&
533 this.getColor(rx, ry) == color
534 ) {
535 return true;
536 }
537 }
538 return false;
539 }
540
541 isAttackedByScepter(sq, color) {
542 const steps =
543 V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]);
544 return (
545 super.isAttackedBySlideNJump(sq, color, steps, V.SCEPTER, "oneStep")
546 );
547 }
548
549 isAttackedByHorse(sq, color) {
550 return (
551 super.isAttackedBySlideNJump(sq, color, V.steps[V.BISHOP], V.HORSE) ||
552 super.isAttackedBySlideNJump(
553 sq, color, V.steps[V.ROOK], V.HORSE, "oneStep")
554 );
555 }
556
557 isAttackedByDragon(sq, color) {
558 return (
559 super.isAttackedBySlideNJump(sq, color, V.steps[V.ROOK], V.DRAGON) ||
560 super.isAttackedBySlideNJump(
561 sq, color, V.steps[V.BISHOP], V.DRAGON, "oneStep")
562 );
563 }
564
565 isAttackedByMarshal(sq, color) {
566 return (
567 super.isAttackedBySlideNJump(sq, color, V.MARSHAL, V.steps[V.ROOK]) ||
568 super.isAttackedBySlideNJump(
569 sq,
570 color,
571 V.MARSHAL,
572 V.steps[V.KNIGHT],
573 "oneStep"
574 )
575 );
576 }
577
578 isAttackedByCardinal(sq, color) {
579 return (
580 super.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) ||
581 super.isAttackedBySlideNJump(
582 sq,
583 color,
584 V.CARDINAL,
585 V.steps[V.KNIGHT],
586 "oneStep"
587 )
588 );
589 }
590
591 getAllValidMoves() {
592 let moves = super.getAllPotentialMoves();
593 if (this.movesCount >= 2) {
594 const color = this.turn;
595 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
596 moves = moves.concat(
597 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
598 );
599 }
600 }
601 return this.filterValid(moves);
602 }
603
604 atLeastOneMove(noReserve) {
605 if (!super.atLeastOneMove()) {
606 if (!noReserve) {
607 // Search one reserve move
608 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
609 let moves = this.filterValid(
610 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
611 );
612 if (moves.length > 0) return true;
613 }
614 }
615 return false;
616 }
617 return true;
618 }
619
620 // Reverse 'PromoteMap'
621 static get P_CORRESPONDANCES() {
622 return {
623 d: 'r',
624 s: 'n',
625 h: 'b',
626 w: 'c',
627 a: 'm'
628 };
629 }
630
631 static MayDecode(piece) {
632 if (Object.keys(V.P_CORRESPONDANCES).includes(piece))
633 return V.P_CORRESPONDANCES[piece];
634 return piece;
635 }
636
637 play(move) {
638 move.subTurn = this.subTurn; //much easier
639 if (this.movesCount >= 2 || this.subTurn == 2 || move.vanish.length == 0) {
640 this.turn = V.GetOppCol(this.turn);
641 this.subTurn = 1;
642 this.movesCount++;
643 }
644 else this.subTurn = 2;
645 move.flags = JSON.stringify(this.aggregateFlags());
646 this.epSquares.push(this.getEpSquare(move));
647 V.PlayOnBoard(this.board, move);
648 this.postPlay(move);
649 }
650
651 updateCastleFlags(move, piece) {
652 if (piece == V.KING && move.appear.length == 2) {
653 // Castling (only move which disable flags)
654 this.castleFlags[move.appear[0].c][0] = 10;
655 this.castleFlags[move.appear[0].c][1] = 10;
656 }
657 }
658
659 postPlay(move) {
660 if (move.vanish.length == 0 && move.appear.length == 0) return;
661 super.postPlay(move);
662 const color = move.appear[0].c;
663 if (move.vanish.length == 0)
664 // Drop unpromoted piece:
665 this.reserve[color][move.appear[0].p]--;
666 else if (move.vanish.length == 2 && move.appear.length == 1)
667 // May capture a promoted piece:
668 this.reserve[color][V.MayDecode(move.vanish[1].p)]++;
669 }
670
671 undo(move) {
672 this.epSquares.pop();
673 this.disaggregateFlags(JSON.parse(move.flags));
674 V.UndoOnBoard(this.board, move);
675 if (this.movesCount >= 2 || this.subTurn == 1 || move.vanish.length == 0) {
676 this.turn = V.GetOppCol(this.turn);
677 this.movesCount--;
678 }
679 this.subTurn = move.subTurn;
680 this.postUndo(move);
681 }
682
683 postUndo(move) {
684 if (move.vanish.length == 0 && move.appear.length == 0) return;
685 super.postUndo(move);
686 const color = move.appear[0].c;
687 if (move.vanish.length == 0)
688 this.reserve[color][move.appear[0].p]++;
689 else if (move.vanish.length == 2 && move.appear.length == 1)
690 this.reserve[color][V.MayDecode(move.vanish[1].p)]--;
691 }
692
693 getCurrentScore() {
694 const c = this.turn,
695 oppCol = V.GetOppCol(this.turn);
696 let facingKings = false;
697 if (
698 this.kingPos[c][0] == this.kingPos[oppCol][0] ||
699 this.kingPos[c][1] == this.kingPos[oppCol][1]
700 ) {
701 facingKings = true;
702 let step = [
703 this.kingPos[oppCol][0] - this.kingPos[c][0],
704 this.kingPos[oppCol][1] - this.kingPos[c][1]
705 ];
706 if (step[0] != 0) step[0] /= Math.abs(step[0]);
707 else step[1] /= Math.abs(step[1]);
708 let [x, y] =
709 [ this.kingPos[c][0] + step[0], this.kingPos[c][1] + step[1] ];
710 while (x != this.kingPos[oppCol][0] || y != this.kingPos[oppCol][1]) {
711 if (this.board[x][y] != V.EMPTY) {
712 facingKings = false;
713 break;
714 }
715 x += step[0];
716 y += step[1];
717 }
718 }
719 if (facingKings) return (c == "w" ? "1-0" : "0-1");
720 if (!this.atLeastOneMove()) return (c == "w" ? "0-1" : "1-0");
721 return "*";
722 }
723
724 static get VALUES() {
725 return Object.assign(
726 {},
727 ChessRules.VALUES,
728 {
729 n: 2.5, //knight is weaker
730 g: 9,
731 s: 5,
732 h: 6,
733 d: 7,
734 c: 7,
735 w: 9,
736 m: 8,
737 a: 9
738 }
739 );
740 }
741
742 static get SEARCH_DEPTH() {
743 return 2;
744 }
745
746 getComputerMove() {
747 if (this.movesCount <= 1) {
748 // Special case: swap and pass at random
749 const moves1 = this.getAllValidMoves();
750 const m1 = moves1[randInt(moves1.length)];
751 this.play(m1);
752 if (m1.vanish.length == 0) {
753 this.undo(m1);
754 return m1;
755 }
756 const moves2 = this.getAllValidMoves();
757 const m2 = moves2[randInt(moves2.length)];
758 this.undo(m1);
759 return [m1, m2];
760 }
761 return super.getComputerMove();
762 }
763
764 evalPosition() {
765 let evaluation = super.evalPosition();
766 // Add reserves:
767 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
768 const p = V.RESERVE_PIECES[i];
769 evaluation += this.reserve["w"][p] * V.VALUES[p];
770 evaluation -= this.reserve["b"][p] * V.VALUES[p];
771 }
772 return evaluation;
773 }
774
775 getNotation(move) {
776 if (move.vanish.length == 0) {
777 if (move.appear.length == 0) return "pass";
778 const pieceName =
779 (move.appear[0].p == V.PAWN ? "" : move.appear[0].p.toUpperCase());
780 return pieceName + "@" + V.CoordsToSquare(move.end);
781 }
782 if (move.appear.length == 2) {
783 if (move.appear[0].p != V.KING)
784 return V.CoordsToSquare(move.start) + "S" + V.CoordsToSquare(move.end);
785 return (move.end.y < move.start.y ? "0-0" : "0-0-0");
786 }
787 let notation = super.getNotation(move);
788 if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p)
789 // Add promotion indication:
790 notation += "=" + move.appear[0].p.toUpperCase();
791 return notation;
792 }
793
794 };