1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class PacosakoRules
extends ChessRules
{
8 select: ChessRules
.Options
.select
,
11 label: "pacoplay mode",
19 static AbbreviateOptions(opts
) {
20 return (opts
["pacoplay"] ? "PP" : "");
23 static get IMAGE_EXTENSION() {
27 // Unions (left = white if upperCase, black otherwise)
55 // Arobase is character 64
56 return f
.charCodeAt() <= 90 ? "w" + f
.toLowerCase() : "b" + f
;
59 static IsGoodPosition(position
) {
60 if (position
.length
== 0) return false;
61 const rows
= position
.split("/");
62 if (rows
.length
!= V
.size
.x
) return false;
63 let kingSymb
= ['k', 'g', 'm', 'u', 'x', 'z', '@'];
64 let kings
= { 'k': 0, 'K': 0 };
65 for (let row
of rows
) {
67 for (let i
= 0; i
< row
.length
; i
++) {
68 if (!!(row
[i
].toLowerCase().match(/[a-z@]/))) {
70 if (kingSymb
.includes(row
[i
])) kings
['k']++;
71 // Not "else if", if two kings dancing together
72 if (kingSymb
.some(s
=> row
[i
] == s
.toUpperCase())) kings
['K']++;
75 const num
= parseInt(row
[i
], 10);
76 if (isNaN(num
) || num
<= 0) return false;
80 if (sumElts
!= V
.size
.y
) return false;
82 // Both kings should be on board. Exactly one per color.
83 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
88 return "Pacosako/" + b
;
92 if (ChessRules
.PIECES
.includes(m
.appear
[0].p
)) return super.getPPpath(m
);
93 // For an union, show only relevant piece:
94 // The color must be deduced from the move: reaching final rank of who?
95 const color
= (m
.appear
[0].x
== 0 ? 'w' : 'b');
96 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
97 return "Pacosako/" + color
+ up
[color
];
100 canTake([x1
, y1
], [x2
, y2
]) {
101 const p1
= this.board
[x1
][y1
].charAt(1);
102 if (!(ChessRules
.PIECES
.includes(p1
))) return false;
103 const p2
= this.board
[x2
][y2
].charAt(1);
104 if (!(ChessRules
.PIECES
.includes(p2
))) return true;
105 const c1
= this.board
[x1
][y1
].charAt(0);
106 const c2
= this.board
[x2
][y2
].charAt(0);
110 canIplay(side
, [x
, y
]) {
114 !(ChessRules
.PIECES
.includes(this.board
[x
][y
].charAt(1))) ||
115 this.board
[x
][y
].charAt(0) == side
121 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
122 const fenRows
= V
.ParseFen(fen
).position
.split("/");
123 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
124 const kingSymb
= ['k', 'g', 'm', 'u', 'x', 'z', '@'];
125 for (let i
= 0; i
< fenRows
.length
; i
++) {
127 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
128 const c
= fenRows
[i
].charAt(j
);
129 if (!!(c
.toLowerCase().match(/[a-z@]/))) {
130 if (kingSymb
.includes(c
))
131 this.kingPos
["b"] = [i
, k
];
132 // Not "else if", in case of two kings dancing together
133 if (kingSymb
.some(s
=> c
== s
.toUpperCase()))
134 this.kingPos
["w"] = [i
, k
];
137 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
138 if (!isNaN(num
)) k
+= num
- 1;
145 setOtherVariables(fen
) {
146 // Stack of "last move" only for intermediate chaining
147 this.lastMoveEnd
= [null];
148 // Local stack of non-capturing union moves:
150 const umove
= V
.ParseFen(fen
).umove
;
151 this.pacoplay
= !umove
; //"pacoplay.com mode" ?
152 if (!this.pacoplay
) {
153 if (umove
== "-") this.umoves
.push(null);
156 start: ChessRules
.SquareToCoords(umove
.substr(0, 2)),
157 end: ChessRules
.SquareToCoords(umove
.substr(2))
161 // Local stack of positions to avoid redundant moves:
162 this.repetitions
= [];
163 super.setOtherVariables(fen
);
166 static IsGoodFen(fen
) {
167 if (!ChessRules
.IsGoodFen(fen
)) return false;
168 const fenParts
= fen
.split(" ");
169 if (fenParts
.length
!= 6) return false;
170 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
175 static IsGoodFlags(flags
) {
176 // 4 for castle + 16 for pawns (more permissive, for pacoplay mode)
177 return !!flags
.match(/^[a-z]{4,4}[01]{0,16}$/);
181 super.setFlags(fenflags
); //castleFlags
182 if (this.pacoplay
) return;
184 w: [...Array(8)], //pawns can move 2 squares?
187 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
188 for (let c
of ["w", "b"]) {
189 for (let i
= 0; i
< 8; i
++)
190 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
195 if (!this.pacoplay
) return super.aggregateFlags();
196 return [this.castleFlags
, this.pawnFlags
];
199 disaggregateFlags(flags
) {
200 if (!this.pacoplay
) super.disaggregateFlags(flags
);
202 this.castleFlags
= flags
[0];
203 this.pawnFlags
= flags
[1];
209 move.vanish
.length
== 1 &&
210 !(ChessRules
.PIECES
.includes(move.appear
[0].p
)) &&
211 move.appear
[0].p
== move.vanish
[0].p
//not a promotion
214 return { start: move.start
, end: move.end
};
219 static ParseFen(fen
) {
220 const fenParts
= fen
.split(" ");
221 return Object
.assign(
222 ChessRules
.ParseFen(fen
),
223 { umove: fenParts
[5] }
227 static GenRandInitFen(options
) {
228 // Add 16 pawns flags + empty umove:
229 const pawnFlags
= (options
.pacoplay
? "" : "1111111111111111");
230 return ChessRules
.GenRandInitFen(options
).slice(0, -2) +
231 pawnFlags
+ " -" + (!options
.pacoplay
? " -" : "");
235 let fen
= super.getFlagsFen();
236 if (!this.pacoplay
) {
238 for (let c
of ["w", "b"])
239 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
245 const L
= this.umoves
.length
;
249 : ChessRules
.CoordsToSquare(this.umoves
[L
- 1].start
) +
250 ChessRules
.CoordsToSquare(this.umoves
[L
- 1].end
)
255 const umoveFen
= this.pacoplay
? "" : (" " + this.getUmoveFen());
256 return super.getFen() + umoveFen
;
260 const umoveFen
= this.pacoplay
? "" : ("_" + this.getUmoveFen());
261 return super.getFenForRepeat() + umoveFen
;
265 const p
= this.board
[i
][j
].charAt(1);
266 if (ChessRules
.PIECES
.includes(p
)) return super.getColor(i
, j
);
267 return this.turn
; //union: I can use it, so it's "my" color...
270 getPiece(i
, j
, color
) {
271 const p
= this.board
[i
][j
].charAt(1);
272 if (ChessRules
.PIECES
.includes(p
)) return p
;
273 const c
= this.board
[i
][j
].charAt(0);
274 // NOTE: this.turn == HACK, but should work...
275 color
= color
|| this.turn
;
276 return V
.UNIONS
[p
][c
== color
? 0 : 1];
279 getUnionPieces(color
, code
) {
280 const pieces
= V
.UNIONS
[code
];
282 w: pieces
[color
== 'w' ? 0 : 1],
283 b: pieces
[color
== 'b' ? 0 : 1]
287 // p1: white piece, p2: black piece
288 getUnionCode(p1
, p2
) {
290 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p1
&& v
[1] == p2
)
292 const c
= (uIdx
>= 0 ? 'w' : 'b');
295 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p2
&& v
[1] == p1
)
298 return { c: c
, p: Object
.keys(V
.UNIONS
)[uIdx
] };
301 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
302 const L
= this.lastMoveEnd
.length
;
303 const lm
= this.lastMoveEnd
[L
-1];
304 const piece
= (!!lm
? lm
.p : null);
305 const initColor
= (!!piece
? this.turn : this.board
[sx
][sy
].charAt(0));
306 const initPiece
= (piece
|| this.board
[sx
][sy
].charAt(1));
308 const oppCol
= V
.GetOppCol(c
);
309 if (!!tr
&& !(ChessRules
.PIECES
.includes(initPiece
))) {
310 // Transformation computed without taking union into account
311 const up
= this.getUnionPieces(initColor
, initPiece
);
312 let args
= [tr
.p
, up
[oppCol
]];
313 if (c
== 'b') args
= args
.reverse();
314 const cp
= this.getUnionCode(args
[0], args
[1]);
319 // - union to free square (other cases are illegal: return null)
320 // - normal piece to free square,
321 // to enemy normal piece, or
322 // to union (releasing our piece)
324 start: { x: sx
, y: sy
},
325 end: { x: ex
, y: ey
},
338 // Treat free square cases first:
339 if (this.board
[ex
][ey
] == V
.EMPTY
) {
344 c: !!tr
? tr
.c : initColor
,
345 p: !!tr
? tr
.p : initPiece
350 // Now the two cases with union / release:
351 const destColor
= this.board
[ex
][ey
].charAt(0);
352 const destPiece
= this.board
[ex
][ey
].charAt(1);
361 if (ChessRules
.PIECES
.includes(destPiece
)) {
362 // Normal piece: just create union
363 let args
= [!!tr
? tr
.p : initPiece
, destPiece
];
364 if (c
== 'b') args
= args
.reverse();
365 const cp
= this.getUnionCode(args
[0], args
[1]);
376 // Releasing a piece in an union: keep track of released piece
377 const up
= this.getUnionPieces(destColor
, destPiece
);
378 let args
= [!!tr
? tr
.p : initPiece
, up
[oppCol
]];
379 if (c
== 'b') args
= args
.reverse();
380 const cp
= this.getUnionCode(args
[0], args
[1]);
389 // In move.end, to be sent to the server
390 mv
.end
.released
= up
[c
];
394 getPotentialMovesFrom([x
, y
]) {
395 const L
= this.lastMoveEnd
.length
;
396 const lm
= this.lastMoveEnd
[L
-1];
397 if (!!lm
&& (x
!= lm
.x
|| y
!= lm
.y
)) return [];
398 const piece
= (!!lm
? lm
.p : this.getPiece(x
, y
));
400 var saveSquare
= this.board
[x
][y
];
401 this.board
[x
][y
] = this.turn
+ piece
;
407 const firstRank
= (c
== 'w' ? 7 : 0);
408 baseMoves
= this.getPotentialPawnMoves([x
, y
]).filter(m
=> {
409 // Skip forbidden 2-squares jumps (except from first rank)
410 // Also skip unions capturing en-passant (not allowed).
413 m
.start
.x
== firstRank
||
414 Math
.abs(m
.end
.x
- m
.start
.x
) == 1 ||
416 (!this.pacoplay
&& this.pawnFlags
[c
][m
.start
.y
])
420 this.board
[x
][y
].charAt(1) == V
.PAWN
||
428 baseMoves
= this.getPotentialRookMoves([x
, y
]);
431 baseMoves
= this.getPotentialKnightMoves([x
, y
]);
434 baseMoves
= this.getPotentialBishopMoves([x
, y
]);
437 baseMoves
= this.getPotentialQueenMoves([x
, y
]);
440 baseMoves
= this.getPotentialKingMoves([x
, y
]);
443 // When a pawn in an union reaches final rank with a non-standard
444 // promotion move: apply promotion anyway
446 const oppCol
= V
.GetOppCol(c
);
447 const oppLastRank
= (c
== 'w' ? 7 : 0);
448 baseMoves
.forEach(m
=> {
450 m
.end
.x
== oppLastRank
&&
451 ['c', 'd', 'e', 'f', 'g'].includes(m
.appear
[0].p
)
453 // Move to first rank, which is last rank for opponent's pawn.
454 // => Show promotion choices.
455 // Find our piece in union (not a pawn)
456 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
457 // merge with all potential promotion pieces + push (loop)
458 for (let promotionPiece
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
459 let args
= [up
[c
], promotionPiece
];
460 if (c
== 'b') args
= args
.reverse();
461 const cp
= this.getUnionCode(args
[0], args
[1]);
462 let cpMove
= JSON
.parse(JSON
.stringify(m
));
463 cpMove
.appear
[0].c
= cp
.c
;
464 cpMove
.appear
[0].p
= cp
.p
;
470 m
.vanish
.length
> 0 &&
471 m
.vanish
[0].p
== V
.PAWN
&&
472 m
.start
.y
!= m
.end
.y
&&
473 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
476 // No en-passant inside a chaining
478 // Fix en-passant capture: union type, maybe released piece too
479 const cs
= [m
.end
.x
+ (c
== 'w' ? 1 : -1), m
.end
.y
];
480 const code
= this.board
[cs
[0]][cs
[1]].charAt(1);
481 if (code
== V
.PAWN
) {
482 // Simple en-passant capture (usual: just form union)
487 // An union pawn + something just moved two squares
488 const color
= this.board
[cs
[0]][cs
[1]].charAt(0);
489 const up
= this.getUnionPieces(color
, code
);
490 m
.end
.released
= up
[c
];
491 let args
= [V
.PAWN
, up
[oppCol
]];
492 if (c
== 'b') args
= args
.reverse();
493 const cp
= this.getUnionCode(args
[0], args
[1]);
494 m
.appear
[0].c
= cp
.c
;
495 m
.appear
[0].p
= cp
.p
;
501 if (!!lm
) this.board
[x
][y
] = saveSquare
;
505 getPotentialKingMoves(sq
) {
506 if (!this.pacoplay
) return super.getPotentialKingMoves(sq
);
507 // Initialize with normal moves, without captures
509 for (let s
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
510 const [i
, j
] = [sq
[0] + s
[0], sq
[1] + s
[1]];
511 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
512 moves
.push(this.getBasicMove(sq
, [i
, j
]));
514 if (this.castleFlags
[this.turn
].some(v
=> v
< V
.size
.y
))
515 moves
= moves
.concat(this.getCastleMoves(sq
));
519 getEpSquare(moveOrSquare
) {
520 if (typeof moveOrSquare
=== "string") {
521 const square
= moveOrSquare
;
522 if (square
== "-") return undefined;
523 return V
.SquareToCoords(square
);
525 const move = moveOrSquare
;
526 const s
= move.start
,
530 Math
.abs(s
.x
- e
.x
) == 2 &&
531 this.getPiece(s
.x
, s
.y
, this.turn
) == V
.PAWN
541 // Does m2 un-do m1 ? (to disallow undoing union moves)
542 oppositeMoves(m1
, m2
) {
545 !(ChessRules
.PIECES
.includes(m2
.appear
[0].p
)) &&
546 m2
.vanish
.length
== 1 &&
548 m1
.start
.x
== m2
.end
.x
&&
549 m1
.end
.x
== m2
.start
.x
&&
550 m1
.start
.y
== m2
.end
.y
&&
551 m1
.end
.y
== m2
.start
.y
555 getCastleMoves([x
, y
]) {
556 const c
= this.getColor(x
, y
);
557 const oppCol
= V
.GetOppCol(c
);
559 const finalSquares
= [ [2, 3], [6, 5] ];
560 castlingCheck: for (let castleSide
= 0; castleSide
< 2; castleSide
++) {
561 if (this.castleFlags
[c
][castleSide
] >= 8) continue;
562 const rookPos
= this.castleFlags
[c
][castleSide
];
563 const castlingColor
= this.board
[x
][rookPos
].charAt(0);
564 const castlingPiece
= this.board
[x
][rookPos
].charAt(1);
566 // Nothing on the path of the king ?
567 const finDist
= finalSquares
[castleSide
][0] - y
;
568 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
570 let kingSquares
= [y
];
574 this.board
[x
][i
] != V
.EMPTY
&&
575 (this.getColor(x
, i
) != c
|| ![y
, rookPos
].includes(i
))
578 continue castlingCheck
;
582 } while (i
!= finalSquares
[castleSide
][0]);
583 // No checks on the path of the king ?
584 if (this.isAttacked(kingSquares
, oppCol
)) continue castlingCheck
;
586 // Nothing on the path to the rook?
587 step
= castleSide
== 0 ? -1 : 1;
588 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
589 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
592 // Nothing on final squares, except maybe king and castling rook?
593 for (i
= 0; i
< 2; i
++) {
595 finalSquares
[castleSide
][i
] != rookPos
&&
596 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
598 finalSquares
[castleSide
][i
] != y
||
599 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
602 continue castlingCheck
;
611 y: finalSquares
[castleSide
][0],
617 y: finalSquares
[castleSide
][1],
623 // King might be initially disguised (Titan...)
624 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
625 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: castlingColor
})
628 Math
.abs(y
- rookPos
) <= 2
629 ? { x: x
, y: rookPos
}
630 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
638 getEnpassantCaptures(sq
, shiftX
) {
639 // HACK: when artificially change turn, do not consider en-passant
640 const mcMod2
= this.movesCount
% 2;
642 if ((c
== 'w' && mcMod2
== 1) || (c
== 'b' && mcMod2
== 0)) return [];
643 return super.getEnpassantCaptures(sq
, shiftX
);
646 isAttacked_aux(files
, color
, positions
, fromSquare
, released
) {
647 // "positions" = array of FENs to detect infinite loops. Example:
648 // r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/P1eDP3/3B1PP1/R3K1NR,
649 // Bxd2 Bxc3 Bxb4 Bxc3 Bxb4 etc.
651 fen: super.getBaseFen(),
656 positions
.some(p
=> {
658 p
.piece
== newPos
.piece
&&
659 p
.fen
== newPos
.fen
&&
660 p
.from == newPos
.from
664 // Start of an infinite loop: exit
667 positions
.push(newPos
);
668 const rank
= (color
== 'w' ? 0 : 7);
669 const moves
= this.getPotentialMovesFrom(fromSquare
);
670 if (moves
.some(m
=> m
.end
.x
== rank
&& files
.includes(m
.end
.y
)))
673 for (let m
of moves
) {
674 if (!!m
.end
.released
) {
675 // Turn won't change since !!m.released
677 const res
= this.isAttacked_aux(
678 files
, color
, positions
, [m
.end
.x
, m
.end
.y
], m
.end
.released
);
680 if (res
) return true;
686 isAttacked(files
, color
) {
687 const rank
= (color
== 'w' ? 0 : 7);
688 // Since it's too difficult (impossible?) to search from the square itself,
689 // let's adopt a suboptimal but working strategy: find all attacks.
691 // Artificial turn change is required:
694 outerLoop: for (let i
=0; i
<8; i
++) {
695 for (let j
=0; j
<8; j
++) {
696 // Attacks must start from a normal piece, not an union.
697 // Therefore, the following test is correct.
699 this.board
[i
][j
] != V
.EMPTY
&&
700 // Do not start with king (irrelevant, and lead to infinite calls)
701 [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].includes(
702 this.board
[i
][j
].charAt(1)) &&
703 this.board
[i
][j
].charAt(0) == color
706 const moves
= this.getPotentialMovesFrom([i
, j
]);
707 if (moves
.some(m
=> m
.end
.x
== rank
&& files
.includes(m
.end
.y
))) {
711 for (let m
of moves
) {
712 if (!!m
.end
.released
) {
713 // Turn won't change since !!m.released
716 res
= this.isAttacked_aux(
717 files
, color
, positions
, [m
.end
.x
, m
.end
.y
], m
.end
.released
);
719 if (res
) break outerLoop
;
729 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
730 for (let step
of steps
) {
731 let rx
= x
+ step
[0],
733 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
739 this.board
[rx
][ry
] != V
.EMPTY
&&
740 this.getPiece(rx
, ry
) == piece
&&
741 this.getColor(rx
, ry
) == color
&&
742 this.canTake([rx
, ry
], [x
, y
]) //TODO: necessary line?
743 //If not, generic method is OK
751 // Do not consider checks, except to forbid castling
757 if (moves
.length
== 0) return [];
758 const L
= (!this.pacoplay
? this.umoves
.length : 0);
759 return moves
.filter(m
=> {
760 if (L
> 0 && this.oppositeMoves(this.umoves
[L
- 1], m
)) return false;
761 if (!m
.end
.released
) return true;
762 // Check for repetitions:
763 V
.PlayOnBoard(this.board
, m
);
765 piece: m
.end
.released
,
766 square: { x: m
.end
.x
, y: m
.end
.y
},
767 position: this.getBaseFen()
770 this.repetitions
.some(r
=> {
772 r
.piece
== newState
.piece
&&
774 r
.square
.x
== newState
.square
.x
&&
775 r
.square
.y
== newState
.square
.y
777 r
.position
== newState
.position
780 V
.UndoOnBoard(this.board
, m
);
785 updateCastleFlags(move, piece
) {
787 const firstRank
= (c
== "w" ? 7 : 0);
788 if (piece
== V
.KING
&& move.appear
.length
> 0)
789 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
791 move.start
.x
== firstRank
&&
792 this.castleFlags
[c
].includes(move.start
.y
)
794 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
795 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
798 move.end
.x
== firstRank
&&
799 this.castleFlags
[c
].includes(move.end
.y
)
801 // Move to our rook: necessary normal piece, to union, releasing
802 // (or the rook was moved before!)
803 const flagIdx
= (move.end
.y
== this.castleFlags
[c
][0] ? 0 : 1);
804 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
809 // Easier before move is played in this case (flags are saved)
811 const L
= this.lastMoveEnd
.length
;
812 const lm
= this.lastMoveEnd
[L
-1];
813 // NOTE: lm.p != V.KING, always.
817 : this.getPiece(move.vanish
[0].x
, move.vanish
[0].y
);
819 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
820 this.updateCastleFlags(move, piece
);
821 const pawnFirstRank
= (c
== 'w' ? 6 : 1);
824 move.start
.x
== pawnFirstRank
&&
826 Math
.abs(move.end
.x
- move.start
.x
) == 2
828 // This move turns off a 2-squares pawn flag
829 this.pawnFlags
[c
][move.start
.y
] = false;
834 move.flags
= JSON
.stringify(this.aggregateFlags());
836 this.epSquares
.push(this.getEpSquare(move));
837 // Check if the move is the last of the turn: all cases except releases
838 if (!move.end
.released
) {
839 // No more union releases available
840 this.turn
= V
.GetOppCol(this.turn
);
842 this.lastMoveEnd
.push(null);
845 this.lastMoveEnd
.push({
846 p: move.end
.released
,
851 V
.PlayOnBoard(this.board
, move);
852 if (!this.pacoplay
) this.umoves
.push(this.getUmove(move));
853 if (!move.end
.released
) this.repetitions
= [];
855 this.repetitions
.push(
857 piece: move.end
.released
,
858 square: { x: move.end
.x
, y: move.end
.y
},
859 position: this.getBaseFen()
866 this.epSquares
.pop();
867 this.disaggregateFlags(JSON
.parse(move.flags
));
868 V
.UndoOnBoard(this.board
, move);
869 this.lastMoveEnd
.pop();
870 if (!move.end
.released
) {
871 this.turn
= V
.GetOppCol(this.turn
);
874 if (!this.pacoplay
) this.umoves
.pop();
875 if (!!move.end
.released
) this.repetitions
.pop();
880 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
881 this.kingPos
[this.turn
] = [move.start
.x
, move.start
.y
];
885 // Check kings: if one is dancing, the side lost
886 // But, if both dancing, let's say it's a draw :-)
887 const [kpW
, kpB
] = [this.kingPos
['w'], this.kingPos
['b']];
888 const atKingPlace
= [
889 this.board
[kpW
[0]][kpW
[1]].charAt(1),
890 this.board
[kpB
[0]][kpB
[1]].charAt(1)
892 if (!atKingPlace
.includes('k')) return "1/2";
893 if (atKingPlace
[0] != 'k') return "0-1";
894 if (atKingPlace
[1] != 'k') return "1-0";
899 let initMoves
= this.getAllValidMoves();
900 if (initMoves
.length
== 0) return null;
901 // Loop until valid move is found (no blocked pawn released...)
903 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
906 // Just play random moves (for now at least. TODO?)
907 while (moves
.length
> 0) {
908 mv
= moves
[randInt(moves
.length
)];
911 if (!!mv
.end
.released
)
912 // A piece was just released from an union
913 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
916 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
917 if (!mv
.end
.released
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
919 return null; //never reached
922 // NOTE: evalPosition() is wrong, but unused since bot plays at random
925 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
926 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
929 const L
= this.lastMoveEnd
.length
;
930 const lm
= this.lastMoveEnd
[L
-1];
932 if (!lm
&& move.vanish
.length
== 0)
933 // When importing a game, the info move.released is lost
934 piece
= move.appear
[0].p
;
935 else piece
= (!!lm
? lm
.p : move.vanish
[0].p
);
936 if (!(ChessRules
.PIECES
.includes(piece
))) {
937 // Decode (moving) union
938 const up
= this.getUnionPieces(
939 move.vanish
.length
> 0 ? move.vanish
[0].c : move.appear
[0].c
, piece
);
943 // Basic move notation:
944 let notation
= piece
.toUpperCase();
946 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
947 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
951 const finalSquare
= V
.CoordsToSquare(move.end
);
952 notation
+= finalSquare
;
954 // Add potential promotion indications:
955 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
956 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
) {
958 if (ChessRules
.PIECES
.includes(move.appear
[0].p
))
959 notation
+= move.appear
[0].p
.toUpperCase();
961 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
962 notation
+= up
[c
].toUpperCase();
966 move.end
.x
== firstLastRank
[0] &&
967 move.vanish
.length
> 0 &&
968 ['c', 'd', 'e', 'f', 'g'].includes(move.vanish
[0].p
)
970 // We promoted an opponent's pawn
971 const oppCol
= V
.GetOppCol(c
);
972 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
973 notation
+= "=" + up
[oppCol
].toUpperCase();