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)
36 static IsGoodPosition(position
) {
37 if (position
.length
== 0) return false;
38 const rows
= position
.split("/");
39 if (rows
.length
!= V
.size
.x
) return false;
40 let kingSymb
= ['k', 'g', 'm', 'u', 'x'];
41 let kings
= { 'k': 0, 'K': 0 };
42 for (let row
of rows
) {
44 for (let i
= 0; i
< row
.length
; i
++) {
45 const lowR
= row
[i
].toLowerCase
46 if (!!(row
[i
].toLowerCase().match(/[a-z]/))) {
48 if (kingSymb
.includes(row
[i
])) kings
['k']++;
49 else if (kingSymb
.some(s
=> row
[i
] == s
.toUpperCase())) kings
['K']++;
52 const num
= parseInt(row
[i
], 10);
53 if (isNaN(num
) || num
<= 0) return false;
57 if (sumElts
!= V
.size
.y
) return false;
59 // Both kings should be on board. Exactly one per color.
60 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
65 return "Pacosako/" + b
;
69 if (ChessRules
.PIECES
.includes(m
.appear
[0].p
)) return super.getPPpath(m
);
70 // For an union, show only relevant piece:
71 // The color must be deduced from the move: reaching final rank of who?
72 const color
= (m
.appear
[0].x
== 0 ? 'w' : 'b');
73 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
74 return "Pacosako/" + color
+ up
[color
];
77 canTake([x1
, y1
], [x2
, y2
]) {
78 const p1
= this.board
[x1
][y1
].charAt(1);
79 if (!(ChessRules
.PIECES
.includes(p1
))) return false;
80 const p2
= this.board
[x2
][y2
].charAt(1);
81 if (!(ChessRules
.PIECES
.includes(p2
))) return true;
82 const c1
= this.board
[x1
][y1
].charAt(0);
83 const c2
= this.board
[x2
][y2
].charAt(0);
87 canIplay(side
, [x
, y
]) {
91 !(ChessRules
.PIECES
.includes(this.board
[x
][y
].charAt(1))) ||
92 this.board
[x
][y
].charAt(0) == side
98 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
99 const fenRows
= V
.ParseFen(fen
).position
.split("/");
100 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
101 const kingSymb
= ['k', 'g', 'm', 'u', 'x'];
102 for (let i
= 0; i
< fenRows
.length
; i
++) {
104 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
105 const c
= fenRows
[i
].charAt(j
);
106 if (kingSymb
.includes(c
))
107 this.kingPos
["b"] = [i
, k
];
108 else if (kingSymb
.some(s
=> c
== s
.toUpperCase()))
109 this.kingPos
["w"] = [i
, k
];
111 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
112 if (!isNaN(num
)) k
+= num
- 1;
119 setOtherVariables(fen
) {
120 super.setOtherVariables(fen
);
121 // Stack of "last move" only for intermediate chaining
122 this.lastMoveEnd
= [null];
123 // Local stack of non-capturing union moves:
125 const umove
= V
.ParseFen(fen
).umove
;
126 if (umove
== "-") this.umoves
.push(null);
129 start: ChessRules
.SquareToCoords(umove
.substr(0, 2)),
130 end: ChessRules
.SquareToCoords(umove
.substr(2))
135 static IsGoodFen(fen
) {
136 if (!ChessRules
.IsGoodFen(fen
)) return false;
137 const fenParts
= fen
.split(" ");
138 if (fenParts
.length
!= 6) return false;
139 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
146 move.vanish
.length
== 1 &&
147 !(ChessRules
.PIECES
.includes(move.appear
[0].p
))
150 return { start: move.start
, end: move.end
};
155 static ParseFen(fen
) {
156 const fenParts
= fen
.split(" ");
157 return Object
.assign(
158 ChessRules
.ParseFen(fen
),
159 { umove: fenParts
[5] }
163 static GenRandInitFen(randomness
) {
165 return ChessRules
.GenRandInitFen(randomness
) + " -";
169 const L
= this.umoves
.length
;
173 : ChessRules
.CoordsToSquare(this.umoves
[L
- 1].start
) +
174 ChessRules
.CoordsToSquare(this.umoves
[L
- 1].end
)
179 return super.getFen() + " " + this.getUmoveFen();
183 return super.getFenForRepeat() + "_" + this.getUmoveFen();
187 const p
= this.board
[i
][j
].charAt(1);
188 if (ChessRules
.PIECES
.includes(p
)) return super.getColor(i
, j
);
189 return this.turn
; //union: I can use it, so it's "my" color...
192 getPiece(i
, j
, color
) {
193 const p
= this.board
[i
][j
].charAt(1);
194 if (ChessRules
.PIECES
.includes(p
)) return p
;
195 const c
= this.board
[i
][j
].charAt(0);
196 // NOTE: this.turn == HACK, but should work...
197 color
= color
|| this.turn
;
198 return V
.UNIONS
[p
][c
== color
? 0 : 1];
201 getUnionPieces(color
, code
) {
202 const pieces
= V
.UNIONS
[code
];
204 w: pieces
[color
== 'w' ? 0 : 1],
205 b: pieces
[color
== 'b' ? 0 : 1]
209 // p1: white piece, p2: black piece
210 getUnionCode(p1
, p2
) {
212 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p1
&& v
[1] == p2
)
214 const c
= (uIdx
>= 0 ? 'w' : 'b');
217 Object
.values(V
.UNIONS
).findIndex(v
=> v
[0] == p2
&& v
[1] == p1
)
220 return { c: c
, p: Object
.keys(V
.UNIONS
)[uIdx
] };
223 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
224 const L
= this.lastMoveEnd
.length
;
225 const lm
= this.lastMoveEnd
[L
-1];
226 const piece
= (!!lm
? lm
.p : null);
227 const initColor
= (!!piece
? this.turn : this.board
[sx
][sy
].charAt(0));
228 const initPiece
= (piece
|| this.board
[sx
][sy
].charAt(1));
230 const oppCol
= V
.GetOppCol(c
);
231 if (!!tr
&& !(ChessRules
.PIECES
.includes(initPiece
))) {
232 // Transformation computed without taking union into account
233 const up
= this.getUnionPieces(initColor
, initPiece
);
234 let args
= [tr
.p
, up
[oppCol
]];
235 if (c
== 'b') args
= args
.reverse();
236 const cp
= this.getUnionCode(args
[0], args
[1]);
241 // - union to free square (other cases are illegal: return null)
242 // - normal piece to free square,
243 // to enemy normal piece, or
244 // to union (releasing our piece)
246 start: { x: sx
, y: sy
},
247 end: { x: ex
, y: ey
},
260 // Treat free square cases first:
261 if (this.board
[ex
][ey
] == V
.EMPTY
) {
266 c: !!tr
? tr
.c : initColor
,
267 p: !!tr
? tr
.p : initPiece
272 // Now the two cases with union / release:
273 const destColor
= this.board
[ex
][ey
].charAt(0);
274 const destPiece
= this.board
[ex
][ey
].charAt(1);
283 if (ChessRules
.PIECES
.includes(destPiece
)) {
284 // Normal piece: just create union
285 let args
= [!!tr
? tr
.p : initPiece
, destPiece
];
286 if (c
== 'b') args
= args
.reverse();
287 const cp
= this.getUnionCode(args
[0], args
[1]);
298 // Releasing a piece in an union: keep track of released piece
299 const up
= this.getUnionPieces(destColor
, destPiece
);
300 let args
= [!!tr
? tr
.p : initPiece
, up
[oppCol
]];
301 if (c
== 'b') args
= args
.reverse();
302 const cp
= this.getUnionCode(args
[0], args
[1]);
315 getPotentialMovesFrom([x
, y
]) {
316 const L
= this.lastMoveEnd
.length
;
317 const lm
= this.lastMoveEnd
[L
-1];
318 if (!!lm
&& (x
!= lm
.x
|| y
!= lm
.y
)) return [];
319 const piece
= (!!lm
? lm
.p : this.getPiece(x
, y
));
321 var saveSquare
= this.board
[x
][y
];
322 this.board
[x
][y
] = this.turn
+ piece
;
325 switch (piece
|| this.getPiece(x
, y
)) {
327 baseMoves
= this.getPotentialPawnMoves([x
, y
]);
330 baseMoves
= this.getPotentialRookMoves([x
, y
]);
333 baseMoves
= this.getPotentialKnightMoves([x
, y
]);
336 baseMoves
= this.getPotentialBishopMoves([x
, y
]);
339 baseMoves
= this.getPotentialQueenMoves([x
, y
]);
342 baseMoves
= this.getPotentialKingMoves([x
, y
]);
345 // When a pawn in an union reaches final rank with a non-standard
346 // promotion move: apply promotion anyway
349 const oppCol
= V
.GetOppCol(c
);
350 const oppLastRank
= (c
== 'w' ? 7 : 0);
351 baseMoves
.forEach(m
=> {
353 m
.end
.x
== oppLastRank
&&
354 ['c', 'd', 'e', 'f', 'g'].includes(m
.appear
[0].p
)
356 // Move to first rank, which is last rank for opponent's pawn.
357 // => Show promotion choices.
358 // Find our piece in union (not a pawn)
359 const up
= this.getUnionPieces(m
.appear
[0].c
, m
.appear
[0].p
);
360 // merge with all potential promotion pieces + push (loop)
361 for (let promotionPiece
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
362 let args
= [up
[c
], promotionPiece
];
363 if (c
== 'b') args
= args
.reverse();
364 const cp
= this.getUnionCode(args
[0], args
[1]);
365 let cpMove
= JSON
.parse(JSON
.stringify(m
));
366 cpMove
.appear
[0].c
= cp
.c
;
367 cpMove
.appear
[0].p
= cp
.p
;
373 m
.vanish
.length
> 0 &&
374 m
.vanish
[0].p
== V
.PAWN
&&
375 m
.start
.y
!= m
.end
.y
&&
376 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
379 // No en-passant inside a chaining
381 // Fix en-passant capture: union type, maybe released piece too
382 const cs
= [m
.end
.x
+ (c
== 'w' ? 1 : -1), m
.end
.y
];
383 const color
= this.board
[cs
[0]][cs
[1]].charAt(0);
384 const code
= this.board
[cs
[0]][cs
[1]].charAt(1);
385 if (code
== V
.PAWN
) {
386 // Simple en-passant capture (usual: just form union)
391 // An union pawn + something juste moved two squares
392 const up
= this.getUnionPieces(color
, code
);
394 let args
= [V
.PAWN
, up
[oppCol
]];
395 if (c
== 'b') args
= args
.reverse();
396 const cp
= this.getUnionCode(args
[0], args
[1]);
397 m
.appear
[0].c
= cp
.c
;
398 m
.appear
[0].p
= cp
.p
;
404 if (!!lm
) this.board
[x
][y
] = saveSquare
;
408 getPotentialKingMoves(sq
) {
409 let moves
= this.getSlideNJumpMoves(
411 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
415 const oppCol
= V
.GetOppCol(c
);
417 !this.isAttacked(this.kingPos
[c
], oppCol
) &&
418 this.castleFlags
[c
].some(v
=> v
< V
.size
.y
)
420 moves
= moves
.concat(super.getCastleMoves(sq
, null, true));
425 getEpSquare(moveOrSquare
) {
426 if (typeof moveOrSquare
=== "string") {
427 const square
= moveOrSquare
;
428 if (square
== "-") return undefined;
429 return V
.SquareToCoords(square
);
431 const move = moveOrSquare
;
432 const s
= move.start
,
434 const oppCol
= V
.GetOppCol(this.turn
);
437 Math
.abs(s
.x
- e
.x
) == 2 &&
438 this.getPiece(s
.x
, s
.y
, oppCol
) == V
.PAWN
448 // Does m2 un-do m1 ? (to disallow undoing union moves)
449 oppositeMoves(m1
, m2
) {
452 !(ChessRules
.PIECES
.includes(m2
.appear
[0].p
)) &&
453 m2
.vanish
.length
== 1 &&
454 m1
.start
.x
== m2
.end
.x
&&
455 m1
.end
.x
== m2
.start
.x
&&
456 m1
.start
.y
== m2
.end
.y
&&
457 m1
.end
.y
== m2
.start
.y
461 // Do not consider checks for now (TODO)
469 if (moves
.length
== 0) return [];
470 const L
= this.umoves
.length
; //at least 1: init from FEN
471 return moves
.filter(m
=> !this.oppositeMoves(this.umoves
[L
- 1], m
));
475 this.epSquares
.push(this.getEpSquare(move));
476 // Check if the move is the last of the turn: all cases except releases
477 if (!move.released
) {
478 // No more union releases available
479 this.turn
= V
.GetOppCol(this.turn
);
481 this.lastMoveEnd
.push(null);
483 else this.lastMoveEnd
.push(Object
.assign({ p: move.released
}, move.end
));
484 V
.PlayOnBoard(this.board
, move);
485 this.umoves
.push(this.getUmove(move));
490 if (move.vanish
.length
== 0)
491 // A piece released just moved. Cannot be the king.
493 const c
= move.vanish
[0].c
;
494 const piece
= move.vanish
[0].p
;
496 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
497 this.updateCastleFlags(move, piece
);
501 this.epSquares
.pop();
502 V
.UndoOnBoard(this.board
, move);
503 this.lastMoveEnd
.pop();
504 if (!move.released
) {
505 this.turn
= V
.GetOppCol(this.turn
);
513 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
514 this.kingPos
[this.turn
] = [move.start
.x
, move.start
.y
];
518 // Check kings: if one is dancing, the side lost
519 // But, if both dancing, let's say it's a draw :-)
520 const [kpW
, kpB
] = [this.kingPos
['w'], this.kingPos
['b']];
521 const atKingPlace
= [
522 this.board
[kpW
[0]][kpW
[1]].charAt(1),
523 this.board
[kpB
[0]][kpB
[1]].charAt(1)
525 if (!atKingPlace
.includes('k')) return "1/2";
526 if (atKingPlace
[0] != 'k') return "0-1";
527 if (atKingPlace
[1] != 'k') return "1-0";
532 let initMoves
= this.getAllValidMoves();
533 if (initMoves
.length
== 0) return null;
534 // Loop until valid move is found (no blocked pawn released...)
536 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
539 // Just play random moves (for now at least. TODO?)
540 while (moves
.length
> 0) {
541 mv
= moves
[randInt(moves
.length
)];
545 // A piece was just released from an union
546 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
549 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
550 if (!mv
.released
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
554 // NOTE: evalPosition() is wrong, but unused since bot plays at random
557 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
558 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
561 const L
= this.lastMoveEnd
.length
;
562 const lm
= this.lastMoveEnd
[L
-1];
564 if (!lm
&& move.vanish
.length
== 0)
565 // When importing a game, the info move.released is lost
566 piece
= move.appear
[0].p
;
567 else piece
= (!!lm
? lm
.p : move.vanish
[0].p
);
568 if (!(ChessRules
.PIECES
.includes(piece
))) {
569 // Decode (moving) union
570 const up
= this.getUnionPieces(
571 move.vanish
.length
> 0 ? move.vanish
[0].c : move.appear
[0].c
, piece
);
575 // Basic move notation:
576 let notation
= piece
.toUpperCase();
578 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
579 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
583 const finalSquare
= V
.CoordsToSquare(move.end
);
584 notation
+= finalSquare
;
586 // Add potential promotion indications:
587 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
588 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
) {
589 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
590 notation
+= "=" + up
[c
].toUpperCase();
593 move.end
.x
== firstLastRank
[0] &&
594 move.vanish
.length
> 0 &&
595 ['c', 'd', 'e', 'f', 'g'].includes(move.vanish
[0].p
)
597 // We promoted an opponent's pawn
598 const oppCol
= V
.GetOppCol(c
);
599 const up
= this.getUnionPieces(move.appear
[0].c
, move.appear
[0].p
);
600 notation
+= "=" + up
[oppCol
].toUpperCase();