Debug Knightrelay variant
[vchess.git] / client / src / variants / Allmate.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export const VariantRules = class AllmateRules extends ChessRules {
4 static get HasEnpassant() {
5 return false;
6 }
7
8 canTake(sq1, sq2) {
9 return false; //Captures handled differently
10 }
11
12 getCheckSquares() {
13 // No notion of check
14 return [];
15 }
16
17 static GenRandInitFen() {
18 return ChessRules.GenRandInitFen().replace(/ -$/, "");
19 }
20
21 getPotentialMovesFrom([x, y]) {
22 let moves = super.getPotentialMovesFrom([x, y]);
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 // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
42 outerLoop: for (let i=0; i<V.size.x; i++) {
43 for (let j=0; j<V.size.y; j++) {
44 if (this.getColor(i,j) == oppCol) {
45 let oppMoves = [];
46 switch (this.getPiece(i, j)) {
47 case V.PAWN:
48 oppMoves = this.getPotentialPawnMoves([i, j]);
49 break;
50 case V.ROOK:
51 oppMoves = this.getPotentialRookMoves([i, j]);
52 break;
53 case V.KNIGHT:
54 oppMoves = this.getPotentialKnightMoves([i, j]);
55 break;
56 case V.BISHOP:
57 oppMoves = this.getPotentialBishopMoves([i, j]);
58 break;
59 case V.QUEEN:
60 oppMoves = this.getPotentialQueenMoves([i, j]);
61 break;
62 case V.KING:
63 oppMoves = this.getPotentialKingMoves([i, j]);
64 break;
65 }
66 for (let om of oppMoves) {
67 V.PlayOnBoard(this.board, om);
68 Object.values(attacked).forEach(sq => {
69 const origSq = [sq[0], sq[1]];
70 if (om.start.x == sq[0] && om.start.y == sq[1])
71 // Piece moved:
72 sq = [om.appear[0].x, om.appear[0].y];
73 if (!this.isAttacked(sq, [color]))
74 delete attacked[origSq[0]+"_"+origSq[1]];
75 });
76 V.UndoOnBoard(this.board, om);
77 if (Object.keys(attacked).length == 0)
78 // No need to explore more moves
79 break outerLoop;
80 }
81 }
82 }
83 }
84
85 // 3) Add mate-captures:
86 Object.values(attacked).forEach(sq => {
87 m.vanish.push(new PiPo({
88 x: sq[0],
89 y: sq[1],
90 c: oppCol,
91 p: this.getPiece(sq[0], sq[1])
92 }));
93 });
94
95 this.undo(m);
96 });
97
98 return moves;
99 }
100
101 // No "under check" conditions in castling
102 getCastleMoves([x, y]) {
103 const c = this.getColor(x, y);
104 if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
105 return []; //x isn't first rank, or king has moved (shortcut)
106
107 // Castling ?
108 const oppCol = V.GetOppCol(c);
109 let moves = [];
110 let i = 0;
111 // King, then rook:
112 const finalSquares = [
113 [2, 3],
114 [V.size.y - 2, V.size.y - 3]
115 ];
116 castlingCheck: for (
117 let castleSide = 0;
118 castleSide < 2;
119 castleSide++ //large, then small
120 ) {
121 if (!this.castleFlags[c][castleSide]) continue;
122 // If this code is reached, rooks and king are on initial position
123
124 // Nothing on the path of the king ? (and no checks)
125 const finDist = finalSquares[castleSide][0] - y;
126 let step = finDist / Math.max(1, Math.abs(finDist));
127 for (let i = y; i != finalSquares[castleSide][0]; i += step) {
128 if (
129 this.board[x][i] != V.EMPTY &&
130 // NOTE: next check is enough, because of chessboard constraints
131 (this.getColor(x, i) != c ||
132 ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
133 ) {
134 continue castlingCheck;
135 }
136 }
137
138 // Nothing on the path to the rook?
139 step = castleSide == 0 ? -1 : 1;
140 for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
141 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
142 }
143 const rookPos = this.INIT_COL_ROOK[c][castleSide];
144
145 // Nothing on final squares, except maybe king and castling rook?
146 for (i = 0; i < 2; i++) {
147 if (
148 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
149 this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
150 finalSquares[castleSide][i] != rookPos
151 ) {
152 continue castlingCheck;
153 }
154 }
155
156 // If this code is reached, castle is valid
157 moves.push(
158 new Move({
159 appear: [
160 new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
161 new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
162 ],
163 vanish: [
164 new PiPo({ x: x, y: y, p: V.KING, c: c }),
165 new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
166 ],
167 end:
168 Math.abs(y - rookPos) <= 2
169 ? { x: x, y: rookPos }
170 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
171 })
172 );
173 }
174
175 return moves;
176 }
177
178 // TODO: allow pieces to "commit suicide"? (Currently yes except king)
179 filterValid(moves) {
180 // Remove moves which let the king mate-captured:
181 if (moves.length == 0) return [];
182 const color = this.turn;
183 const oppCol = V.GetOppCol(color);
184 return moves.filter(m => {
185 let res = true;
186 this.play(m);
187 if (this.underCheck(color)) {
188 res = false;
189 const attacked = this.kingPos[color];
190 // Try to find a move to escape check
191 // TODO: very inefficient method.
192 outerLoop: for (let i=0; i<V.size.x; i++) {
193 for (let j=0; j<V.size.y; j++) {
194 if (this.getColor(i,j) == color) {
195 let emoves = [];
196 // Artficial turn change to "play twice":
197 this.turn = color;
198 switch (this.getPiece(i, j)) {
199 case V.PAWN:
200 emoves = this.getPotentialPawnMoves([i, j]);
201 break;
202 case V.ROOK:
203 emoves = this.getPotentialRookMoves([i, j]);
204 break;
205 case V.KNIGHT:
206 emoves = this.getPotentialKnightMoves([i, j]);
207 break;
208 case V.BISHOP:
209 emoves = this.getPotentialBishopMoves([i, j]);
210 break;
211 case V.QUEEN:
212 emoves = this.getPotentialQueenMoves([i, j]);
213 break;
214 case V.KING:
215 emoves = this.getPotentialKingMoves([i, j]);
216 break;
217 }
218 this.turn = oppCol;
219 for (let em of emoves) {
220 V.PlayOnBoard(this.board, em);
221 let sq = attacked;
222 if (em.start.x == attacked[0] && em.start.y == attacked[1])
223 // King moved:
224 sq = [em.appear[0].x, em.appear[0].y];
225 if (!this.isAttacked(sq, [oppCol]))
226 res = true;
227 V.UndoOnBoard(this.board, em);
228 if (res)
229 // No need to explore more moves
230 break outerLoop;
231 }
232 }
233 }
234 }
235 }
236 this.undo(m);
237 return res;
238 });
239 }
240
241 updateVariables(move) {
242 super.updateVariables(move);
243 const color = V.GetOppCol(this.turn);
244 if (move.vanish.length >= 2 && move.appear.length == 1) {
245 move.vanish.forEach(v => {
246 if (v.c == color)
247 return;
248 // Did opponent king disappeared?
249 if (v.p == V.KING)
250 this.kingPos[this.turn] = [-1, -1];
251 // Or maybe a rook?
252 else if (v.p == V.ROOK) {
253 if (v.y < this.INIT_COL_KING[v.c])
254 this.castleFlags[v.c][0] = false;
255 else
256 // v.y > this.INIT_COL_KING[v.c]
257 this.castleFlags[v.c][1] = false;
258 }
259 });
260 }
261 }
262
263 unupdateVariables(move) {
264 super.unupdateVariables(move);
265 const color = this.turn;
266 if (move.vanish.length >= 2 && move.appear.length == 1) {
267 // Did opponent king disappeared?
268 const psq = move.vanish.find(v => v.p == V.KING && v.c != color)
269 if (psq)
270 this.kingPos[psq.c] = [psq.x, psq.y];
271 }
272 }
273
274 getCurrentScore() {
275 const color = this.turn;
276 const kp = this.kingPos[color];
277 if (kp[0] < 0)
278 // King disappeared
279 return color == "w" ? "0-1" : "1-0";
280 if (this.atLeastOneMove())
281 return "*";
282 // Kings still there, no moves:
283 return "1/2";
284 }
285
286 static get SEARCH_DEPTH() {
287 return 2;
288 }
289
290 getNotation(move) {
291 let notation = super.getNotation(move);
292 // Add a capture mark (not describing what is captured...):
293 if (move.vanish.length > 1 && move.appear[0].p != V.KING) {
294 if (notation.match(/^[a-h]/))
295 // Pawn capture: remove "bx" in bxc4 for example
296 notation = notation.substr(2);
297 else
298 notation = notation.replace("x","") + "X";
299 }
300 return notation;
301 }
302 };