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