0996a70f54f967bba8504a2c7915d8a80c49f6f4
1 import ChessRules
from "/base_rules.js";
3 export default class DynamoRules
extends ChessRules
{
5 // TODO? later, allow to push out pawns on a and h files
12 canIplay(side
, [x
, y
]) {
13 // Sometimes opponent's pieces can be moved directly
14 return this.turn
== side
;
17 setOtherVariables(fen
) {
18 super.setOtherVariables(fen
);
20 // Local stack of "action moves"
22 const amove
= V
.ParseFen(fen
).amove
;
24 const amoveParts
= amove
.split("/");
26 // No need for start & end
31 if (amoveParts
[i
] != "-") {
32 amoveParts
[i
].split(".").forEach(av
=> {
34 const xy
= V
.SquareToCoords(av
.substr(2));
35 move[i
== 0 ? "appear" : "vanish"].push(
46 this.amoves
.push(move);
48 // Stack "first moves" (on subTurn 1) to merge and check opposite moves
52 static ParseFen(fen
) {
54 ChessRules
.ParseFen(fen
),
55 { amove: fen
.split(" ")[4] }
59 static IsGoodFen(fen
) {
60 if (!ChessRules
.IsGoodFen(fen
)) return false;
61 const fenParts
= fen
.split(" ");
62 if (fenParts
.length
!= 5) return false;
63 if (fenParts
[4] != "-") {
64 // TODO: a single regexp instead.
65 // Format is [bpa2[.wpd3]] || '-'/[bbc3[.wrd5]] || '-'
66 const amoveParts
= fenParts
[4].split("/");
67 if (amoveParts
.length
!= 2) return false;
68 for (let part
of amoveParts
) {
70 for (let psq
of part
.split("."))
71 if (!psq
.match(/^[a-z]{3}[1-8]$/)) return false;
79 return super.getFen() + " " + this.getAmoveFen();
83 return super.getFenForRepeat() + "_" + this.getAmoveFen();
87 const L
= this.amoves
.length
;
88 if (L
== 0) return "-";
90 ["appear","vanish"].map(
92 if (this.amoves
[L
-1][mpart
].length
== 0) return "-";
94 this.amoves
[L
-1][mpart
].map(
96 const square
= V
.CoordsToSquare({ x: av
.x
, y: av
.y
});
97 return av
.c
+ av
.p
+ square
;
107 // Captures don't occur (only pulls & pushes)
111 // Step is right, just add (push/pull) moves in this direction
112 // Direction is assumed normalized.
113 getMovesInDirection([x
, y
], [dx
, dy
], nbSteps
) {
114 nbSteps
= nbSteps
|| 8; //max 8 steps anyway
115 let [i
, j
] = [x
+ dx
, y
+ dy
];
117 const color
= this.getColor(x
, y
);
118 const piece
= this.getPiece(x
, y
);
119 const lastRank
= (color
== 'w' ? 0 : 7);
121 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
122 if (i
== lastRank
&& piece
== V
.PAWN
) {
123 // Promotion by push or pull
124 V
.PawnSpecs
.promotions
.forEach(p
=> {
125 let move = super.getBasicMove([x
, y
], [i
, j
], { c: color
, p: p
});
129 else moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
130 if (++counter
> nbSteps
) break;
134 if (!V
.OnBoard(i
, j
) && piece
!= V
.KING
) {
135 // Add special "exit" move, by "taking king"
138 start: { x: x
, y: y
},
139 end: { x: this.kingPos
[color
][0], y: this.kingPos
[color
][1] },
141 vanish: [{ x: x
, y: y
, c: color
, p: piece
}]
148 // Normalize direction to know the step
149 getNormalizedDirection([dx
, dy
]) {
150 const absDir
= [Math
.abs(dx
), Math
.abs(dy
)];
152 if (absDir
[0] != 0 && absDir
[1] != 0 && absDir
[0] != absDir
[1])
154 divisor
= Math
.min(absDir
[0], absDir
[1]);
156 // Standard slider (or maybe a pawn or king: same)
157 divisor
= Math
.max(absDir
[0], absDir
[1]);
158 return [dx
/ divisor
, dy
/ divisor
];
161 // There was something on x2,y2, maybe our color, pushed or (self)pulled
162 isAprioriValidExit([x1
, y1
], [x2
, y2
], color2
, piece2
) {
163 const color1
= this.getColor(x1
, y1
);
164 const pawnShift
= (color1
== 'w' ? -1 : 1);
165 const lastRank
= (color1
== 'w' ? 0 : 7);
166 const deltaX
= Math
.abs(x1
- x2
);
167 const deltaY
= Math
.abs(y1
- y2
);
168 const checkSlider
= () => {
169 const dir
= this.getNormalizedDirection([x2
- x1
, y2
- y1
]);
170 let [i
, j
] = [x1
+ dir
[0], y1
+ dir
[1]];
171 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
175 return !V
.OnBoard(i
, j
);
177 switch (piece2
|| this.getPiece(x1
, y1
)) {
180 x1
+ pawnShift
== x2
&&
182 (color1
== color2
&& x2
== lastRank
&& y1
== y2
) ||
186 !V
.OnBoard(2 * x2
- x1
, 2 * y2
- y1
)
191 if (x1
!= x2
&& y1
!= y2
) return false;
192 return checkSlider();
195 deltaX
+ deltaY
== 3 &&
196 (deltaX
== 1 || deltaY
== 1) &&
197 !V
.OnBoard(2 * x2
- x1
, 2 * y2
- y1
)
200 if (deltaX
!= deltaY
) return false;
201 return checkSlider();
203 if (deltaX
!= 0 && deltaY
!= 0 && deltaX
!= deltaY
) return false;
204 return checkSlider();
209 !V
.OnBoard(2 * x2
- x1
, 2 * y2
- y1
)
215 isAprioriValidVertical([x1
, y1
], x2
) {
216 const piece
= this.getPiece(x1
, y1
);
217 const deltaX
= Math
.abs(x1
- x2
);
218 const startRank
= (this.getColor(x1
, y1
) == 'w' ? 6 : 1);
220 [V
.QUEEN
, V
.ROOK
].includes(piece
) ||
222 [V
.KING
, V
.PAWN
].includes(piece
) &&
225 (deltaX
== 2 && piece
== V
.PAWN
&& x1
== startRank
)
231 // NOTE: for pushes, play the pushed piece first.
232 // for pulls: play the piece doing the action first
233 // NOTE: to push a piece out of the board, make it slide until its king
234 getPotentialMovesFrom([x
, y
]) {
235 const color
= this.turn
;
236 const sqCol
= this.getColor(x
, y
);
237 const pawnShift
= (color
== 'w' ? -1 : 1);
238 const pawnStartRank
= (color
== 'w' ? 6 : 1);
239 const getMoveHash
= (m
) => {
240 return V
.CoordsToSquare(m
.start
) + V
.CoordsToSquare(m
.end
);
242 if (this.subTurn
== 1) {
243 const addMoves
= (dir
, nbSteps
) => {
245 this.getMovesInDirection([x
, y
], [-dir
[0], -dir
[1]], nbSteps
)
246 .filter(m
=> !movesHash
[getMoveHash(m
)]);
247 newMoves
.forEach(m
=> { movesHash
[getMoveHash(m
)] = true; });
248 Array
.prototype.push
.apply(moves
, newMoves
);
250 // Free to play any move (if piece of my color):
253 ? super.getPotentialMovesFrom([x
, y
])
255 // There may be several suicide moves: keep only one
257 moves
= moves
.filter(m
=> {
258 const suicide
= (m
.appear
.length
== 0);
260 if (hasExit
) return false;
265 // Structure to avoid adding moves twice (can be action & move)
267 moves
.forEach(m
=> { movesHash
[getMoveHash(m
)] = true; });
268 // [x, y] is pushed by 'color'
269 for (let step
of V
.steps
[V
.KNIGHT
]) {
270 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
273 this.board
[i
][j
] != V
.EMPTY
&&
274 this.getColor(i
, j
) == color
&&
275 this.getPiece(i
, j
) == V
.KNIGHT
280 for (let step
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
281 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
282 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
288 this.board
[i
][j
] != V
.EMPTY
&&
289 this.getColor(i
, j
) == color
291 const deltaX
= Math
.abs(i
- x
);
292 const deltaY
= Math
.abs(j
- y
);
293 switch (this.getPiece(i
, j
)) {
296 (x
- i
) / deltaX
== pawnShift
&&
300 if (sqCol
== color
&& deltaY
== 0) {
302 const maxSteps
= (i
== pawnStartRank
&& deltaX
== 1 ? 2 : 1);
303 addMoves(step
, maxSteps
);
305 else if (sqCol
!= color
&& deltaY
== 1 && deltaX
== 1)
311 if (deltaX
== 0 || deltaY
== 0) addMoves(step
);
314 if (deltaX
== deltaY
) addMoves(step
);
317 // All steps are valid for a queen:
321 if (deltaX
<= 1 && deltaY
<= 1) addMoves(step
, 1);
328 // If subTurn == 2 then we should have a first move,
329 // which restrict what we can play now: only in the first move direction
330 const L
= this.firstMove
.length
;
331 const fm
= this.firstMove
[L
-1];
333 (fm
.appear
.length
== 2 && fm
.vanish
.length
== 2) ||
334 (fm
.vanish
[0].c
== sqCol
&& sqCol
!= color
)
336 // Castle or again opponent color: no move playable then.
339 const piece
= this.getPiece(x
, y
);
340 const getPushExit
= () => {
341 // Piece at subTurn 1 exited: can I have caused the exit?
343 this.isAprioriValidExit(
345 [fm
.start
.x
, fm
.start
.y
],
350 const dir
= this.getNormalizedDirection(
351 [fm
.start
.x
- x
, fm
.start
.y
- y
]);
353 [V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece
)
356 return this.getMovesInDirection([x
, y
], dir
, nbSteps
);
360 const getPushMoves
= () => {
361 // Piece from subTurn 1 is still on board:
362 const dirM
= this.getNormalizedDirection(
363 [fm
.end
.x
- fm
.start
.x
, fm
.end
.y
- fm
.start
.y
]);
364 const dir
= this.getNormalizedDirection(
365 [fm
.start
.x
- x
, fm
.start
.y
- y
]);
366 // Normalized directions should match
367 if (dir
[0] == dirM
[0] && dir
[1] == dirM
[1]) {
368 // We don't know if first move is a pushed piece or normal move,
369 // so still must check if the push is valid.
370 const deltaX
= Math
.abs(fm
.start
.x
- x
);
371 const deltaY
= Math
.abs(fm
.start
.y
- y
);
374 if (x
== pawnStartRank
) {
376 (fm
.start
.x
- x
) * pawnShift
< 0 ||
379 (fm
.vanish
[0].c
== color
&& deltaY
> 0) ||
380 (fm
.vanish
[0].c
!= color
&& deltaY
== 0) ||
381 Math
.abs(fm
.end
.x
- fm
.start
.x
) > deltaX
||
382 fm
.end
.y
- fm
.start
.y
!= fm
.start
.y
- y
389 fm
.start
.x
- x
!= pawnShift
||
391 (fm
.vanish
[0].c
== color
&& deltaY
== 1) ||
392 (fm
.vanish
[0].c
!= color
&& deltaY
== 0) ||
393 fm
.end
.x
- fm
.start
.x
!= pawnShift
||
394 fm
.end
.y
- fm
.start
.y
!= fm
.start
.y
- y
402 (deltaX
+ deltaY
!= 3 || (deltaX
== 0 && deltaY
== 0)) ||
403 (fm
.end
.x
- fm
.start
.x
!= fm
.start
.x
- x
) ||
404 (fm
.end
.y
- fm
.start
.y
!= fm
.start
.y
- y
)
411 (deltaX
>= 2 || deltaY
>= 2) ||
412 (fm
.end
.x
- fm
.start
.x
!= fm
.start
.x
- x
) ||
413 (fm
.end
.y
- fm
.start
.y
!= fm
.start
.y
- y
)
419 if (deltaX
!= deltaY
) return [];
422 if (deltaX
!= 0 && deltaY
!= 0) return [];
425 if (deltaX
!= deltaY
&& deltaX
!= 0 && deltaY
!= 0) return [];
428 // Nothing should stand between [x, y] and the square fm.start
429 let [i
, j
] = [x
+ dir
[0], y
+ dir
[1]];
431 (i
!= fm
.start
.x
|| j
!= fm
.start
.y
) &&
432 this.board
[i
][j
] == V
.EMPTY
437 if (i
== fm
.start
.x
&& j
== fm
.start
.y
)
438 return this.getMovesInDirection([x
, y
], dir
);
442 const getPullExit
= () => {
443 // Piece at subTurn 1 exited: can I be pulled?
444 // Note: kings cannot suicide, so fm.vanish[0].p is not KING.
445 // Could be PAWN though, if a pawn was pushed out of board.
447 fm
.vanish
[0].p
!= V
.PAWN
&& //pawns cannot pull
448 this.isAprioriValidExit(
450 [fm
.start
.x
, fm
.start
.y
],
456 const dir
= this.getNormalizedDirection(
457 [fm
.start
.x
- x
, fm
.start
.y
- y
]);
458 const nbSteps
= (fm
.vanish
[0].p
== V
.KNIGHT
? 1 : null);
459 return this.getMovesInDirection([x
, y
], dir
, nbSteps
);
463 const getPullMoves
= () => {
464 if (fm
.vanish
[0].p
== V
.PAWN
)
467 const dirM
= this.getNormalizedDirection(
468 [fm
.end
.x
- fm
.start
.x
, fm
.end
.y
- fm
.start
.y
]);
469 const dir
= this.getNormalizedDirection(
470 [fm
.start
.x
- x
, fm
.start
.y
- y
]);
471 // Normalized directions should match
472 if (dir
[0] == dirM
[0] && dir
[1] == dirM
[1]) {
473 // Am I at the right distance?
474 const deltaX
= Math
.abs(x
- fm
.start
.x
);
475 const deltaY
= Math
.abs(y
- fm
.start
.y
);
477 (fm
.vanish
[0].p
== V
.KING
&& (deltaX
> 1 || deltaY
> 1)) ||
478 (fm
.vanish
[0].p
== V
.KNIGHT
&&
479 (deltaX
+ deltaY
!= 3 || deltaX
== 0 || deltaY
== 0))
483 // Nothing should stand between [x, y] and the square fm.start
484 let [i
, j
] = [x
+ dir
[0], y
+ dir
[1]];
486 (i
!= fm
.start
.x
|| j
!= fm
.start
.y
) &&
487 this.board
[i
][j
] == V
.EMPTY
492 if (i
== fm
.start
.x
&& j
== fm
.start
.y
)
493 return this.getMovesInDirection([x
, y
], dir
);
497 if (fm
.vanish
[0].c
!= color
) {
498 // Only possible action is a push:
499 if (fm
.appear
.length
== 0) return getPushExit();
500 return getPushMoves();
502 else if (sqCol
!= color
) {
503 // Only possible action is a pull, considering moving piece abilities
504 if (fm
.appear
.length
== 0) return getPullExit();
505 return getPullMoves();
508 // My color + my color: both actions possible
509 // Structure to avoid adding moves twice (can be action & move)
511 if (fm
.appear
.length
== 0) {
512 const pushes
= getPushExit();
513 pushes
.forEach(m
=> { movesHash
[getMoveHash(m
)] = true; });
515 pushes
.concat(getPullExit().filter(m
=> !movesHash
[getMoveHash(m
)]))
518 const pushes
= getPushMoves();
519 pushes
.forEach(m
=> { movesHash
[getMoveHash(m
)] = true; });
521 pushes
.concat(getPullMoves().filter(m
=> !movesHash
[getMoveHash(m
)]))
527 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
529 const c
= this.getColor(x
, y
);
530 const piece
= this.getPiece(x
, y
);
531 outerLoop: for (let step
of steps
) {
534 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
535 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
536 if (oneStep
) continue outerLoop
;
540 if (V
.OnBoard(i
, j
)) {
541 if (this.canTake([x
, y
], [i
, j
]))
542 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
545 // Add potential board exit (suicide), except for the king
546 if (piece
!= V
.KING
) {
548 start: { x: x
, y: y
},
549 end: { x: this.kingPos
[c
][0], y: this.kingPos
[c
][1] },
566 // Does m2 un-do m1 ? (to disallow undoing actions)
567 oppositeMoves(m1
, m2
) {
568 const isEqual
= (av1
, av2
) => {
569 for (let av
of av1
) {
570 const avInAv2
= av2
.find(elt
=> {
578 if (!avInAv2
) return false;
582 // All appear and vanish arrays must have the same length
583 const mL
= m1
.appear
.length
;
585 m2
.appear
.length
== mL
&&
586 m1
.vanish
.length
== mL
&&
587 m2
.vanish
.length
== mL
&&
588 isEqual(m1
.appear
, m2
.vanish
) &&
589 isEqual(m1
.vanish
, m2
.appear
)
593 getAmove(move1
, move2
) {
594 // Just merge (one is action one is move, one may be empty)
596 appear: move1
.appear
.concat(move2
.appear
),
597 vanish: move1
.vanish
.concat(move2
.vanish
)
602 const color
= this.turn
;
603 const La
= this.amoves
.length
;
604 if (this.subTurn
== 1) {
605 return moves
.filter(m
=> {
606 // A move is valid either if it doesn't result in a check,
607 // or if a second move is possible to counter the check
608 // (not undoing a potential move + action of the opponent)
610 let res
= this.underCheck(color
);
611 if (this.subTurn
== 2) {
612 let isOpposite
= La
> 0 && this.oppositeMoves(this.amoves
[La
-1], m
);
613 if (res
|| isOpposite
) {
614 const moves2
= this.getAllPotentialMoves();
615 for (let m2
of moves2
) {
617 const res2
= this.underCheck(color
);
618 const amove
= this.getAmove(m
, m2
);
620 La
> 0 && this.oppositeMoves(this.amoves
[La
-1], amove
);
622 if (!res2
&& !isOpposite
) {
633 if (La
== 0) return super.filterValid(moves
);
634 const Lf
= this.firstMove
.length
;
638 // Move shouldn't undo another:
639 const amove
= this.getAmove(this.firstMove
[Lf
-1], m
);
640 return !this.oppositeMoves(this.amoves
[La
-1], amove
);
646 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
647 for (let step
of steps
) {
648 let rx
= x
+ step
[0],
650 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
656 this.getPiece(rx
, ry
) == piece
&&
657 this.getColor(rx
, ry
) == color
659 // Continue some steps in the same direction (pull)
664 this.board
[rx
][ry
] == V
.EMPTY
&&
670 if (!V
.OnBoard(rx
, ry
)) return true;
671 // Step in the other direction (push)
676 this.board
[rx
][ry
] == V
.EMPTY
&&
682 if (!V
.OnBoard(rx
, ry
)) return true;
688 isAttackedByPawn([x
, y
], color
) {
689 // The king can be pushed out by a pawn on last rank or near the edge
690 const pawnShift
= (color
== "w" ? 1 : -1);
691 for (let i
of [-1, 1]) {
693 V
.OnBoard(x
+ pawnShift
, y
+ i
) &&
694 this.board
[x
+ pawnShift
][y
+ i
] != V
.EMPTY
&&
695 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
696 this.getColor(x
+ pawnShift
, y
+ i
) == color
698 if (!V
.OnBoard(x
- pawnShift
, y
- i
)) return true;
704 static OnTheEdge(x
, y
) {
705 return (x
== 0 || x
== 7 || y
== 0 || y
== 7);
708 isAttackedByKing([x
, y
], color
) {
709 // Attacked if I'm on the edge and the opponent king just next,
710 // but not on the edge.
711 if (V
.OnTheEdge(x
, y
)) {
712 for (let step
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
713 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
716 !V
.OnTheEdge(i
, j
) &&
717 this.board
[i
][j
] != V
.EMPTY
&&
718 this.getPiece(i
, j
) == V
.KING
719 // NOTE: since only one king of each color, and (x, y) is occupied
720 // by our king, no need to check other king's color.
729 // No consideration of color: all pieces could be played
730 getAllPotentialMoves() {
731 let potentialMoves
= [];
732 for (let i
= 0; i
< V
.size
.x
; i
++) {
733 for (let j
= 0; j
< V
.size
.y
; j
++) {
734 if (this.board
[i
][j
] != V
.EMPTY
) {
735 Array
.prototype.push
.apply(
737 this.getPotentialMovesFrom([i
, j
])
742 return potentialMoves
;
747 start: { x: -1, y: -1 },
748 end: { x: -1, y: -1 },
755 // A click to promote a piece on subTurn 2 would trigger this.
756 // For now it would then return [NaN, NaN] because surrounding squares
757 // have no IDs in the promotion modal. TODO: improve this?
758 if (isNaN(square
[0])) return null;
759 // If subTurn == 2 && square is empty && !underCheck && !isOpposite,
760 // then return an empty move, allowing to "pass" subTurn2
761 const La
= this.amoves
.length
;
762 const Lf
= this.firstMove
.length
;
765 this.board
[square
[0]][square
[1]] == V
.EMPTY
&&
766 !this.underCheck(this.turn
) &&
767 (La
== 0 || !this.oppositeMoves(this.amoves
[La
-1], this.firstMove
[Lf
-1]))
769 return this.getEmptyMove();
775 if (this.subTurn
== 1 && move.vanish
.length
== 0) {
776 // Patch to work with old format: (TODO: remove later)
780 const color
= this.turn
;
781 move.subTurn
= this.subTurn
; //for undo
782 const gotoNext
= (mv
) => {
783 const L
= this.firstMove
.length
;
784 this.amoves
.push(this.getAmove(this.firstMove
[L
-1], mv
));
785 this.turn
= V
.GetOppCol(color
);
789 move.flags
= JSON
.stringify(this.aggregateFlags());
790 V
.PlayOnBoard(this.board
, move);
791 if (this.subTurn
== 2) gotoNext(move);
794 this.firstMove
.push(move);
795 this.toNewKingPos(move);
797 // Condition is true on empty arrays:
798 this.getAllPotentialMoves().every(m
=> {
799 V
.PlayOnBoard(this.board
, m
);
800 this.toNewKingPos(m
);
801 const res
= this.underCheck(color
);
802 V
.UndoOnBoard(this.board
, m
);
803 this.toOldKingPos(m
);
807 // No valid move at subTurn 2
808 gotoNext(this.getEmptyMove());
810 this.toOldKingPos(move);
816 for (let a
of move.appear
)
817 if (a
.p
== V
.KING
) this.kingPos
[a
.c
] = [a
.x
, a
.y
];
821 if (move.start
.x
< 0) return;
822 this.toNewKingPos(move);
823 this.updateCastleFlags(move);
826 updateCastleFlags(move) {
827 const firstRank
= { 'w': V
.size
.x
- 1, 'b': 0 };
828 for (let v
of move.vanish
) {
829 if (v
.p
== V
.KING
) this.castleFlags
[v
.c
] = [V
.size
.y
, V
.size
.y
];
830 else if (v
.x
== firstRank
[v
.c
] && this.castleFlags
[v
.c
].includes(v
.y
)) {
831 const flagIdx
= (v
.y
== this.castleFlags
[v
.c
][0] ? 0 : 1);
832 this.castleFlags
[v
.c
][flagIdx
] = V
.size
.y
;
838 if (!!move.ignore
) return; //TODO: remove that later
839 this.disaggregateFlags(JSON
.parse(move.flags
));
840 V
.UndoOnBoard(this.board
, move);
841 if (this.subTurn
== 1) {
843 this.turn
= V
.GetOppCol(this.turn
);
846 if (move.subTurn
== 1) this.firstMove
.pop();
847 this.subTurn
= move.subTurn
;
848 this.toOldKingPos(move);
852 // (Potentially) Reset king position
853 for (let v
of move.vanish
)
854 if (v
.p
== V
.KING
) this.kingPos
[v
.c
] = [v
.x
, v
.y
];
858 let moves
= this.getAllValidMoves();
859 if (moves
.length
== 0) return null;
860 // "Search" at depth 1 for now
861 const maxeval
= V
.INFINITY
;
862 const color
= this.turn
;
864 start: { x: -1, y: -1 },
865 end: { x: -1, y: -1 },
871 if (this.turn
!= color
) m
.eval
= this.evalPosition();
873 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
874 const moves2
= this.getAllValidMoves().concat([emptyMove
]);
876 moves2
.forEach(m2
=> {
878 const score
= this.getCurrentScore();
880 if (score
!= "1/2") {
881 if (score
!= "*") mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
882 else mvEval
= this.evalPosition();
885 (color
== 'w' && mvEval
> m
.eval
) ||
886 (color
== 'b' && mvEval
< m
.eval
)
896 moves
.sort((a
, b
) => {
897 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
899 let candidates
= [0];
900 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
902 const mIdx
= candidates
[randInt(candidates
.length
)];
903 if (!moves
[mIdx
].next
) return moves
[mIdx
];
904 const move2
= moves
[mIdx
].next
;
905 delete moves
[mIdx
]["next"];
906 return [moves
[mIdx
], move2
];
910 if (move.start
.x
< 0)
911 // A second move is always required, but may be empty
913 const initialSquare
= V
.CoordsToSquare(move.start
);
914 const finalSquare
= V
.CoordsToSquare(move.end
);
915 if (move.appear
.length
== 0)
916 // Pushed or pulled out of the board
917 return initialSquare
+ "R";
918 return move.appear
[0].p
.toUpperCase() + initialSquare
+ finalSquare
;