a92338f78dabf0def35087edf180a709002816bc
[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 } //move on board2: mark vanishing pieces as Alice
133 else {
134 m.vanish.forEach(psq => {
135 psq.p = V.ALICE_CODES[psq.p];
136 });
137 }
138 // Fix en-passant captures
139 if (
140 m.vanish[0].p == V.PAWN &&
141 m.vanish.length == 2 &&
142 this.board[m.end.x][m.end.y] == V.EMPTY
143 ) {
144 m.vanish[1].c = V.GetOppCol(this.getColor(x, y));
145 // In the special case of en-passant, if
146 // - board1 takes board2 : vanish[1] --> Alice
147 // - board2 takes board1 : vanish[1] --> normal
148 let van = m.vanish[1];
149 if (mirrorSide == 1 && codes.includes(this.getPiece(van.x, van.y)))
150 van.p = V.ALICE_CODES[van.p];
151 else if (
152 mirrorSide == 2 &&
153 pieces.includes(this.getPiece(van.x, van.y))
154 )
155 van.p = V.ALICE_PIECES[van.p];
156 }
157 return true;
158 });
159 return res;
160 }
161
162 filterValid(moves, sideBoard) {
163 if (moves.length == 0) return [];
164 if (!sideBoard) sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
165 const color = this.turn;
166 return moves.filter(m => {
167 this.playSide(m, sideBoard); //no need to track flags
168 const res = !this.underCheck(color, sideBoard);
169 this.undoSide(m, sideBoard);
170 return res;
171 });
172 }
173
174 getAllValidMoves() {
175 const color = this.turn;
176 let potentialMoves = [];
177 const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
178 for (var i = 0; i < V.size.x; i++) {
179 for (var j = 0; j < V.size.y; j++) {
180 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
181 Array.prototype.push.apply(
182 potentialMoves,
183 this.getPotentialMovesFrom([i, j], sideBoard)
184 );
185 }
186 }
187 }
188 return this.filterValid(potentialMoves, sideBoard);
189 }
190
191 // Play on sideboards [TODO: only one sideBoard required]
192 playSide(move, sideBoard) {
193 const pieces = Object.keys(V.ALICE_CODES);
194 move.vanish.forEach(psq => {
195 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
196 sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY;
197 });
198 move.appear.forEach(psq => {
199 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
200 const piece = mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p];
201 sideBoard[mirrorSide - 1][psq.x][psq.y] = psq.c + piece;
202 if (piece == V.KING) this.kingPos[psq.c] = [psq.x, psq.y];
203 });
204 }
205
206 // Undo on sideboards
207 undoSide(move, sideBoard) {
208 const pieces = Object.keys(V.ALICE_CODES);
209 move.appear.forEach(psq => {
210 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
211 sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY;
212 });
213 move.vanish.forEach(psq => {
214 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
215 const piece = mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p];
216 sideBoard[mirrorSide - 1][psq.x][psq.y] = psq.c + piece;
217 if (piece == V.KING) this.kingPos[psq.c] = [psq.x, psq.y];
218 });
219 }
220
221 // sideBoard: arg containing both boards (see getAllValidMoves())
222 underCheck(color, sideBoard) {
223 const kp = this.kingPos[color];
224 const mirrorSide = sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2;
225 let saveBoard = this.board;
226 this.board = sideBoard[mirrorSide - 1];
227 let res = this.isAttacked(kp, [V.GetOppCol(color)]);
228 this.board = saveBoard;
229 return res;
230 }
231
232 getCheckSquares(color) {
233 const pieces = Object.keys(V.ALICE_CODES);
234 const kp = this.kingPos[color];
235 const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
236 let sideBoard = this.getSideBoard(mirrorSide);
237 let saveBoard = this.board;
238 this.board = sideBoard;
239 let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
240 ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
241 : [];
242 this.board = saveBoard;
243 return res;
244 }
245
246 updateVariables(move) {
247 super.updateVariables(move); //standard king
248 const piece = move.vanish[0].p;
249 const c = move.vanish[0].c;
250 // "l" = Alice king
251 if (piece == "l") {
252 this.kingPos[c][0] = move.appear[0].x;
253 this.kingPos[c][1] = move.appear[0].y;
254 this.castleFlags[c] = [false, false];
255 }
256 }
257
258 unupdateVariables(move) {
259 super.unupdateVariables(move);
260 const c = move.vanish[0].c;
261 if (move.vanish[0].p == "l") this.kingPos[c] = [move.start.x, move.start.y];
262 }
263
264 getCurrentScore() {
265 if (this.atLeastOneMove())
266 // game not over
267 return "*";
268
269 const pieces = Object.keys(V.ALICE_CODES);
270 const color = this.turn;
271 const kp = this.kingPos[color];
272 const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
273 let sideBoard = this.getSideBoard(mirrorSide);
274 let saveBoard = this.board;
275 this.board = sideBoard;
276 let res = "*";
277 if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
278 res = "1/2";
279 else res = color == "w" ? "0-1" : "1-0";
280 this.board = saveBoard;
281 return res;
282 }
283
284 static get VALUES() {
285 return Object.assign(ChessRules.VALUES, {
286 s: 1,
287 u: 5,
288 o: 3,
289 c: 3,
290 t: 9,
291 l: 1000
292 });
293 }
294
295 getNotation(move) {
296 if (move.appear.length == 2 && move.appear[0].p == V.KING) {
297 if (move.end.y < move.start.y) return "0-0-0";
298 return "0-0";
299 }
300
301 const finalSquare = V.CoordsToSquare(move.end);
302 const piece = this.getPiece(move.start.x, move.start.y);
303
304 const captureMark = move.vanish.length > move.appear.length ? "x" : "";
305 let pawnMark = "";
306 if (["p", "s"].includes(piece) && captureMark.length == 1)
307 pawnMark = V.CoordToColumn(move.start.y); //start column
308
309 // Piece or pawn movement
310 let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
311 if (["s", "p"].includes(piece) && !["s", "p"].includes(move.appear[0].p)) {
312 // Promotion
313 notation += "=" + move.appear[0].p.toUpperCase();
314 }
315 return notation;
316 }
317 };