1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
3 export const VariantRules
= class Allmate2Rules
extends ChessRules
{
4 static get HasEnpassant() {
13 static GenRandInitFen() {
14 return ChessRules
.GenRandInitFen().replace(/ -$/, "");
17 getPotentialMovesFrom([x
, y
]) {
18 let moves
= super.getPotentialMovesFrom([x
, y
]);
19 // Remove standard captures (without removing castling):
20 moves
= moves
.filter(m
=> {
21 return m
.vanish
.length
== 1 || m
.appear
.length
== 2;
24 // Augment moves with "mate-captures":
25 // TODO: this is coded in a highly inefficient way...
26 const color
= this.turn
;
27 const oppCol
= V
.GetOppCol(this.turn
);
31 // 1) What is attacked?
33 for (let i
=0; i
<V
.size
.x
; i
++) {
34 for (let j
=0; j
<V
.size
.y
; j
++) {
35 if (this.getColor(i
,j
) == oppCol
&& this.isAttacked([i
,j
], [color
]))
36 attacked
[i
+"_"+j
] = [i
,j
];
40 // 2) Among attacked pieces, which cannot escape capture?
41 // --> without (normal-)capturing: difference with Allmate variant
42 // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
43 outerLoop: for (let i
=0; i
<V
.size
.x
; i
++) {
44 for (let j
=0; j
<V
.size
.y
; j
++) {
45 if (this.getColor(i
,j
) == oppCol
) {
47 switch (this.getPiece(i
, j
)) {
49 oppMoves
= this.getPotentialPawnMoves([i
, j
]);
52 oppMoves
= this.getPotentialRookMoves([i
, j
]);
55 oppMoves
= this.getPotentialKnightMoves([i
, j
]);
58 oppMoves
= this.getPotentialBishopMoves([i
, j
]);
61 oppMoves
= this.getPotentialQueenMoves([i
, j
]);
64 oppMoves
= this.getPotentialKingMoves([i
, j
]);
67 for (let om
of oppMoves
) {
68 if (om
.vanish
.length
== 2 && om
.appear
.length
== 1)
69 // Skip captures: forbidden in this mode
71 V
.PlayOnBoard(this.board
, om
);
72 Object
.values(attacked
).forEach(sq
=> {
73 const origSq
= [sq
[0], sq
[1]];
74 if (om
.start
.x
== sq
[0] && om
.start
.y
== sq
[1])
76 sq
= [om
.appear
[0].x
, om
.appear
[0].y
];
77 if (!this.isAttacked(sq
, [color
]))
78 delete attacked
[origSq
[0]+"_"+origSq
[1]];
80 V
.UndoOnBoard(this.board
, om
);
81 if (Object
.keys(attacked
).length
== 0)
82 // No need to explore more moves
89 // 3) Add mate-captures:
90 Object
.values(attacked
).forEach(sq
=> {
91 m
.vanish
.push(new PiPo({
95 p: this.getPiece(sq
[0], sq
[1])
105 // No "under check" conditions in castling
106 getCastleMoves([x
, y
]) {
107 const c
= this.getColor(x
, y
);
108 if (x
!= (c
== "w" ? V
.size
.x
- 1 : 0) || y
!= this.INIT_COL_KING
[c
])
109 return []; //x isn't first rank, or king has moved (shortcut)
112 const oppCol
= V
.GetOppCol(c
);
116 const finalSquares
= [
118 [V
.size
.y
- 2, V
.size
.y
- 3]
123 castleSide
++ //large, then small
125 if (!this.castleFlags
[c
][castleSide
]) continue;
126 // If this code is reached, rooks and king are on initial position
128 // Nothing on the path of the king ? (and no checks)
129 const finDist
= finalSquares
[castleSide
][0] - y
;
130 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
131 for (let i
= y
; i
!= finalSquares
[castleSide
][0]; i
+= step
) {
133 this.board
[x
][i
] != V
.EMPTY
&&
134 // NOTE: next check is enough, because of chessboard constraints
135 (this.getColor(x
, i
) != c
||
136 ![V
.KING
, V
.ROOK
].includes(this.getPiece(x
, i
)))
138 continue castlingCheck
;
142 // Nothing on the path to the rook?
143 step
= castleSide
== 0 ? -1 : 1;
144 for (i
= y
+ step
; i
!= this.INIT_COL_ROOK
[c
][castleSide
]; i
+= step
) {
145 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
147 const rookPos
= this.INIT_COL_ROOK
[c
][castleSide
];
149 // Nothing on final squares, except maybe king and castling rook?
150 for (i
= 0; i
< 2; i
++) {
152 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
153 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
&&
154 finalSquares
[castleSide
][i
] != rookPos
156 continue castlingCheck
;
160 // If this code is reached, castle is valid
164 new PiPo({ x: x
, y: finalSquares
[castleSide
][0], p: V
.KING
, c: c
}),
165 new PiPo({ x: x
, y: finalSquares
[castleSide
][1], p: V
.ROOK
, c: c
})
168 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
169 new PiPo({ x: x
, y: rookPos
, p: V
.ROOK
, c: c
})
172 Math
.abs(y
- rookPos
) <= 2
173 ? { x: x
, y: rookPos
}
174 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
182 // TODO: allow pieces to "commit suicide"? (Currently yes except king)
184 // Remove moves which let the king mate-captured:
185 if (moves
.length
== 0) return [];
186 const color
= this.turn
;
187 const oppCol
= V
.GetOppCol(color
);
188 return moves
.filter(m
=> {
191 if (this.underCheck(color
)) {
193 const attacked
= this.kingPos
[color
];
194 // Try to find a move to escape check
195 // TODO: very inefficient method.
196 outerLoop: for (let i
=0; i
<V
.size
.x
; i
++) {
197 for (let j
=0; j
<V
.size
.y
; j
++) {
198 if (this.getColor(i
,j
) == color
) {
200 // Artficial turn change to "play twice":
202 switch (this.getPiece(i
, j
)) {
204 emoves
= this.getPotentialPawnMoves([i
, j
]);
207 emoves
= this.getPotentialRookMoves([i
, j
]);
210 emoves
= this.getPotentialKnightMoves([i
, j
]);
213 emoves
= this.getPotentialBishopMoves([i
, j
]);
216 emoves
= this.getPotentialQueenMoves([i
, j
]);
219 emoves
= this.getPotentialKingMoves([i
, j
]);
223 for (let em
of emoves
) {
224 V
.PlayOnBoard(this.board
, em
);
226 if (em
.start
.x
== attacked
[0] && em
.start
.y
== attacked
[1])
228 sq
= [em
.appear
[0].x
, em
.appear
[0].y
];
229 if (!this.isAttacked(sq
, [oppCol
]))
231 V
.UndoOnBoard(this.board
, em
);
233 // No need to explore more moves
245 updateVariables(move) {
246 super.updateVariables(move);
247 const color
= V
.GetOppCol(this.turn
);
248 if (move.vanish
.length
>= 2 && move.appear
.length
== 1) {
249 move.vanish
.forEach(v
=> {
252 // Did opponent king disappeared?
254 this.kingPos
[this.turn
] = [-1, -1];
256 else if (v
.p
== V
.ROOK
) {
257 if (v
.y
< this.INIT_COL_KING
[v
.c
])
258 this.castleFlags
[v
.c
][0] = false;
260 // v.y > this.INIT_COL_KING[v.c]
261 this.castleFlags
[v
.c
][1] = false;
267 unupdateVariables(move) {
268 super.unupdateVariables(move);
269 const color
= this.turn
;
270 if (move.vanish
.length
>= 2 && move.appear
.length
== 1) {
271 // Did opponent king disappeared?
272 const psq
= move.vanish
.find(v
=> v
.p
== V
.KING
&& v
.c
!= color
)
274 this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
279 const color
= this.turn
;
280 const kp
= this.kingPos
[color
];
283 return color
== "w" ? "0-1" : "1-0";
284 if (this.atLeastOneMove())
286 // Kings still there, no moves:
290 static get SEARCH_DEPTH() {
295 let notation
= super.getNotation(move);
296 // Add a capture mark (not describing what is captured...):
297 if (move.vanish
.length
> 1 && move.appear
.length
== 1) {
298 if (notation
.match(/^[a-h]x/))
299 // Pawn capture: remove initial "b" in bxc4 for example
300 notation
= notation
.substr(1);
301 notation
= notation
.replace("x","") + "X";