Update TODO, improve style for Crazyhouse variant, fix an introduced bug in example...
[vchess.git] / client / src / variants / Allmate2.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export const VariantRules = class Allmate2Rules extends ChessRules {
4 static get HasEnpassant() {
5 return false;
6 }
7
8 getCheckSquares() {
9 // No notion of check
10 return [];
11 }
12
13 static GenRandInitFen(randomness) {
14 return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
15 }
16
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;
22 });
23
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);
28 moves.forEach(m => {
29 this.play(m);
30
31 // 1) What is attacked?
32 let 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];
37 }
38 }
39
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) {
46 let oppMoves = [];
47 switch (this.getPiece(i, j)) {
48 case V.PAWN:
49 oppMoves = this.getPotentialPawnMoves([i, j]);
50 break;
51 case V.ROOK:
52 oppMoves = this.getPotentialRookMoves([i, j]);
53 break;
54 case V.KNIGHT:
55 oppMoves = this.getPotentialKnightMoves([i, j]);
56 break;
57 case V.BISHOP:
58 oppMoves = this.getPotentialBishopMoves([i, j]);
59 break;
60 case V.QUEEN:
61 oppMoves = this.getPotentialQueenMoves([i, j]);
62 break;
63 case V.KING:
64 oppMoves = this.getPotentialKingMoves([i, j]);
65 break;
66 }
67 for (let om of oppMoves) {
68 if (om.vanish.length == 2 && om.appear.length == 1)
69 // Skip captures: forbidden in this mode
70 continue;
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])
75 // Piece moved:
76 sq = [om.appear[0].x, om.appear[0].y];
77 if (!this.isAttacked(sq, [color]))
78 delete attacked[origSq[0]+"_"+origSq[1]];
79 });
80 V.UndoOnBoard(this.board, om);
81 if (Object.keys(attacked).length == 0)
82 // No need to explore more moves
83 break outerLoop;
84 }
85 }
86 }
87 }
88
89 // 3) Add mate-captures:
90 Object.values(attacked).forEach(sq => {
91 m.vanish.push(new PiPo({
92 x: sq[0],
93 y: sq[1],
94 c: oppCol,
95 p: this.getPiece(sq[0], sq[1])
96 }));
97 });
98
99 this.undo(m);
100 });
101
102 return moves;
103 }
104
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)
110
111 // Castling ?
112 const oppCol = V.GetOppCol(c);
113 let moves = [];
114 let i = 0;
115 // King, then rook:
116 const finalSquares = [
117 [2, 3],
118 [V.size.y - 2, V.size.y - 3]
119 ];
120 castlingCheck: for (
121 let castleSide = 0;
122 castleSide < 2;
123 castleSide++ //large, then small
124 ) {
125 if (!this.castleFlags[c][castleSide]) continue;
126 // If this code is reached, rooks and king are on initial position
127
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) {
132 if (
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)))
137 ) {
138 continue castlingCheck;
139 }
140 }
141
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;
146 }
147 const rookPos = this.INIT_COL_ROOK[c][castleSide];
148
149 // Nothing on final squares, except maybe king and castling rook?
150 for (i = 0; i < 2; i++) {
151 if (
152 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
153 this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
154 finalSquares[castleSide][i] != rookPos
155 ) {
156 continue castlingCheck;
157 }
158 }
159
160 // If this code is reached, castle is valid
161 moves.push(
162 new Move({
163 appear: [
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 })
166 ],
167 vanish: [
168 new PiPo({ x: x, y: y, p: V.KING, c: c }),
169 new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
170 ],
171 end:
172 Math.abs(y - rookPos) <= 2
173 ? { x: x, y: rookPos }
174 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
175 })
176 );
177 }
178
179 return moves;
180 }
181
182 // TODO: allow pieces to "commit suicide"? (Currently yes except king)
183 filterValid(moves) {
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 => {
189 let res = true;
190 this.play(m);
191 if (this.underCheck(color)) {
192 res = false;
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) {
199 let emoves = [];
200 // Artficial turn change to "play twice":
201 this.turn = color;
202 switch (this.getPiece(i, j)) {
203 case V.PAWN:
204 emoves = this.getPotentialPawnMoves([i, j]);
205 break;
206 case V.ROOK:
207 emoves = this.getPotentialRookMoves([i, j]);
208 break;
209 case V.KNIGHT:
210 emoves = this.getPotentialKnightMoves([i, j]);
211 break;
212 case V.BISHOP:
213 emoves = this.getPotentialBishopMoves([i, j]);
214 break;
215 case V.QUEEN:
216 emoves = this.getPotentialQueenMoves([i, j]);
217 break;
218 case V.KING:
219 emoves = this.getPotentialKingMoves([i, j]);
220 break;
221 }
222 this.turn = oppCol;
223 for (let em of emoves) {
224 V.PlayOnBoard(this.board, em);
225 let sq = attacked;
226 if (em.start.x == attacked[0] && em.start.y == attacked[1])
227 // King moved:
228 sq = [em.appear[0].x, em.appear[0].y];
229 if (!this.isAttacked(sq, [oppCol]))
230 res = true;
231 V.UndoOnBoard(this.board, em);
232 if (res)
233 // No need to explore more moves
234 break outerLoop;
235 }
236 }
237 }
238 }
239 }
240 this.undo(m);
241 return res;
242 });
243 }
244
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 => {
250 if (v.c == color)
251 return;
252 // Did opponent king disappeared?
253 if (v.p == V.KING)
254 this.kingPos[this.turn] = [-1, -1];
255 // Or maybe a rook?
256 else if (v.p == V.ROOK) {
257 if (v.y < this.INIT_COL_KING[v.c])
258 this.castleFlags[v.c][0] = false;
259 else
260 // v.y > this.INIT_COL_KING[v.c]
261 this.castleFlags[v.c][1] = false;
262 }
263 });
264 }
265 }
266
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)
273 if (psq)
274 this.kingPos[psq.c] = [psq.x, psq.y];
275 }
276 }
277
278 getCurrentScore() {
279 const color = this.turn;
280 const kp = this.kingPos[color];
281 if (kp[0] < 0)
282 // King disappeared
283 return color == "w" ? "0-1" : "1-0";
284 if (this.atLeastOneMove())
285 return "*";
286 // Kings still there, no moves:
287 return "1/2";
288 }
289
290 static get SEARCH_DEPTH() {
291 return 2;
292 }
293
294 getNotation(move) {
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";
302 }
303 return notation;
304 }
305 };