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