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 get IMAGE_EXTENSION() {
23 // Unions (left = white if upperCase, black otherwise)
51 // Arobase is character 64
52 return f
.charCodeAt() <= 90 ? "w" + f
.toLowerCase() : "b" + f
;
55 static IsGoodPosition(position
) {
56 if (position
.length
== 0) return false;
57 const rows
= position
.split("/");
58 if (rows
.length
!= V
.size
.x
) return false;
59 let kingSymb
= ['k', 'g', 'm', 'u', 'x', 'z', '@'];
60 let kings
= { 'k': 0, 'K': 0 };
61 for (let row
of rows
) {
63 for (let i
= 0; i
< row
.length
; i
++) {
64 if (!!(row
[i
].toLowerCase().match(/[a-z@]/))) {
66 if (kingSymb
.includes(row
[i
])) kings
['k']++;
67 // Not "else if", if two kings dancing together
68 if (kingSymb
.some(s
=> row
[i
] == s
.toUpperCase())) kings
['K']++;
71 const num
= parseInt(row
[i
], 10);
72 if (isNaN(num
) || num
<= 0) return false;
76 if (sumElts
!= V
.size
.y
) return false;
78 // Both kings should be on board. Exactly one per color.
79 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
84 return "Pacosako/" + b
;
88 if (ChessRules
.PIECES
.includes(m
.appear
[0].p
)) return super.getPPpath(m
);
89 // For an union, show only relevant piece:
90 // The color must be deduced from the move: reaching final rank of who?
91 const color
= (m
.appear
[0].x
== 0 ? 'w' : 'b');
92 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
93 return "Pacosako/" + color
+ up
[color
];
96 canTake([x1
, y1
], [x2
, y2
]) {
97 const p1
= this.board
[x1
][y1
].charAt(1);
98 if (!(ChessRules
.PIECES
.includes(p1
))) return false;
99 const p2
= this.board
[x2
][y2
].charAt(1);
100 if (!(ChessRules
.PIECES
.includes(p2
))) return true;
101 const c1
= this.board
[x1
][y1
].charAt(0);
102 const c2
= this.board
[x2
][y2
].charAt(0);
106 canIplay(side
, [x
, y
]) {
110 !(ChessRules
.PIECES
.includes(this.board
[x
][y
].charAt(1))) ||
111 this.board
[x
][y
].charAt(0) == side
117 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
118 const fenRows
= V
.ParseFen(fen
).position
.split("/");
119 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
120 const kingSymb
= ['k', 'g', 'm', 'u', 'x', 'z', '@'];
121 for (let i
= 0; i
< fenRows
.length
; i
++) {
123 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
124 const c
= fenRows
[i
].charAt(j
);
125 if (!!(c
.toLowerCase().match(/[a-z@]/))) {
126 if (kingSymb
.includes(c
))
127 this.kingPos
["b"] = [i
, k
];
128 // Not "else if", in case of two kings dancing together
129 if (kingSymb
.some(s
=> c
== s
.toUpperCase()))
130 this.kingPos
["w"] = [i
, k
];
133 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
134 if (!isNaN(num
)) k
+= num
- 1;
141 setOtherVariables(fen
) {
142 // Stack of "last move" only for intermediate chaining
143 this.lastMoveEnd
= [null];
144 // Local stack of non-capturing union moves:
146 const umove
= V
.ParseFen(fen
).umove
;
147 this.pacoplay
= !umove
; //"pacoplay.com mode" ?
148 if (!this.pacoplay
) {
149 if (umove
== "-") this.umoves
.push(null);
152 start: ChessRules
.SquareToCoords(umove
.substr(0, 2)),
153 end: ChessRules
.SquareToCoords(umove
.substr(2))
157 // Local stack of positions to avoid redundant moves:
158 this.repetitions
= [];
159 super.setOtherVariables(fen
);
162 static IsGoodFen(fen
) {
163 if (!ChessRules
.IsGoodFen(fen
)) return false;
164 const fenParts
= fen
.split(" ");
165 if (fenParts
.length
!= 6) return false;
166 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
171 static IsGoodFlags(flags
) {
172 // 4 for castle + 16 for pawns (more permissive, for pacoplay mode)
173 return !!flags
.match(/^[a-z]{4,4}[01]{0,16}$/);
177 super.setFlags(fenflags
); //castleFlags
178 if (this.pacoplay
) return;
180 w: [...Array(8)], //pawns can move 2 squares?
183 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
184 for (let c
of ["w", "b"]) {
185 for (let i
= 0; i
< 8; i
++)
186 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
191 if (!this.pacoplay
) return super.aggregateFlags();
192 return [this.castleFlags
, this.pawnFlags
];
195 disaggregateFlags(flags
) {
196 if (!this.pacoplay
) super.disaggregateFlags(flags
);
198 this.castleFlags
= flags
[0];
199 this.pawnFlags
= flags
[1];
205 move.vanish
.length
== 1 &&
206 !(ChessRules
.PIECES
.includes(move.appear
[0].p
)) &&
207 move.appear
[0].p
== move.vanish
[0].p
//not a promotion
210 return { start: move.start
, end: move.end
};
215 static ParseFen(fen
) {
216 const fenParts
= fen
.split(" ");
217 return Object
.assign(
218 ChessRules
.ParseFen(fen
),
219 { umove: fenParts
[5] }
223 static GenRandInitFen(options
) {
224 // Add 16 pawns flags + empty umove:
225 const pawnFlags
= (options
.pacoplay
? "" : "1111111111111111");
226 return ChessRules
.GenRandInitFen(options
).slice(0, -2) +
227 pawnFlags
+ " -" + (!options
.pacoplay
? " -" : "");
231 let fen
= super.getFlagsFen();
232 if (!this.pacoplay
) {
234 for (let c
of ["w", "b"])
235 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
241 const L
= this.umoves
.length
;
245 : ChessRules
.CoordsToSquare(this.umoves
[L
- 1].start
) +
246 ChessRules
.CoordsToSquare(this.umoves
[L
- 1].end
)
251 const umoveFen
= this.pacoplay
? "" : (" " + this.getUmoveFen());
252 return super.getFen() + umoveFen
;
256 const umoveFen
= this.pacoplay
? "" : ("_" + this.getUmoveFen());
257 return super.getFenForRepeat() + umoveFen
;
261 const p
= this.board
[i
][j
].charAt(1);
262 if (ChessRules
.PIECES
.includes(p
)) return super.getColor(i
, j
);
263 return this.turn
; //union: I can use it, so it's "my" color...
266 getPiece(i
, j
, color
) {
267 const p
= this.board
[i
][j
].charAt(1);
268 if (ChessRules
.PIECES
.includes(p
)) return p
;
269 const c
= this.board
[i
][j
].charAt(0);
270 // NOTE: this.turn == HACK, but should work...
271 color
= color
|| this.turn
;
272 return V
.UNIONS
[p
][c
== color
? 0 : 1];
275 getUnionPieces(color
, code
) {
276 const pieces
= V
.UNIONS
[code
];
278 w: pieces
[color
== 'w' ? 0 : 1],
279 b: pieces
[color
== 'b' ? 0 : 1]
283 // p1: white piece, p2: black piece
284 getUnionCode(p1
, p2
) {
286 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p1
&& v
[1] == p2
)
288 const c
= (uIdx
>= 0 ? 'w' : 'b');
291 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p2
&& v
[1] == p1
)
294 return { c: c
, p: Object
.keys(V
.UNIONS
)[uIdx
] };
297 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
298 const L
= this.lastMoveEnd
.length
;
299 const lm
= this.lastMoveEnd
[L
-1];
300 const piece
= (!!lm
? lm
.p : null);
301 const initColor
= (!!piece
? this.turn : this.board
[sx
][sy
].charAt(0));
302 const initPiece
= (piece
|| this.board
[sx
][sy
].charAt(1));
304 const oppCol
= V
.GetOppCol(c
);
305 if (!!tr
&& !(ChessRules
.PIECES
.includes(initPiece
))) {
306 // Transformation computed without taking union into account
307 const up
= this.getUnionPieces(initColor
, initPiece
);
308 let args
= [tr
.p
, up
[oppCol
]];
309 if (c
== 'b') args
= args
.reverse();
310 const cp
= this.getUnionCode(args
[0], args
[1]);
315 // - union to free square (other cases are illegal: return null)
316 // - normal piece to free square,
317 // to enemy normal piece, or
318 // to union (releasing our piece)
320 start: { x: sx
, y: sy
},
321 end: { x: ex
, y: ey
},
334 // Treat free square cases first:
335 if (this.board
[ex
][ey
] == V
.EMPTY
) {
340 c: !!tr
? tr
.c : initColor
,
341 p: !!tr
? tr
.p : initPiece
346 // Now the two cases with union / release:
347 const destColor
= this.board
[ex
][ey
].charAt(0);
348 const destPiece
= this.board
[ex
][ey
].charAt(1);
357 if (ChessRules
.PIECES
.includes(destPiece
)) {
358 // Normal piece: just create union
359 let args
= [!!tr
? tr
.p : initPiece
, destPiece
];
360 if (c
== 'b') args
= args
.reverse();
361 const cp
= this.getUnionCode(args
[0], args
[1]);
372 // Releasing a piece in an union: keep track of released piece
373 const up
= this.getUnionPieces(destColor
, destPiece
);
374 let args
= [!!tr
? tr
.p : initPiece
, up
[oppCol
]];
375 if (c
== 'b') args
= args
.reverse();
376 const cp
= this.getUnionCode(args
[0], args
[1]);
385 // In move.end, to be sent to the server
386 mv
.end
.released
= up
[c
];
390 getPotentialMovesFrom([x
, y
]) {
391 const L
= this.lastMoveEnd
.length
;
392 const lm
= this.lastMoveEnd
[L
-1];
393 if (!!lm
&& (x
!= lm
.x
|| y
!= lm
.y
)) return [];
394 const piece
= (!!lm
? lm
.p : this.getPiece(x
, y
));
396 var saveSquare
= this.board
[x
][y
];
397 this.board
[x
][y
] = this.turn
+ piece
;
403 const firstRank
= (c
== 'w' ? 7 : 0);
404 baseMoves
= this.getPotentialPawnMoves([x
, y
]).filter(m
=> {
405 // Skip forbidden 2-squares jumps (except from first rank)
406 // Also skip unions capturing en-passant (not allowed).
409 m
.start
.x
== firstRank
||
410 Math
.abs(m
.end
.x
- m
.start
.x
) == 1 ||
412 (!this.pacoplay
&& this.pawnFlags
[c
][m
.start
.y
])
416 this.board
[x
][y
].charAt(1) == V
.PAWN
||
424 baseMoves
= this.getPotentialRookMoves([x
, y
]);
427 baseMoves
= this.getPotentialKnightMoves([x
, y
]);
430 baseMoves
= this.getPotentialBishopMoves([x
, y
]);
433 baseMoves
= this.getPotentialQueenMoves([x
, y
]);
436 baseMoves
= this.getPotentialKingMoves([x
, y
]);
439 // When a pawn in an union reaches final rank with a non-standard
440 // promotion move: apply promotion anyway
442 const oppCol
= V
.GetOppCol(c
);
443 const oppLastRank
= (c
== 'w' ? 7 : 0);
444 baseMoves
.forEach(m
=> {
446 m
.end
.x
== oppLastRank
&&
447 ['c', 'd', 'e', 'f', 'g'].includes(m
.appear
[0].p
)
449 // Move to first rank, which is last rank for opponent's pawn.
450 // => Show promotion choices.
451 // Find our piece in union (not a pawn)
452 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
453 // merge with all potential promotion pieces + push (loop)
454 for (let promotionPiece
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
455 let args
= [up
[c
], promotionPiece
];
456 if (c
== 'b') args
= args
.reverse();
457 const cp
= this.getUnionCode(args
[0], args
[1]);
458 let cpMove
= JSON
.parse(JSON
.stringify(m
));
459 cpMove
.appear
[0].c
= cp
.c
;
460 cpMove
.appear
[0].p
= cp
.p
;
466 m
.vanish
.length
> 0 &&
467 m
.vanish
[0].p
== V
.PAWN
&&
468 m
.start
.y
!= m
.end
.y
&&
469 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
472 // No en-passant inside a chaining
474 // Fix en-passant capture: union type, maybe released piece too
475 const cs
= [m
.end
.x
+ (c
== 'w' ? 1 : -1), m
.end
.y
];
476 const code
= this.board
[cs
[0]][cs
[1]].charAt(1);
477 if (code
== V
.PAWN
) {
478 // Simple en-passant capture (usual: just form union)
483 // An union pawn + something just moved two squares
484 const color
= this.board
[cs
[0]][cs
[1]].charAt(0);
485 const up
= this.getUnionPieces(color
, code
);
486 m
.end
.released
= up
[c
];
487 let args
= [V
.PAWN
, up
[oppCol
]];
488 if (c
== 'b') args
= args
.reverse();
489 const cp
= this.getUnionCode(args
[0], args
[1]);
490 m
.appear
[0].c
= cp
.c
;
491 m
.appear
[0].p
= cp
.p
;
497 if (!!lm
) this.board
[x
][y
] = saveSquare
;
501 getPotentialKingMoves(sq
) {
502 if (!this.pacoplay
) return super.getPotentialKingMoves(sq
);
503 // Initialize with normal moves, without captures
505 for (let s
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
506 const [i
, j
] = [sq
[0] + s
[0], sq
[1] + s
[1]];
507 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
508 moves
.push(this.getBasicMove(sq
, [i
, j
]));
510 if (this.castleFlags
[this.turn
].some(v
=> v
< V
.size
.y
))
511 moves
= moves
.concat(this.getCastleMoves(sq
));
515 getEpSquare(moveOrSquare
) {
516 if (typeof moveOrSquare
=== "string") {
517 const square
= moveOrSquare
;
518 if (square
== "-") return undefined;
519 return V
.SquareToCoords(square
);
521 const move = moveOrSquare
;
522 const s
= move.start
,
526 Math
.abs(s
.x
- e
.x
) == 2 &&
527 this.getPiece(s
.x
, s
.y
, this.turn
) == V
.PAWN
537 // Does m2 un-do m1 ? (to disallow undoing union moves)
538 oppositeMoves(m1
, m2
) {
541 !(ChessRules
.PIECES
.includes(m2
.appear
[0].p
)) &&
542 m2
.vanish
.length
== 1 &&
544 m1
.start
.x
== m2
.end
.x
&&
545 m1
.end
.x
== m2
.start
.x
&&
546 m1
.start
.y
== m2
.end
.y
&&
547 m1
.end
.y
== m2
.start
.y
551 getCastleMoves([x
, y
]) {
552 const c
= this.getColor(x
, y
);
553 const oppCol
= V
.GetOppCol(c
);
555 const finalSquares
= [ [2, 3], [6, 5] ];
556 castlingCheck: for (let castleSide
= 0; castleSide
< 2; castleSide
++) {
557 if (this.castleFlags
[c
][castleSide
] >= 8) continue;
558 const rookPos
= this.castleFlags
[c
][castleSide
];
559 const castlingColor
= this.board
[x
][rookPos
].charAt(0);
560 const castlingPiece
= this.board
[x
][rookPos
].charAt(1);
562 // Nothing on the path of the king ?
563 const finDist
= finalSquares
[castleSide
][0] - y
;
564 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
566 let kingSquares
= [y
];
570 this.board
[x
][i
] != V
.EMPTY
&&
571 (this.getColor(x
, i
) != c
|| ![y
, rookPos
].includes(i
))
574 continue castlingCheck
;
578 } while (i
!= finalSquares
[castleSide
][0]);
579 // No checks on the path of the king ?
580 if (this.isAttacked(kingSquares
, oppCol
)) continue castlingCheck
;
582 // Nothing on the path to the rook?
583 step
= castleSide
== 0 ? -1 : 1;
584 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
585 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
588 // Nothing on final squares, except maybe king and castling rook?
589 for (i
= 0; i
< 2; i
++) {
591 finalSquares
[castleSide
][i
] != rookPos
&&
592 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
594 finalSquares
[castleSide
][i
] != y
||
595 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
598 continue castlingCheck
;
607 y: finalSquares
[castleSide
][0],
613 y: finalSquares
[castleSide
][1],
619 // King might be initially disguised (Titan...)
620 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
621 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: castlingColor
})
624 Math
.abs(y
- rookPos
) <= 2
625 ? { x: x
, y: rookPos
}
626 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
634 getEnpassantCaptures(sq
, shiftX
) {
635 // HACK: when artificially change turn, do not consider en-passant
636 const mcMod2
= this.movesCount
% 2;
638 if ((c
== 'w' && mcMod2
== 1) || (c
== 'b' && mcMod2
== 0)) return [];
639 return super.getEnpassantCaptures(sq
, shiftX
);
642 isAttacked_aux(files
, color
, positions
, fromSquare
, released
) {
643 // "positions" = array of FENs to detect infinite loops. Example:
644 // r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/P1eDP3/3B1PP1/R3K1NR,
645 // Bxd2 Bxc3 Bxb4 Bxc3 Bxb4 etc.
647 fen: super.getBaseFen(),
652 positions
.some(p
=> {
654 p
.piece
== newPos
.piece
&&
655 p
.fen
== newPos
.fen
&&
656 p
.from == newPos
.from
660 // Start of an infinite loop: exit
663 positions
.push(newPos
);
664 const rank
= (color
== 'w' ? 0 : 7);
665 const moves
= this.getPotentialMovesFrom(fromSquare
);
666 if (moves
.some(m
=> m
.end
.x
== rank
&& files
.includes(m
.end
.y
)))
669 for (let m
of moves
) {
670 if (!!m
.end
.released
) {
671 // Turn won't change since !!m.released
673 const res
= this.isAttacked_aux(
674 files
, color
, positions
, [m
.end
.x
, m
.end
.y
], m
.end
.released
);
676 if (res
) return true;
682 isAttacked(files
, color
) {
683 const rank
= (color
== 'w' ? 0 : 7);
684 // Since it's too difficult (impossible?) to search from the square itself,
685 // let's adopt a suboptimal but working strategy: find all attacks.
687 // Artificial turn change is required:
690 outerLoop: for (let i
=0; i
<8; i
++) {
691 for (let j
=0; j
<8; j
++) {
692 // Attacks must start from a normal piece, not an union.
693 // Therefore, the following test is correct.
695 this.board
[i
][j
] != V
.EMPTY
&&
696 // Do not start with king (irrelevant, and lead to infinite calls)
697 [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].includes(
698 this.board
[i
][j
].charAt(1)) &&
699 this.board
[i
][j
].charAt(0) == color
702 const moves
= this.getPotentialMovesFrom([i
, j
]);
703 if (moves
.some(m
=> m
.end
.x
== rank
&& files
.includes(m
.end
.y
))) {
707 for (let m
of moves
) {
708 if (!!m
.end
.released
) {
709 // Turn won't change since !!m.released
712 res
= this.isAttacked_aux(
713 files
, color
, positions
, [m
.end
.x
, m
.end
.y
], m
.end
.released
);
715 if (res
) break outerLoop
;
725 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
726 for (let step
of steps
) {
727 let rx
= x
+ step
[0],
729 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
735 this.board
[rx
][ry
] != V
.EMPTY
&&
736 this.getPiece(rx
, ry
) == piece
&&
737 this.getColor(rx
, ry
) == color
&&
738 this.canTake([rx
, ry
], [x
, y
]) //TODO: necessary line?
739 //If not, generic method is OK
747 // Do not consider checks, except to forbid castling
753 if (moves
.length
== 0) return [];
754 const L
= (!this.pacoplay
? this.umoves
.length : 0);
755 return moves
.filter(m
=> {
756 if (L
> 0 && this.oppositeMoves(this.umoves
[L
- 1], m
)) return false;
757 if (!m
.end
.released
) return true;
758 // Check for repetitions:
759 V
.PlayOnBoard(this.board
, m
);
761 piece: m
.end
.released
,
762 square: { x: m
.end
.x
, y: m
.end
.y
},
763 position: this.getBaseFen()
766 this.repetitions
.some(r
=> {
768 r
.piece
== newState
.piece
&&
770 r
.square
.x
== newState
.square
.x
&&
771 r
.square
.y
== newState
.square
.y
773 r
.position
== newState
.position
776 V
.UndoOnBoard(this.board
, m
);
781 updateCastleFlags(move, piece
) {
783 const firstRank
= (c
== "w" ? 7 : 0);
784 if (piece
== V
.KING
&& move.appear
.length
> 0)
785 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
787 move.start
.x
== firstRank
&&
788 this.castleFlags
[c
].includes(move.start
.y
)
790 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
791 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
794 move.end
.x
== firstRank
&&
795 this.castleFlags
[c
].includes(move.end
.y
)
797 // Move to our rook: necessary normal piece, to union, releasing
798 // (or the rook was moved before!)
799 const flagIdx
= (move.end
.y
== this.castleFlags
[c
][0] ? 0 : 1);
800 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
805 // Easier before move is played in this case (flags are saved)
807 const L
= this.lastMoveEnd
.length
;
808 const lm
= this.lastMoveEnd
[L
-1];
809 // NOTE: lm.p != V.KING, always.
813 : this.getPiece(move.vanish
[0].x
, move.vanish
[0].y
);
815 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
816 this.updateCastleFlags(move, piece
);
817 const pawnFirstRank
= (c
== 'w' ? 6 : 1);
820 move.start
.x
== pawnFirstRank
&&
822 Math
.abs(move.end
.x
- move.start
.x
) == 2
824 // This move turns off a 2-squares pawn flag
825 this.pawnFlags
[c
][move.start
.y
] = false;
830 move.flags
= JSON
.stringify(this.aggregateFlags());
832 this.epSquares
.push(this.getEpSquare(move));
833 // Check if the move is the last of the turn: all cases except releases
834 if (!move.end
.released
) {
835 // No more union releases available
836 this.turn
= V
.GetOppCol(this.turn
);
838 this.lastMoveEnd
.push(null);
841 this.lastMoveEnd
.push({
842 p: move.end
.released
,
847 V
.PlayOnBoard(this.board
, move);
848 if (!this.pacoplay
) this.umoves
.push(this.getUmove(move));
849 if (!move.end
.released
) this.repetitions
= [];
851 this.repetitions
.push(
853 piece: move.end
.released
,
854 square: { x: move.end
.x
, y: move.end
.y
},
855 position: this.getBaseFen()
862 this.epSquares
.pop();
863 this.disaggregateFlags(JSON
.parse(move.flags
));
864 V
.UndoOnBoard(this.board
, move);
865 this.lastMoveEnd
.pop();
866 if (!move.end
.released
) {
867 this.turn
= V
.GetOppCol(this.turn
);
870 if (!this.pacoplay
) this.umoves
.pop();
871 if (!!move.end
.released
) this.repetitions
.pop();
876 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
877 this.kingPos
[this.turn
] = [move.start
.x
, move.start
.y
];
881 // Check kings: if one is dancing, the side lost
882 // But, if both dancing, let's say it's a draw :-)
883 const [kpW
, kpB
] = [this.kingPos
['w'], this.kingPos
['b']];
884 const atKingPlace
= [
885 this.board
[kpW
[0]][kpW
[1]].charAt(1),
886 this.board
[kpB
[0]][kpB
[1]].charAt(1)
888 if (!atKingPlace
.includes('k')) return "1/2";
889 if (atKingPlace
[0] != 'k') return "0-1";
890 if (atKingPlace
[1] != 'k') return "1-0";
895 let initMoves
= this.getAllValidMoves();
896 if (initMoves
.length
== 0) return null;
897 // Loop until valid move is found (no blocked pawn released...)
899 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
902 // Just play random moves (for now at least. TODO?)
903 while (moves
.length
> 0) {
904 mv
= moves
[randInt(moves
.length
)];
907 if (!!mv
.end
.released
)
908 // A piece was just released from an union
909 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
912 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
913 if (!mv
.end
.released
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
915 return null; //never reached
918 // NOTE: evalPosition() is wrong, but unused since bot plays at random
921 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
922 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
925 const L
= this.lastMoveEnd
.length
;
926 const lm
= this.lastMoveEnd
[L
-1];
928 if (!lm
&& move.vanish
.length
== 0)
929 // When importing a game, the info move.released is lost
930 piece
= move.appear
[0].p
;
931 else piece
= (!!lm
? lm
.p : move.vanish
[0].p
);
932 if (!(ChessRules
.PIECES
.includes(piece
))) {
933 // Decode (moving) union
934 const up
= this.getUnionPieces(
935 move.vanish
.length
> 0 ? move.vanish
[0].c : move.appear
[0].c
, piece
);
939 // Basic move notation:
940 let notation
= piece
.toUpperCase();
942 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
943 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
947 const finalSquare
= V
.CoordsToSquare(move.end
);
948 notation
+= finalSquare
;
950 // Add potential promotion indications:
951 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
952 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
) {
954 if (ChessRules
.PIECES
.includes(move.appear
[0].p
))
955 notation
+= move.appear
[0].p
.toUpperCase();
957 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
958 notation
+= up
[c
].toUpperCase();
962 move.end
.x
== firstLastRank
[0] &&
963 move.vanish
.length
> 0 &&
964 ['c', 'd', 'e', 'f', 'g'].includes(move.vanish
[0].p
)
966 // We promoted an opponent's pawn
967 const oppCol
= V
.GetOppCol(c
);
968 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
969 notation
+= "=" + up
[oppCol
].toUpperCase();