8e0c3cc6ac021aa7568b3a8816ef1ec1de5f08b2
1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
, shuffle
} from "@/utils/alea";
5 export class InterweaveRules
extends ChessRules
{
6 static get HasFlags() {
10 static GenRandInitFen(randomness
) {
12 return "rbnkknbr/pppppppp/8/8/8/8/PPPPPPPP/RBNKKNBR w 0 - 000000";
14 let pieces
= { w: new Array(8), b: new Array(8) };
15 for (let c
of ["w", "b"]) {
16 if (c
== 'b' && randomness
== 1) {
17 pieces
['b'] = pieces
['w'];
21 // Each pair of pieces on 2 colors:
22 const composition
= ['r', 'n', 'b', 'k', 'r', 'n', 'b', 'k'];
23 let positions
= shuffle(ArrayFun
.range(4));
24 for (let i
= 0; i
< 4; i
++)
25 pieces
[c
][2 * positions
[i
]] = composition
[i
];
26 positions
= shuffle(ArrayFun
.range(4));
27 for (let i
= 0; i
< 4; i
++)
28 pieces
[c
][2 * positions
[i
] + 1] = composition
[i
];
31 pieces
["b"].join("") +
32 "/pppppppp/8/8/8/8/PPPPPPPP/" +
33 pieces
["w"].join("").toUpperCase() +
34 // En-passant allowed, but no flags
39 static IsGoodFen(fen
) {
40 if (!ChessRules
.IsGoodFen(fen
)) return false;
41 const fenParsed
= V
.ParseFen(fen
);
43 if (!fenParsed
.captured
|| !fenParsed
.captured
.match(/^[0-9]{6,6}$/))
48 static IsGoodPosition(position
) {
49 if (position
.length
== 0) return false;
50 const rows
= position
.split("/");
51 if (rows
.length
!= V
.size
.x
) return false;
52 let kings
= { "k": 0, "K": 0 };
53 for (let row
of rows
) {
55 for (let i
= 0; i
< row
.length
; i
++) {
56 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
57 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
59 const num
= parseInt(row
[i
]);
60 if (isNaN(num
)) return false;
64 if (sumElts
!= V
.size
.y
) return false;
66 // Both kings should be on board. Exactly two per color.
67 if (Object
.values(kings
).some(v
=> v
!= 2)) return false;
71 static ParseFen(fen
) {
72 const fenParts
= fen
.split(" ");
74 ChessRules
.ParseFen(fen
),
75 { captured: fenParts
[4] }
80 return super.getFen() + " " + this.getCapturedFen();
84 return super.getFenForRepeat() + "_" + this.getCapturedFen();
88 let counts
= [...Array(6).fill(0)];
89 [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
].forEach((p
,idx
) => {
90 counts
[idx
] = this.captured
["w"][p
];
91 counts
[3 + idx
] = this.captured
["b"][p
];
93 return counts
.join("");
98 setOtherVariables(fen
) {
99 super.setOtherVariables(fen
);
100 const fenParsed
= V
.ParseFen(fen
);
101 // Initialize captured pieces' counts from FEN
104 [V
.ROOK
]: parseInt(fenParsed
.captured
[0]),
105 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[1]),
106 [V
.BISHOP
]: parseInt(fenParsed
.captured
[2]),
109 [V
.ROOK
]: parseInt(fenParsed
.captured
[3]),
110 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[4]),
111 [V
.BISHOP
]: parseInt(fenParsed
.captured
[5]),
114 // Stack of "last move" only for intermediate captures
115 this.lastMoveEnd
= [null];
118 // Trim all non-capturing moves
119 static KeepCaptures(moves
) {
120 return moves
.filter(m
=> m
.vanish
.length
>= 2 || m
.appear
.length
== 0);
123 // Stop at the first capture found (if any)
124 atLeastOneCapture() {
125 const color
= this.turn
;
126 for (let i
= 0; i
< V
.size
.x
; i
++) {
127 for (let j
= 0; j
< V
.size
.y
; j
++) {
129 this.board
[i
][j
] != V
.EMPTY
&&
130 this.getColor(i
, j
) == color
&&
131 V
.KeepCaptures(this.getPotentialMovesFrom([i
, j
])).length
> 0
140 // En-passant after 2-sq jump
141 getEpSquare(moveOrSquare
) {
142 if (!moveOrSquare
) return undefined;
143 if (typeof moveOrSquare
=== "string") {
144 const square
= moveOrSquare
;
145 if (square
== "-") return undefined;
146 // Enemy pawn initial column must be given too:
148 const epParts
= square
.split(",");
149 res
.push(V
.SquareToCoords(epParts
[0]));
150 res
.push(V
.ColumnToCoord(epParts
[1]));
153 // Argument is a move:
154 const move = moveOrSquare
;
155 const [sx
, ex
, sy
, ey
] =
156 [move.start
.x
, move.end
.x
, move.start
.y
, move.end
.y
];
158 move.vanish
.length
== 1 &&
159 this.getPiece(sx
, sy
) == V
.PAWN
&&
160 Math
.abs(sx
- ex
) == 2 &&
161 Math
.abs(sy
- ey
) == 2
168 // The arrival column must be remembered, because
169 // potentially two pawns could be candidates to be captured:
170 // one on our left, and one on our right.
174 return undefined; //default
177 static IsGoodEnpassant(enpassant
) {
178 if (enpassant
!= "-") {
179 const epParts
= enpassant
.split(",");
180 const epSq
= V
.SquareToCoords(epParts
[0]);
181 if (isNaN(epSq
.x
) || isNaN(epSq
.y
) || !V
.OnBoard(epSq
)) return false;
182 const arrCol
= V
.ColumnToCoord(epParts
[1]);
183 if (isNaN(arrCol
) || arrCol
< 0 || arrCol
>= V
.size
.y
) return false;
189 const L
= this.epSquares
.length
;
190 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
192 V
.CoordsToSquare(this.epSquares
[L
- 1][0]) +
194 V
.CoordToColumn(this.epSquares
[L
- 1][1])
198 getPotentialMovesFrom([x
, y
]) {
199 switch (this.getPiece(x
, y
)) {
201 return this.getPotentialPawnMoves([x
, y
]);
203 return this.getPotentialRookMoves([x
, y
]);
205 return this.getPotentialKnightMoves([x
, y
]);
207 return this.getPotentialBishopMoves([x
, y
]);
209 return this.getPotentialKingMoves([x
, y
]);
215 // Special pawns movements
216 getPotentialPawnMoves([x
, y
]) {
217 const color
= this.turn
;
218 const oppCol
= V
.GetOppCol(color
);
220 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
221 const shiftX
= color
== "w" ? -1 : 1;
222 const startRank
= color
== "w" ? sizeX
- 2 : 1;
223 const potentialFinalPieces
=
224 [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
].filter(p
=> this.captured
[color
][p
] > 0);
225 const lastRanks
= (color
== "w" ? [0, 1] : [sizeX
- 1, sizeX
- 2]);
226 if (x
+ shiftX
== lastRanks
[0] && potentialFinalPieces
.length
== 0)
227 // If no captured piece is available, the pawn cannot promote
231 x
+ shiftX
== lastRanks
[0]
232 ? potentialFinalPieces
234 x
+ shiftX
== lastRanks
[1]
235 ? potentialFinalPieces
.concat([V
.PAWN
])
237 // One square diagonally
238 for (let shiftY
of [-1, 1]) {
239 if (this.board
[x
+ shiftX
][y
+ shiftY
] == V
.EMPTY
) {
240 for (let piece
of finalPieces1
) {
242 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
249 V
.PawnSpecs
.twoSquares
&&
251 y
+ 2 * shiftY
>= 0 &&
252 y
+ 2 * shiftY
< sizeY
&&
253 this.board
[x
+ 2 * shiftX
][y
+ 2 * shiftY
] == V
.EMPTY
257 this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
+ 2 * shiftY
])
264 x
+ 2 * shiftX
== lastRanks
[0]
265 ? potentialFinalPieces
267 x
+ 2 * shiftX
== lastRanks
[1]
268 ? potentialFinalPieces
.concat([V
.PAWN
])
271 this.board
[x
+ shiftX
][y
] != V
.EMPTY
&&
272 this.canTake([x
, y
], [x
+ shiftX
, y
]) &&
273 V
.OnBoard(x
+ 2 * shiftX
, y
) &&
274 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
276 const oppPiece
= this.getPiece(x
+ shiftX
, y
);
277 for (let piece
of finalPieces2
) {
278 let mv
= this.getBasicMove(
279 [x
, y
], [x
+ 2 * shiftX
, y
], { c: color
, p: piece
});
291 const Lep
= this.epSquares
.length
;
292 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
295 epSquare
[0].x
== x
+ shiftX
&&
296 epSquare
[0].y
== y
&&
297 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
299 for (let piece
of finalPieces2
) {
302 [x
, y
], [x
+ 2 * shiftX
, y
], { c: color
, p: piece
});
303 enpassantMove
.vanish
.push({
307 c: this.getColor(x
, epSquare
[1])
309 moves
.push(enpassantMove
);
313 // Add custodian captures:
314 const steps
= V
.steps
[V
.ROOK
];
316 // Try capturing in every direction
317 for (let step
of steps
) {
318 const sq2
= [m
.end
.x
+ 2 * step
[0], m
.end
.y
+ 2 * step
[1]];
320 V
.OnBoard(sq2
[0], sq2
[1]) &&
321 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
322 this.getColor(sq2
[0], sq2
[1]) == color
325 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
327 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
328 this.getColor(sq1
[0], sq1
[1]) == oppCol
335 p: this.getPiece(sq1
[0], sq1
[1])
346 getSlides([x
, y
], steps
, options
) {
347 options
= options
|| {};
350 outerLoop: for (let step
of steps
) {
354 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
355 if (!options
["doubleStep"] || counter
% 2 == 0)
356 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
357 if (!!options
["oneStep"]) continue outerLoop
;
367 getPotentialRookMoves([x
, y
]) {
369 this.getSlides([x
, y
], V
.steps
[V
.ROOK
], { doubleStep: true })
370 .concat(this.getSlides([x
, y
], V
.steps
[V
.BISHOP
]));
372 const oppCol
= V
.GetOppCol(this.turn
);
374 const delta
= [m
.end
.x
- m
.start
.x
, m
.end
.y
- m
.start
.y
];
376 delta
[0] / Math
.abs(delta
[0]) || 0,
377 delta
[1] / Math
.abs(delta
[1]) || 0
379 if (step
[0] == 0 || step
[1] == 0) {
380 // Rook-like move, candidate for capturing
381 const [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
384 this.board
[i
][j
] != V
.EMPTY
&&
385 this.getColor(i
, j
) == oppCol
390 p: this.getPiece(i
, j
),
400 getPotentialKnightMoves([x
, y
]) {
402 this.getSlides([x
, y
], V
.steps
[V
.ROOK
], { doubleStep: true })
403 .concat(this.getSlides([x
, y
], V
.steps
[V
.BISHOP
]));
404 const oppCol
= V
.GetOppCol(this.turn
);
405 // Look for double-knight moves (could capture):
406 for (let step
of V
.steps
[V
.KNIGHT
]) {
407 const [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
408 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
409 const [ii
, jj
] = [x
+ step
[0], y
+ step
[1]];
410 if (this.board
[ii
][jj
] == V
.EMPTY
|| this.getColor(ii
, jj
) == oppCol
) {
411 let mv
= this.getBasicMove([x
, y
], [i
, j
]);
412 if (this.board
[ii
][jj
] != V
.EMPTY
) {
417 p: this.getPiece(ii
, jj
)
424 // Look for an enemy in every orthogonal direction
425 for (let step
of V
.steps
[V
.ROOK
]) {
426 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
428 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
436 this.getColor(i
, j
) == oppCol
438 const oppPiece
= this.getPiece(i
, j
);
439 // Candidate for capture: can I land after?
440 let [ii
, jj
] = [i
+ step
[0], j
+ step
[1]];
442 while (V
.OnBoard(ii
, jj
) && this.board
[ii
][jj
] == V
.EMPTY
) {
443 if (counter
% 2 == 0) {
444 // Same color: add capture
445 let mv
= this.getBasicMove([x
, y
], [ii
, jj
]);
464 getPotentialBishopMoves([x
, y
]) {
465 let moves
= this.getSlides([x
, y
], V
.steps
[V
.BISHOP
]);
467 const oppCol
= V
.GetOppCol(this.turn
);
469 for (let step
of V
.steps
[V
.ROOK
]) {
470 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
473 this.board
[i
][j
] != V
.EMPTY
&&
474 this.getColor(i
, j
) == oppCol
476 captures
.push([i
, j
]);
479 captures
.forEach(c
=> {
481 start: { x: x
, y: y
},
482 end: { x: c
[0], y: c
[1] },
484 vanish: captures
.map(ct
=> {
489 p: this.getPiece(ct
[0], ct
[1])
497 getPotentialKingMoves([x
, y
]) {
498 let moves
= this.getSlides([x
, y
], V
.steps
[V
.BISHOP
], { oneStep: true });
500 const oppCol
= V
.GetOppCol(this.turn
);
501 for (let step
of V
.steps
[V
.ROOK
]) {
502 const [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
503 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
504 const [ii
, jj
] = [x
+ step
[0], y
+ step
[1]];
505 if (this.board
[ii
][jj
] != V
.EMPTY
&& this.getColor(ii
, jj
) == oppCol
) {
506 let mv
= this.getBasicMove([x
, y
], [i
, j
]);
511 p: this.getPiece(ii
, jj
)
520 getPossibleMovesFrom(sq
) {
521 const L
= this.lastMoveEnd
.length
;
523 !!this.lastMoveEnd
[L
-1] &&
525 sq
[0] != this.lastMoveEnd
[L
-1].x
||
526 sq
[1] != this.lastMoveEnd
[L
-1].y
531 let moves
= this.getPotentialMovesFrom(sq
);
532 const captureMoves
= V
.KeepCaptures(moves
);
533 if (captureMoves
.length
> 0) return captureMoves
;
534 if (this.atLeastOneCapture()) return [];
539 const moves
= this.getAllPotentialMoves();
540 const captures
= V
.KeepCaptures(moves
);
541 if (captures
.length
> 0) return captures
;
551 this.epSquares
.push(this.getEpSquare(move));
552 V
.PlayOnBoard(this.board
, move);
553 if (move.vanish
.length
>= 2) {
554 // Capture: update this.captured
555 for (let i
=1; i
<move.vanish
.length
; i
++)
556 this.captured
[move.vanish
[i
].c
][move.vanish
[i
].p
]++;
558 // Check if the move is the last of the turn
559 if (move.vanish
.length
>= 2 || move.appear
.length
== 0) {
560 const moreCaptures
= (
562 this.getPotentialMovesFrom([move.end
.x
, move.end
.y
])
566 move.last
= !moreCaptures
;
568 else move.last
= true;
570 // No capture, or no more capture available
571 this.turn
= V
.GetOppCol(this.turn
);
573 this.lastMoveEnd
.push(null);
574 move.last
= true; //will be used in undo and computer play
576 else this.lastMoveEnd
.push(move.end
);
580 this.epSquares
.pop();
581 this.lastMoveEnd
.pop();
582 V
.UndoOnBoard(this.board
, move);
583 if (move.vanish
.length
>= 2) {
584 for (let i
=1; i
<move.vanish
.length
; i
++)
585 this.captured
[move.vanish
[i
].c
][move.vanish
[i
].p
]--;
588 this.turn
= V
.GetOppCol(this.turn
);
598 // Count kings: if one is missing, the side lost
599 let kingsCount
= { 'w': 0, 'b': 0 };
600 for (let i
=0; i
<8; i
++) {
601 for (let j
=0; j
<8; j
++) {
602 if (this.board
[i
][j
] != V
.EMPTY
&& this.getPiece(i
, j
) == V
.KING
)
603 kingsCount
[this.getColor(i
, j
)]++;
606 if (kingsCount
['w'] < 2) return "0-1";
607 if (kingsCount
['b'] < 2) return "1-0";
612 let moves
= this.getAllValidMoves();
613 if (moves
.length
== 0) return null;
614 // Just play random moves (for now at least. TODO?)
616 while (moves
.length
> 0) {
617 const mv
= moves
[randInt(moves
.length
)];
621 moves
= V
.KeepCaptures(
622 this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]));
626 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
627 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
631 const initialSquare
= V
.CoordsToSquare(move.start
);
632 const finalSquare
= V
.CoordsToSquare(move.end
);
633 if (move.appear
.length
== 0)
634 // Remover captures 'R'
635 return initialSquare
+ "R";
636 let notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
637 // Add a capture mark (not describing what is captured...):
638 if (move.vanish
.length
>= 2) notation
+= "X";