Bug fixes
[vchess.git] / client / src / variants / Alice.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 // NOTE: alternative implementation, probably cleaner = use only 1 board
5 // TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations)
6 export const VariantRules = class AliceRules extends ChessRules {
7 static get ALICE_PIECES() {
8 return {
9 s: "p",
10 t: "q",
11 u: "r",
12 c: "b",
13 o: "n",
14 l: "k"
15 };
16 }
17 static get ALICE_CODES() {
18 return {
19 p: "s",
20 q: "t",
21 r: "u",
22 b: "c",
23 n: "o",
24 k: "l"
25 };
26 }
27
28 static get PIECES() {
29 return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
30 }
31
32 getPpath(b) {
33 return (Object.keys(V.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
34 }
35
36 setOtherVariables(fen) {
37 super.setOtherVariables(fen);
38 const rows = V.ParseFen(fen).position.split("/");
39 if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0) {
40 // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
41 for (let i = 0; i < rows.length; i++) {
42 let k = 0; //column index on board
43 for (let j = 0; j < rows[i].length; j++) {
44 switch (rows[i].charAt(j)) {
45 case "l":
46 this.kingPos["b"] = [i, k];
47 break;
48 case "L":
49 this.kingPos["w"] = [i, k];
50 break;
51 default: {
52 const num = parseInt(rows[i].charAt(j));
53 if (!isNaN(num)) k += num - 1;
54 }
55 }
56 k++;
57 }
58 }
59 }
60 }
61
62 // Return the (standard) color+piece notation at a square for a board
63 getSquareOccupation(i, j, mirrorSide) {
64 const piece = this.getPiece(i, j);
65 if (mirrorSide == 1 && Object.keys(V.ALICE_CODES).includes(piece))
66 return this.board[i][j];
67 if (mirrorSide == 2 && Object.keys(V.ALICE_PIECES).includes(piece))
68 return this.getColor(i, j) + V.ALICE_PIECES[piece];
69 return "";
70 }
71
72 // Build board of the given (mirror)side
73 getSideBoard(mirrorSide) {
74 // Build corresponding board from complete board
75 let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
76 for (let i = 0; i < V.size.x; i++) {
77 for (let j = 0; j < V.size.y; j++)
78 sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
79 }
80 return sideBoard;
81 }
82
83 // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
84 getPotentialMovesFrom([x, y], sideBoard) {
85 const pieces = Object.keys(V.ALICE_CODES);
86 const codes = Object.keys(V.ALICE_PIECES);
87 const mirrorSide = pieces.includes(this.getPiece(x, y)) ? 1 : 2;
88 if (!sideBoard) sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
89 const color = this.getColor(x, y);
90
91 // Search valid moves on sideBoard
92 const saveBoard = this.board;
93 this.board = sideBoard[mirrorSide - 1];
94 const moves = super.getPotentialMovesFrom([x, y]).filter(m => {
95 // Filter out king moves which result in under-check position on
96 // current board (before mirror traversing)
97 let aprioriValid = true;
98 if (m.appear[0].p == V.KING) {
99 this.play(m);
100 if (this.underCheck(color, sideBoard)) aprioriValid = false;
101 this.undo(m);
102 }
103 return aprioriValid;
104 });
105 this.board = saveBoard;
106
107 // Finally filter impossible moves
108 const res = moves.filter(m => {
109 if (m.appear.length == 2) {
110 //castle
111 // appear[i] must be an empty square on the other board
112 for (let psq of m.appear) {
113 if (this.getSquareOccupation(psq.x, psq.y, 3 - mirrorSide) != V.EMPTY)
114 return false;
115 }
116 } else if (this.board[m.end.x][m.end.y] != V.EMPTY) {
117 // Attempt to capture
118 const piece = this.getPiece(m.end.x, m.end.y);
119 if (
120 (mirrorSide == 1 && codes.includes(piece)) ||
121 (mirrorSide == 2 && pieces.includes(piece))
122 ) {
123 return false;
124 }
125 }
126 // If the move is computed on board1, m.appear change for Alice pieces.
127 if (mirrorSide == 1) {
128 m.appear.forEach(psq => {
129 // forEach: castling taken into account
130 psq.p = V.ALICE_CODES[psq.p]; //goto board2
131 });
132 }
133 else {
134 // Move on board2: mark vanishing pieces as Alice
135 m.vanish.forEach(psq => {
136 psq.p = V.ALICE_CODES[psq.p];
137 });
138 }
139 // Fix en-passant captures
140 if (
141 m.vanish[0].p == V.PAWN &&
142 m.vanish.length == 2 &&
143 this.board[m.end.x][m.end.y] == V.EMPTY
144 ) {
145 m.vanish[1].c = V.GetOppCol(this.getColor(x, y));
146 // In the special case of en-passant, if
147 // - board1 takes board2 : vanish[1] --> Alice
148 // - board2 takes board1 : vanish[1] --> normal
149 let van = m.vanish[1];
150 if (mirrorSide == 1 && codes.includes(this.getPiece(van.x, van.y)))
151 van.p = V.ALICE_CODES[van.p];
152 else if (
153 mirrorSide == 2 &&
154 pieces.includes(this.getPiece(van.x, van.y))
155 )
156 van.p = V.ALICE_PIECES[van.p];
157 }
158 return true;
159 });
160 return res;
161 }
162
163 filterValid(moves, sideBoard) {
164 if (moves.length == 0) return [];
165 if (!sideBoard) sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
166 const color = this.turn;
167 return moves.filter(m => {
168 this.playSide(m, sideBoard); //no need to track flags
169 const res = !this.underCheck(color, sideBoard);
170 this.undoSide(m, sideBoard);
171 return res;
172 });
173 }
174
175 getAllValidMoves() {
176 const color = this.turn;
177 let potentialMoves = [];
178 const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
179 for (var i = 0; i < V.size.x; i++) {
180 for (var j = 0; j < V.size.y; j++) {
181 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
182 Array.prototype.push.apply(
183 potentialMoves,
184 this.getPotentialMovesFrom([i, j], sideBoard)
185 );
186 }
187 }
188 }
189 return this.filterValid(potentialMoves, sideBoard);
190 }
191
192 // Play on sideboards [TODO: only one sideBoard required]
193 playSide(move, sideBoard) {
194 const pieces = Object.keys(V.ALICE_CODES);
195 move.vanish.forEach(psq => {
196 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
197 sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY;
198 });
199 move.appear.forEach(psq => {
200 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
201 const piece = mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p];
202 sideBoard[mirrorSide - 1][psq.x][psq.y] = psq.c + piece;
203 if (piece == V.KING) this.kingPos[psq.c] = [psq.x, psq.y];
204 });
205 }
206
207 // Undo on sideboards
208 undoSide(move, sideBoard) {
209 const pieces = Object.keys(V.ALICE_CODES);
210 move.appear.forEach(psq => {
211 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
212 sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY;
213 });
214 move.vanish.forEach(psq => {
215 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
216 const piece = mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p];
217 sideBoard[mirrorSide - 1][psq.x][psq.y] = psq.c + piece;
218 if (piece == V.KING) this.kingPos[psq.c] = [psq.x, psq.y];
219 });
220 }
221
222 // sideBoard: arg containing both boards (see getAllValidMoves())
223 underCheck(color, sideBoard) {
224 const kp = this.kingPos[color];
225 const mirrorSide = sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2;
226 let saveBoard = this.board;
227 this.board = sideBoard[mirrorSide - 1];
228 let res = this.isAttacked(kp, [V.GetOppCol(color)]);
229 this.board = saveBoard;
230 return res;
231 }
232
233 getCheckSquares(color) {
234 const pieces = Object.keys(V.ALICE_CODES);
235 const kp = this.kingPos[color];
236 const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
237 let sideBoard = this.getSideBoard(mirrorSide);
238 let saveBoard = this.board;
239 this.board = sideBoard;
240 let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
241 ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
242 : [];
243 this.board = saveBoard;
244 return res;
245 }
246
247 updateVariables(move) {
248 super.updateVariables(move); //standard king
249 const piece = move.vanish[0].p;
250 const c = move.vanish[0].c;
251 // "l" = Alice king
252 if (piece == "l") {
253 this.kingPos[c][0] = move.appear[0].x;
254 this.kingPos[c][1] = move.appear[0].y;
255 this.castleFlags[c] = [false, false];
256 }
257 }
258
259 unupdateVariables(move) {
260 super.unupdateVariables(move);
261 const c = move.vanish[0].c;
262 if (move.vanish[0].p == "l") this.kingPos[c] = [move.start.x, move.start.y];
263 }
264
265 getCurrentScore() {
266 if (this.atLeastOneMove())
267 // game not over
268 return "*";
269
270 const pieces = Object.keys(V.ALICE_CODES);
271 const color = this.turn;
272 const kp = this.kingPos[color];
273 const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
274 let sideBoard = this.getSideBoard(mirrorSide);
275 let saveBoard = this.board;
276 this.board = sideBoard;
277 let res = "*";
278 if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
279 res = "1/2";
280 else res = color == "w" ? "0-1" : "1-0";
281 this.board = saveBoard;
282 return res;
283 }
284
285 static get VALUES() {
286 return Object.assign(
287 {
288 s: 1,
289 u: 5,
290 o: 3,
291 c: 3,
292 t: 9,
293 l: 1000
294 },
295 ChessRules.VALUES
296 );
297 }
298
299 static get SEARCH_DEPTH() {
300 return 2;
301 }
302
303 getNotation(move) {
304 if (move.appear.length == 2 && move.appear[0].p == V.KING) {
305 if (move.end.y < move.start.y) return "0-0-0";
306 return "0-0";
307 }
308
309 const finalSquare = V.CoordsToSquare(move.end);
310 const piece = this.getPiece(move.start.x, move.start.y);
311
312 const captureMark = move.vanish.length > move.appear.length ? "x" : "";
313 let pawnMark = "";
314 if (["p", "s"].includes(piece) && captureMark.length == 1)
315 pawnMark = V.CoordToColumn(move.start.y); //start column
316
317 // Piece or pawn movement
318 let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
319 if (["s", "p"].includes(piece) && !["s", "p"].includes(move.appear[0].p)) {
320 // Promotion
321 notation += "=" + move.appear[0].p.toUpperCase();
322 }
323 return notation;
324 }
325 };