1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class PacosakoRules
extends ChessRules
{
6 static get IMAGE_EXTENSION() {
10 // Unions (left = white if upperCase, black otherwise)
38 // Arobase is character 64
39 return f
.charCodeAt() <= 90 ? "w" + f
.toLowerCase() : "b" + f
;
42 static IsGoodPosition(position
) {
43 if (position
.length
== 0) return false;
44 const rows
= position
.split("/");
45 if (rows
.length
!= V
.size
.x
) return false;
46 let kingSymb
= ['k', 'g', 'm', 'u', 'x', 'z', '@'];
47 let kings
= { 'k': 0, 'K': 0 };
48 for (let row
of rows
) {
50 for (let i
= 0; i
< row
.length
; i
++) {
51 if (!!(row
[i
].toLowerCase().match(/[a-z@]/))) {
53 if (kingSymb
.includes(row
[i
])) kings
['k']++;
54 // Not "else if", if two kings dancing together
55 if (kingSymb
.some(s
=> row
[i
] == s
.toUpperCase())) kings
['K']++;
58 const num
= parseInt(row
[i
], 10);
59 if (isNaN(num
) || num
<= 0) return false;
63 if (sumElts
!= V
.size
.y
) return false;
65 // Both kings should be on board. Exactly one per color.
66 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
71 return "Pacosako/" + b
;
75 if (ChessRules
.PIECES
.includes(m
.appear
[0].p
)) return super.getPPpath(m
);
76 // For an union, show only relevant piece:
77 // The color must be deduced from the move: reaching final rank of who?
78 const color
= (m
.appear
[0].x
== 0 ? 'w' : 'b');
79 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
80 return "Pacosako/" + color
+ up
[color
];
83 canTake([x1
, y1
], [x2
, y2
]) {
84 const p1
= this.board
[x1
][y1
].charAt(1);
85 if (!(ChessRules
.PIECES
.includes(p1
))) return false;
86 const p2
= this.board
[x2
][y2
].charAt(1);
87 if (!(ChessRules
.PIECES
.includes(p2
))) return true;
88 const c1
= this.board
[x1
][y1
].charAt(0);
89 const c2
= this.board
[x2
][y2
].charAt(0);
93 canIplay(side
, [x
, y
]) {
97 !(ChessRules
.PIECES
.includes(this.board
[x
][y
].charAt(1))) ||
98 this.board
[x
][y
].charAt(0) == side
104 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
105 const fenRows
= V
.ParseFen(fen
).position
.split("/");
106 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
107 const kingSymb
= ['k', 'g', 'm', 'u', 'x', 'z', '@'];
108 for (let i
= 0; i
< fenRows
.length
; i
++) {
110 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
111 const c
= fenRows
[i
].charAt(j
);
112 if (!!(c
.toLowerCase().match(/[a-z@]/))) {
113 if (kingSymb
.includes(c
))
114 this.kingPos
["b"] = [i
, k
];
115 // Not "else if", in case of two kings dancing together
116 if (kingSymb
.some(s
=> c
== s
.toUpperCase()))
117 this.kingPos
["w"] = [i
, k
];
120 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
121 if (!isNaN(num
)) k
+= num
- 1;
128 setOtherVariables(fen
) {
129 super.setOtherVariables(fen
);
130 // Stack of "last move" only for intermediate chaining
131 this.lastMoveEnd
= [null];
132 // Local stack of non-capturing union moves:
134 const umove
= V
.ParseFen(fen
).umove
;
135 if (umove
== "-") this.umoves
.push(null);
138 start: ChessRules
.SquareToCoords(umove
.substr(0, 2)),
139 end: ChessRules
.SquareToCoords(umove
.substr(2))
142 // Local stack of positions to avoid redundant moves:
143 this.repetitions
= [];
146 static IsGoodFen(fen
) {
147 if (!ChessRules
.IsGoodFen(fen
)) return false;
148 const fenParts
= fen
.split(" ");
149 if (fenParts
.length
!= 6) return false;
150 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
155 static IsGoodFlags(flags
) {
156 // 4 for castle + 16 for pawns
157 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
161 super.setFlags(fenflags
); //castleFlags
163 w: [...Array(8)], //pawns can move 2 squares?
166 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
167 for (let c
of ["w", "b"]) {
168 for (let i
= 0; i
< 8; i
++)
169 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
174 return [this.castleFlags
, this.pawnFlags
];
177 disaggregateFlags(flags
) {
178 this.castleFlags
= flags
[0];
179 this.pawnFlags
= flags
[1];
184 move.vanish
.length
== 1 &&
185 !(ChessRules
.PIECES
.includes(move.appear
[0].p
)) &&
186 move.appear
[0].p
== move.vanish
[0].p
//not a promotion
189 return { start: move.start
, end: move.end
};
194 static ParseFen(fen
) {
195 const fenParts
= fen
.split(" ");
196 return Object
.assign(
197 ChessRules
.ParseFen(fen
),
198 { umove: fenParts
[5] }
202 static GenRandInitFen(randomness
) {
203 // Add 16 pawns flags + empty umove:
204 return ChessRules
.GenRandInitFen(randomness
)
205 .slice(0, -2) + "1111111111111111 - -";
209 let fen
= super.getFlagsFen();
211 for (let c
of ["w", "b"])
212 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
217 const L
= this.umoves
.length
;
221 : ChessRules
.CoordsToSquare(this.umoves
[L
- 1].start
) +
222 ChessRules
.CoordsToSquare(this.umoves
[L
- 1].end
)
227 return super.getFen() + " " + this.getUmoveFen();
231 return super.getFenForRepeat() + "_" + this.getUmoveFen();
235 const p
= this.board
[i
][j
].charAt(1);
236 if (ChessRules
.PIECES
.includes(p
)) return super.getColor(i
, j
);
237 return this.turn
; //union: I can use it, so it's "my" color...
240 getPiece(i
, j
, color
) {
241 const p
= this.board
[i
][j
].charAt(1);
242 if (ChessRules
.PIECES
.includes(p
)) return p
;
243 const c
= this.board
[i
][j
].charAt(0);
244 // NOTE: this.turn == HACK, but should work...
245 color
= color
|| this.turn
;
246 return V
.UNIONS
[p
][c
== color
? 0 : 1];
249 getUnionPieces(color
, code
) {
250 const pieces
= V
.UNIONS
[code
];
252 w: pieces
[color
== 'w' ? 0 : 1],
253 b: pieces
[color
== 'b' ? 0 : 1]
257 // p1: white piece, p2: black piece
258 getUnionCode(p1
, p2
) {
260 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p1
&& v
[1] == p2
)
262 const c
= (uIdx
>= 0 ? 'w' : 'b');
265 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p2
&& v
[1] == p1
)
268 return { c: c
, p: Object
.keys(V
.UNIONS
)[uIdx
] };
271 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
272 const L
= this.lastMoveEnd
.length
;
273 const lm
= this.lastMoveEnd
[L
-1];
274 const piece
= (!!lm
? lm
.p : null);
275 const initColor
= (!!piece
? this.turn : this.board
[sx
][sy
].charAt(0));
276 const initPiece
= (piece
|| this.board
[sx
][sy
].charAt(1));
278 const oppCol
= V
.GetOppCol(c
);
279 if (!!tr
&& !(ChessRules
.PIECES
.includes(initPiece
))) {
280 // Transformation computed without taking union into account
281 const up
= this.getUnionPieces(initColor
, initPiece
);
282 let args
= [tr
.p
, up
[oppCol
]];
283 if (c
== 'b') args
= args
.reverse();
284 const cp
= this.getUnionCode(args
[0], args
[1]);
289 // - union to free square (other cases are illegal: return null)
290 // - normal piece to free square,
291 // to enemy normal piece, or
292 // to union (releasing our piece)
294 start: { x: sx
, y: sy
},
295 end: { x: ex
, y: ey
},
308 // Treat free square cases first:
309 if (this.board
[ex
][ey
] == V
.EMPTY
) {
314 c: !!tr
? tr
.c : initColor
,
315 p: !!tr
? tr
.p : initPiece
320 // Now the two cases with union / release:
321 const destColor
= this.board
[ex
][ey
].charAt(0);
322 const destPiece
= this.board
[ex
][ey
].charAt(1);
331 if (ChessRules
.PIECES
.includes(destPiece
)) {
332 // Normal piece: just create union
333 let args
= [!!tr
? tr
.p : initPiece
, destPiece
];
334 if (c
== 'b') args
= args
.reverse();
335 const cp
= this.getUnionCode(args
[0], args
[1]);
346 // Releasing a piece in an union: keep track of released piece
347 const up
= this.getUnionPieces(destColor
, destPiece
);
348 let args
= [!!tr
? tr
.p : initPiece
, up
[oppCol
]];
349 if (c
== 'b') args
= args
.reverse();
350 const cp
= this.getUnionCode(args
[0], args
[1]);
359 // In move.end, to be sent to the server
360 mv
.end
.released
= up
[c
];
364 getPotentialMovesFrom([x
, y
]) {
365 const L
= this.lastMoveEnd
.length
;
366 const lm
= this.lastMoveEnd
[L
-1];
367 if (!!lm
&& (x
!= lm
.x
|| y
!= lm
.y
)) return [];
368 const piece
= (!!lm
? lm
.p : this.getPiece(x
, y
));
370 var saveSquare
= this.board
[x
][y
];
371 this.board
[x
][y
] = this.turn
+ piece
;
377 const firstRank
= (c
== 'w' ? 7 : 0);
378 baseMoves
= this.getPotentialPawnMoves([x
, y
]).filter(m
=> {
379 // Skip forbidden 2-squares jumps (except from first rank)
380 // Also skip unions capturing en-passant (not allowed).
383 m
.start
.x
== firstRank
||
384 Math
.abs(m
.end
.x
- m
.start
.x
) == 1 ||
385 this.pawnFlags
[c
][m
.start
.y
]
389 this.board
[x
][y
].charAt(1) == V
.PAWN
||
397 baseMoves
= this.getPotentialRookMoves([x
, y
]);
400 baseMoves
= this.getPotentialKnightMoves([x
, y
]);
403 baseMoves
= this.getPotentialBishopMoves([x
, y
]);
406 baseMoves
= this.getPotentialQueenMoves([x
, y
]);
409 baseMoves
= this.getPotentialKingMoves([x
, y
]);
412 // When a pawn in an union reaches final rank with a non-standard
413 // promotion move: apply promotion anyway
415 const oppCol
= V
.GetOppCol(c
);
416 const oppLastRank
= (c
== 'w' ? 7 : 0);
417 baseMoves
.forEach(m
=> {
419 m
.end
.x
== oppLastRank
&&
420 ['c', 'd', 'e', 'f', 'g'].includes(m
.appear
[0].p
)
422 // Move to first rank, which is last rank for opponent's pawn.
423 // => Show promotion choices.
424 // Find our piece in union (not a pawn)
425 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
426 // merge with all potential promotion pieces + push (loop)
427 for (let promotionPiece
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
428 let args
= [up
[c
], promotionPiece
];
429 if (c
== 'b') args
= args
.reverse();
430 const cp
= this.getUnionCode(args
[0], args
[1]);
431 let cpMove
= JSON
.parse(JSON
.stringify(m
));
432 cpMove
.appear
[0].c
= cp
.c
;
433 cpMove
.appear
[0].p
= cp
.p
;
439 m
.vanish
.length
> 0 &&
440 m
.vanish
[0].p
== V
.PAWN
&&
441 m
.start
.y
!= m
.end
.y
&&
442 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
445 // No en-passant inside a chaining
447 // Fix en-passant capture: union type, maybe released piece too
448 const cs
= [m
.end
.x
+ (c
== 'w' ? 1 : -1), m
.end
.y
];
449 const code
= this.board
[cs
[0]][cs
[1]].charAt(1);
450 if (code
== V
.PAWN
) {
451 // Simple en-passant capture (usual: just form union)
456 // An union pawn + something just moved two squares
457 const color
= this.board
[cs
[0]][cs
[1]].charAt(0);
458 const up
= this.getUnionPieces(color
, code
);
459 m
.end
.released
= up
[c
];
460 let args
= [V
.PAWN
, up
[oppCol
]];
461 if (c
== 'b') args
= args
.reverse();
462 const cp
= this.getUnionCode(args
[0], args
[1]);
463 m
.appear
[0].c
= cp
.c
;
464 m
.appear
[0].p
= cp
.p
;
470 if (!!lm
) this.board
[x
][y
] = saveSquare
;
474 getEpSquare(moveOrSquare
) {
475 if (typeof moveOrSquare
=== "string") {
476 const square
= moveOrSquare
;
477 if (square
== "-") return undefined;
478 return V
.SquareToCoords(square
);
480 const move = moveOrSquare
;
481 const s
= move.start
,
485 Math
.abs(s
.x
- e
.x
) == 2 &&
486 this.getPiece(s
.x
, s
.y
, this.turn
) == V
.PAWN
496 // Does m2 un-do m1 ? (to disallow undoing union moves)
497 oppositeMoves(m1
, m2
) {
500 !(ChessRules
.PIECES
.includes(m2
.appear
[0].p
)) &&
501 m2
.vanish
.length
== 1 &&
503 m1
.start
.x
== m2
.end
.x
&&
504 m1
.end
.x
== m2
.start
.x
&&
505 m1
.start
.y
== m2
.end
.y
&&
506 m1
.end
.y
== m2
.start
.y
510 getCastleMoves([x
, y
]) {
511 const c
= this.getColor(x
, y
);
512 const oppCol
= V
.GetOppCol(c
);
514 const finalSquares
= [ [2, 3], [6, 5] ];
515 castlingCheck: for (let castleSide
= 0; castleSide
< 2; castleSide
++) {
516 if (this.castleFlags
[c
][castleSide
] >= 8) continue;
517 const rookPos
= this.castleFlags
[c
][castleSide
];
518 const castlingColor
= this.board
[x
][rookPos
].charAt(0);
519 const castlingPiece
= this.board
[x
][rookPos
].charAt(1);
521 // Nothing on the path of the king ?
522 const finDist
= finalSquares
[castleSide
][0] - y
;
523 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
525 let kingSquares
= [y
];
529 this.board
[x
][i
] != V
.EMPTY
&&
530 (this.getColor(x
, i
) != c
|| ![y
, rookPos
].includes(i
))
533 continue castlingCheck
;
537 } while (i
!= finalSquares
[castleSide
][0]);
538 // No checks on the path of the king ?
539 if (this.isAttacked(kingSquares
, oppCol
)) continue castlingCheck
;
541 // Nothing on the path to the rook?
542 step
= castleSide
== 0 ? -1 : 1;
543 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
544 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
547 // Nothing on final squares, except maybe king and castling rook?
548 for (i
= 0; i
< 2; i
++) {
550 finalSquares
[castleSide
][i
] != rookPos
&&
551 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
553 finalSquares
[castleSide
][i
] != y
||
554 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
557 continue castlingCheck
;
566 y: finalSquares
[castleSide
][0],
572 y: finalSquares
[castleSide
][1],
578 // King might be initially disguised (Titan...)
579 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
580 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: castlingColor
})
583 Math
.abs(y
- rookPos
) <= 2
584 ? { x: x
, y: rookPos
}
585 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
593 getEnpassantCaptures(sq
, shiftX
) {
594 // HACK: when artificially change turn, do not consider en-passant
595 const mcMod2
= this.movesCount
% 2;
597 if ((c
== 'w' && mcMod2
== 1) || (c
== 'b' && mcMod2
== 0)) return [];
598 return super.getEnpassantCaptures(sq
, shiftX
);
601 isAttacked_aux(files
, color
, positions
, fromSquare
, released
) {
602 // "positions" = array of FENs to detect infinite loops. Example:
603 // r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/P1eDP3/3B1PP1/R3K1NR,
604 // Bxd2 Bxc3 Bxb4 Bxc3 Bxb4 etc.
606 fen: super.getBaseFen(),
611 positions
.some(p
=> {
613 p
.piece
== newPos
.piece
&&
614 p
.fen
== newPos
.fen
&&
615 p
.from == newPos
.from
619 // Start of an infinite loop: exit
622 positions
.push(newPos
);
623 const rank
= (color
== 'w' ? 0 : 7);
624 const moves
= this.getPotentialMovesFrom(fromSquare
);
625 if (moves
.some(m
=> m
.end
.x
== rank
&& files
.includes(m
.end
.y
)))
628 for (let m
of moves
) {
629 if (!!m
.end
.released
) {
630 // Turn won't change since !!m.released
632 const res
= this.isAttacked_aux(
633 files
, color
, positions
, [m
.end
.x
, m
.end
.y
], m
.end
.released
);
635 if (res
) return true;
641 isAttacked(files
, color
) {
642 const rank
= (color
== 'w' ? 0 : 7);
643 // Since it's too difficult (impossible?) to search from the square itself,
644 // let's adopt a suboptimal but working strategy: find all attacks.
646 // Artificial turn change is required:
649 outerLoop: for (let i
=0; i
<8; i
++) {
650 for (let j
=0; j
<8; j
++) {
651 // Attacks must start from a normal piece, not an union.
652 // Therefore, the following test is correct.
654 this.board
[i
][j
] != V
.EMPTY
&&
655 // Do not start with king (irrelevant, and lead to infinite calls)
656 [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].includes(
657 this.board
[i
][j
].charAt(1)) &&
658 this.board
[i
][j
].charAt(0) == color
661 const moves
= this.getPotentialMovesFrom([i
, j
]);
662 if (moves
.some(m
=> m
.end
.x
== rank
&& files
.includes(m
.end
.y
))) {
666 for (let m
of moves
) {
667 if (!!m
.end
.released
) {
668 // Turn won't change since !!m.released
671 res
= this.isAttacked_aux(
672 files
, color
, positions
, [m
.end
.x
, m
.end
.y
], m
.end
.released
);
674 if (res
) break outerLoop
;
684 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
685 for (let step
of steps
) {
686 let rx
= x
+ step
[0],
688 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
694 this.board
[rx
][ry
] != V
.EMPTY
&&
695 this.getPiece(rx
, ry
) == piece
&&
696 this.getColor(rx
, ry
) == color
&&
697 this.canTake([rx
, ry
], [x
, y
]) //TODO: necessary line?
698 //If not, generic method is OK
706 // Do not consider checks, except to forbid castling
712 if (moves
.length
== 0) return [];
713 const L
= this.umoves
.length
; //at least 1: init from FEN
714 return moves
.filter(m
=> {
715 if (this.oppositeMoves(this.umoves
[L
- 1], m
)) return false;
716 if (!m
.end
.released
) return true;
717 // Check for repetitions:
718 V
.PlayOnBoard(this.board
, m
);
720 piece: m
.end
.released
,
721 square: { x: m
.end
.x
, y: m
.end
.y
},
722 position: this.getBaseFen()
725 this.repetitions
.some(r
=> {
727 r
.piece
== newState
.piece
&&
729 r
.square
.x
== newState
.square
.x
&&
730 r
.square
.y
== newState
.square
.y
732 r
.position
== newState
.position
735 V
.UndoOnBoard(this.board
, m
);
740 updateCastleFlags(move, piece
) {
742 const firstRank
= (c
== "w" ? 7 : 0);
743 if (piece
== V
.KING
&& move.appear
.length
> 0)
744 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
746 move.start
.x
== firstRank
&&
747 this.castleFlags
[c
].includes(move.start
.y
)
749 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
750 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
753 move.end
.x
== firstRank
&&
754 this.castleFlags
[c
].includes(move.end
.y
)
756 // Move to our rook: necessary normal piece, to union, releasing
757 // (or the rook was moved before!)
758 const flagIdx
= (move.end
.y
== this.castleFlags
[c
][0] ? 0 : 1);
759 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
764 // Easier before move is played in this case (flags are saved)
766 const L
= this.lastMoveEnd
.length
;
767 const lm
= this.lastMoveEnd
[L
-1];
768 // NOTE: lm.p != V.KING, always.
772 : this.getPiece(move.vanish
[0].x
, move.vanish
[0].y
);
774 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
775 this.updateCastleFlags(move, piece
);
776 const pawnFirstRank
= (c
== 'w' ? 6 : 1);
778 move.start
.x
== pawnFirstRank
&&
780 Math
.abs(move.end
.x
- move.start
.x
) == 2
782 // This move turns off a 2-squares pawn flag
783 this.pawnFlags
[c
][move.start
.y
] = false;
788 move.flags
= JSON
.stringify(this.aggregateFlags());
790 this.epSquares
.push(this.getEpSquare(move));
791 // Check if the move is the last of the turn: all cases except releases
792 if (!move.end
.released
) {
793 // No more union releases available
794 this.turn
= V
.GetOppCol(this.turn
);
796 this.lastMoveEnd
.push(null);
799 this.lastMoveEnd
.push({
800 p: move.end
.released
,
805 V
.PlayOnBoard(this.board
, move);
806 this.umoves
.push(this.getUmove(move));
807 if (!move.end
.released
) this.repetitions
= [];
809 this.repetitions
.push(
811 piece: move.end
.released
,
812 square: { x: move.end
.x
, y: move.end
.y
},
813 position: this.getBaseFen()
820 this.epSquares
.pop();
821 this.disaggregateFlags(JSON
.parse(move.flags
));
822 V
.UndoOnBoard(this.board
, move);
823 this.lastMoveEnd
.pop();
824 if (!move.end
.released
) {
825 this.turn
= V
.GetOppCol(this.turn
);
829 if (!!move.end
.released
) this.repetitions
.pop();
834 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
835 this.kingPos
[this.turn
] = [move.start
.x
, move.start
.y
];
839 // Check kings: if one is dancing, the side lost
840 // But, if both dancing, let's say it's a draw :-)
841 const [kpW
, kpB
] = [this.kingPos
['w'], this.kingPos
['b']];
842 const atKingPlace
= [
843 this.board
[kpW
[0]][kpW
[1]].charAt(1),
844 this.board
[kpB
[0]][kpB
[1]].charAt(1)
846 if (!atKingPlace
.includes('k')) return "1/2";
847 if (atKingPlace
[0] != 'k') return "0-1";
848 if (atKingPlace
[1] != 'k') return "1-0";
853 let initMoves
= this.getAllValidMoves();
854 if (initMoves
.length
== 0) return null;
855 // Loop until valid move is found (no blocked pawn released...)
857 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
860 // Just play random moves (for now at least. TODO?)
861 while (moves
.length
> 0) {
862 mv
= moves
[randInt(moves
.length
)];
865 if (!!mv
.end
.released
)
866 // A piece was just released from an union
867 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
870 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
871 if (!mv
.end
.released
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
873 return null; //never reached
876 // NOTE: evalPosition() is wrong, but unused since bot plays at random
879 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
880 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
883 const L
= this.lastMoveEnd
.length
;
884 const lm
= this.lastMoveEnd
[L
-1];
886 if (!lm
&& move.vanish
.length
== 0)
887 // When importing a game, the info move.released is lost
888 piece
= move.appear
[0].p
;
889 else piece
= (!!lm
? lm
.p : move.vanish
[0].p
);
890 if (!(ChessRules
.PIECES
.includes(piece
))) {
891 // Decode (moving) union
892 const up
= this.getUnionPieces(
893 move.vanish
.length
> 0 ? move.vanish
[0].c : move.appear
[0].c
, piece
);
897 // Basic move notation:
898 let notation
= piece
.toUpperCase();
900 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
901 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
905 const finalSquare
= V
.CoordsToSquare(move.end
);
906 notation
+= finalSquare
;
908 // Add potential promotion indications:
909 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
910 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
) {
912 if (ChessRules
.PIECES
.includes(move.appear
[0].p
))
913 notation
+= move.appear
[0].p
.toUpperCase();
915 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
916 notation
+= up
[c
].toUpperCase();
920 move.end
.x
== firstLastRank
[0] &&
921 move.vanish
.length
> 0 &&
922 ['c', 'd', 'e', 'f', 'g'].includes(move.vanish
[0].p
)
924 // We promoted an opponent's pawn
925 const oppCol
= V
.GetOppCol(c
);
926 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
927 notation
+= "=" + up
[oppCol
].toUpperCase();