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
], 10);
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
);
101 V
.ParseFen(fen
).captured
.split("").map(x
=> parseInt(x
, 10));
102 // Initialize captured pieces' counts from FEN
105 [V
.ROOK
]: captured
[0],
106 [V
.KNIGHT
]: captured
[1],
107 [V
.BISHOP
]: captured
[2]
110 [V
.ROOK
]: captured
[3],
111 [V
.KNIGHT
]: captured
[4],
112 [V
.BISHOP
]: captured
[5]
115 // Stack of "last move" only for intermediate captures
116 this.lastMoveEnd
= [null];
119 // Trim all non-capturing moves
120 static KeepCaptures(moves
) {
121 return moves
.filter(m
=> m
.vanish
.length
>= 2 || m
.appear
.length
== 0);
124 // Stop at the first capture found (if any)
125 atLeastOneCapture() {
126 const color
= this.turn
;
127 for (let i
= 0; i
< V
.size
.x
; i
++) {
128 for (let j
= 0; j
< V
.size
.y
; j
++) {
130 this.board
[i
][j
] != V
.EMPTY
&&
131 this.getColor(i
, j
) == color
&&
132 V
.KeepCaptures(this.getPotentialMovesFrom([i
, j
])).length
> 0
141 // En-passant after 2-sq jump
142 getEpSquare(moveOrSquare
) {
143 if (!moveOrSquare
) return undefined;
144 if (typeof moveOrSquare
=== "string") {
145 const square
= moveOrSquare
;
146 if (square
== "-") return undefined;
147 // Enemy pawn initial column must be given too:
149 const epParts
= square
.split(",");
150 res
.push(V
.SquareToCoords(epParts
[0]));
151 res
.push(V
.ColumnToCoord(epParts
[1]));
154 // Argument is a move:
155 const move = moveOrSquare
;
156 const [sx
, ex
, sy
, ey
] =
157 [move.start
.x
, move.end
.x
, move.start
.y
, move.end
.y
];
159 move.vanish
.length
== 1 &&
160 this.getPiece(sx
, sy
) == V
.PAWN
&&
161 Math
.abs(sx
- ex
) == 2 &&
162 Math
.abs(sy
- ey
) == 2
169 // The arrival column must be remembered, because
170 // potentially two pawns could be candidates to be captured:
171 // one on our left, and one on our right.
175 return undefined; //default
178 static IsGoodEnpassant(enpassant
) {
179 if (enpassant
!= "-") {
180 const epParts
= enpassant
.split(",");
181 const epSq
= V
.SquareToCoords(epParts
[0]);
182 if (isNaN(epSq
.x
) || isNaN(epSq
.y
) || !V
.OnBoard(epSq
)) return false;
183 const arrCol
= V
.ColumnToCoord(epParts
[1]);
184 if (isNaN(arrCol
) || arrCol
< 0 || arrCol
>= V
.size
.y
) return false;
190 const L
= this.epSquares
.length
;
191 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
193 V
.CoordsToSquare(this.epSquares
[L
- 1][0]) +
195 V
.CoordToColumn(this.epSquares
[L
- 1][1])
199 getPotentialMovesFrom([x
, y
]) {
200 switch (this.getPiece(x
, y
)) {
202 return this.getPotentialPawnMoves([x
, y
]);
204 return this.getPotentialRookMoves([x
, y
]);
206 return this.getPotentialKnightMoves([x
, y
]);
208 return this.getPotentialBishopMoves([x
, y
]);
210 return this.getPotentialKingMoves([x
, y
]);
216 // Special pawns movements
217 getPotentialPawnMoves([x
, y
]) {
218 const color
= this.turn
;
219 const oppCol
= V
.GetOppCol(color
);
221 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
222 const shiftX
= color
== "w" ? -1 : 1;
223 const startRank
= color
== "w" ? sizeX
- 2 : 1;
224 const potentialFinalPieces
=
225 [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
].filter(p
=> this.captured
[color
][p
] > 0);
226 const lastRanks
= (color
== "w" ? [0, 1] : [sizeX
- 1, sizeX
- 2]);
227 if (x
+ shiftX
== lastRanks
[0] && potentialFinalPieces
.length
== 0)
228 // If no captured piece is available, the pawn cannot promote
232 x
+ shiftX
== lastRanks
[0]
233 ? potentialFinalPieces
235 x
+ shiftX
== lastRanks
[1]
236 ? potentialFinalPieces
.concat([V
.PAWN
])
238 // One square diagonally
239 for (let shiftY
of [-1, 1]) {
240 if (this.board
[x
+ shiftX
][y
+ shiftY
] == V
.EMPTY
) {
241 for (let piece
of finalPieces1
) {
243 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
250 V
.PawnSpecs
.twoSquares
&&
252 y
+ 2 * shiftY
>= 0 &&
253 y
+ 2 * shiftY
< sizeY
&&
254 this.board
[x
+ 2 * shiftX
][y
+ 2 * shiftY
] == V
.EMPTY
258 this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
+ 2 * shiftY
])
265 x
+ 2 * shiftX
== lastRanks
[0]
266 ? potentialFinalPieces
268 x
+ 2 * shiftX
== lastRanks
[1]
269 ? potentialFinalPieces
.concat([V
.PAWN
])
272 this.board
[x
+ shiftX
][y
] != V
.EMPTY
&&
273 this.canTake([x
, y
], [x
+ shiftX
, y
]) &&
274 V
.OnBoard(x
+ 2 * shiftX
, y
) &&
275 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
277 const oppPiece
= this.getPiece(x
+ shiftX
, y
);
278 for (let piece
of finalPieces2
) {
279 let mv
= this.getBasicMove(
280 [x
, y
], [x
+ 2 * shiftX
, y
], { c: color
, p: piece
});
292 const Lep
= this.epSquares
.length
;
293 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
296 epSquare
[0].x
== x
+ shiftX
&&
297 epSquare
[0].y
== y
&&
298 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
300 for (let piece
of finalPieces2
) {
303 [x
, y
], [x
+ 2 * shiftX
, y
], { c: color
, p: piece
});
304 enpassantMove
.vanish
.push({
308 c: this.getColor(x
, epSquare
[1])
310 moves
.push(enpassantMove
);
314 // Add custodian captures:
315 const steps
= V
.steps
[V
.ROOK
];
317 // Try capturing in every direction
318 for (let step
of steps
) {
319 const sq2
= [m
.end
.x
+ 2 * step
[0], m
.end
.y
+ 2 * step
[1]];
321 V
.OnBoard(sq2
[0], sq2
[1]) &&
322 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
323 this.getColor(sq2
[0], sq2
[1]) == color
326 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
328 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
329 this.getColor(sq1
[0], sq1
[1]) == oppCol
336 p: this.getPiece(sq1
[0], sq1
[1])
347 getSlides([x
, y
], steps
, options
) {
348 options
= options
|| {};
351 outerLoop: for (let step
of steps
) {
355 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
356 if (!options
["doubleStep"] || counter
% 2 == 0)
357 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
358 if (!!options
["oneStep"]) continue outerLoop
;
368 getPotentialRookMoves([x
, y
]) {
370 this.getSlides([x
, y
], V
.steps
[V
.ROOK
], { doubleStep: true })
371 .concat(this.getSlides([x
, y
], V
.steps
[V
.BISHOP
]));
373 const oppCol
= V
.GetOppCol(this.turn
);
375 const delta
= [m
.end
.x
- m
.start
.x
, m
.end
.y
- m
.start
.y
];
377 delta
[0] / Math
.abs(delta
[0]) || 0,
378 delta
[1] / Math
.abs(delta
[1]) || 0
380 if (step
[0] == 0 || step
[1] == 0) {
381 // Rook-like move, candidate for capturing
382 const [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
385 this.board
[i
][j
] != V
.EMPTY
&&
386 this.getColor(i
, j
) == oppCol
391 p: this.getPiece(i
, j
),
401 getPotentialKnightMoves([x
, y
]) {
403 this.getSlides([x
, y
], V
.steps
[V
.ROOK
], { doubleStep: true })
404 .concat(this.getSlides([x
, y
], V
.steps
[V
.BISHOP
]));
405 const oppCol
= V
.GetOppCol(this.turn
);
406 // Look for double-knight moves (could capture):
407 for (let step
of V
.steps
[V
.KNIGHT
]) {
408 const [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
409 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
410 const [ii
, jj
] = [x
+ step
[0], y
+ step
[1]];
411 if (this.board
[ii
][jj
] == V
.EMPTY
|| this.getColor(ii
, jj
) == oppCol
) {
412 let mv
= this.getBasicMove([x
, y
], [i
, j
]);
413 if (this.board
[ii
][jj
] != V
.EMPTY
) {
418 p: this.getPiece(ii
, jj
)
425 // Look for an enemy in every orthogonal direction
426 for (let step
of V
.steps
[V
.ROOK
]) {
427 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
429 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
437 this.getColor(i
, j
) == oppCol
439 const oppPiece
= this.getPiece(i
, j
);
440 // Candidate for capture: can I land after?
441 let [ii
, jj
] = [i
+ step
[0], j
+ step
[1]];
443 while (V
.OnBoard(ii
, jj
) && this.board
[ii
][jj
] == V
.EMPTY
) {
444 if (counter
% 2 == 0) {
445 // Same color: add capture
446 let mv
= this.getBasicMove([x
, y
], [ii
, jj
]);
465 getPotentialBishopMoves([x
, y
]) {
466 let moves
= this.getSlides([x
, y
], V
.steps
[V
.BISHOP
]);
468 const oppCol
= V
.GetOppCol(this.turn
);
470 for (let step
of V
.steps
[V
.ROOK
]) {
471 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
474 this.board
[i
][j
] != V
.EMPTY
&&
475 this.getColor(i
, j
) == oppCol
477 captures
.push([i
, j
]);
480 captures
.forEach(c
=> {
482 start: { x: x
, y: y
},
483 end: { x: c
[0], y: c
[1] },
485 vanish: captures
.map(ct
=> {
490 p: this.getPiece(ct
[0], ct
[1])
498 getPotentialKingMoves([x
, y
]) {
499 let moves
= this.getSlides([x
, y
], V
.steps
[V
.BISHOP
], { oneStep: true });
501 const oppCol
= V
.GetOppCol(this.turn
);
502 for (let step
of V
.steps
[V
.ROOK
]) {
503 const [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
504 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
505 const [ii
, jj
] = [x
+ step
[0], y
+ step
[1]];
506 if (this.board
[ii
][jj
] != V
.EMPTY
&& this.getColor(ii
, jj
) == oppCol
) {
507 let mv
= this.getBasicMove([x
, y
], [i
, j
]);
512 p: this.getPiece(ii
, jj
)
521 getPossibleMovesFrom(sq
) {
522 const L
= this.lastMoveEnd
.length
;
524 !!this.lastMoveEnd
[L
-1] &&
526 sq
[0] != this.lastMoveEnd
[L
-1].x
||
527 sq
[1] != this.lastMoveEnd
[L
-1].y
532 let moves
= this.getPotentialMovesFrom(sq
);
533 const captureMoves
= V
.KeepCaptures(moves
);
534 if (captureMoves
.length
> 0) return captureMoves
;
535 if (this.atLeastOneCapture()) return [];
540 const moves
= this.getAllPotentialMoves();
541 const captures
= V
.KeepCaptures(moves
);
542 if (captures
.length
> 0) return captures
;
552 this.epSquares
.push(this.getEpSquare(move));
553 V
.PlayOnBoard(this.board
, move);
554 if (move.vanish
.length
>= 2) {
555 // Capture: update this.captured
556 for (let i
=1; i
<move.vanish
.length
; i
++)
557 this.captured
[move.vanish
[i
].c
][move.vanish
[i
].p
]++;
559 // Check if the move is the last of the turn
560 if (move.vanish
.length
>= 2 || move.appear
.length
== 0) {
561 const moreCaptures
= (
563 this.getPotentialMovesFrom([move.end
.x
, move.end
.y
])
567 move.last
= !moreCaptures
;
569 else move.last
= true;
571 // No capture, or no more capture available
572 this.turn
= V
.GetOppCol(this.turn
);
574 this.lastMoveEnd
.push(null);
575 move.last
= true; //will be used in undo and computer play
577 else this.lastMoveEnd
.push(move.end
);
581 this.epSquares
.pop();
582 this.lastMoveEnd
.pop();
583 V
.UndoOnBoard(this.board
, move);
584 if (move.vanish
.length
>= 2) {
585 for (let i
=1; i
<move.vanish
.length
; i
++)
586 this.captured
[move.vanish
[i
].c
][move.vanish
[i
].p
]--;
589 this.turn
= V
.GetOppCol(this.turn
);
599 // Count kings: if one is missing, the side lost
600 let kingsCount
= { 'w': 0, 'b': 0 };
601 for (let i
=0; i
<8; i
++) {
602 for (let j
=0; j
<8; j
++) {
603 if (this.board
[i
][j
] != V
.EMPTY
&& this.getPiece(i
, j
) == V
.KING
)
604 kingsCount
[this.getColor(i
, j
)]++;
607 if (kingsCount
['w'] < 2) return "0-1";
608 if (kingsCount
['b'] < 2) return "1-0";
613 let moves
= this.getAllValidMoves();
614 if (moves
.length
== 0) return null;
615 // Just play random moves (for now at least. TODO?)
617 while (moves
.length
> 0) {
618 const mv
= moves
[randInt(moves
.length
)];
622 moves
= V
.KeepCaptures(
623 this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]));
627 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
628 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
632 const initialSquare
= V
.CoordsToSquare(move.start
);
633 const finalSquare
= V
.CoordsToSquare(move.end
);
634 if (move.appear
.length
== 0)
635 // Remover captures 'R'
636 return initialSquare
+ "R";
637 let notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
638 // Add a capture mark (not describing what is captured...):
639 if (move.vanish
.length
>= 2) notation
+= "X";