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