Draft Hiddenqueen, Grasshopper and Knightmate chess (rules unwritten)
[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
1c9f093d 5// TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations)
6808d7a1
BA
6export const VariantRules = class AliceRules extends ChessRules {
7 static get ALICE_PIECES() {
dac39588 8 return {
6808d7a1
BA
9 s: "p",
10 t: "q",
11 u: "r",
12 c: "b",
13 o: "n",
14 l: "k"
dac39588
BA
15 };
16 }
6808d7a1 17 static get ALICE_CODES() {
dac39588 18 return {
6808d7a1
BA
19 p: "s",
20 q: "t",
21 r: "u",
22 b: "c",
23 n: "o",
24 k: "l"
dac39588
BA
25 };
26 }
a3eb4cc5 27
6808d7a1 28 static get PIECES() {
dac39588
BA
29 return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
30 }
7931e479 31
241bf8f2 32 getPpath(b) {
41217d97 33 return (Object.keys(V.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
241bf8f2
BA
34 }
35
6808d7a1 36 setOtherVariables(fen) {
dac39588
BA
37 super.setOtherVariables(fen);
38 const rows = V.ParseFen(fen).position.split("/");
6808d7a1 39 if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0) {
dac39588 40 // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
6808d7a1 41 for (let i = 0; i < rows.length; i++) {
dac39588 42 let k = 0; //column index on board
6808d7a1
BA
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];
dac39588 47 break;
6808d7a1
BA
48 case "L":
49 this.kingPos["w"] = [i, k];
dac39588 50 break;
6808d7a1 51 default: {
dac39588 52 const num = parseInt(rows[i].charAt(j));
6808d7a1
BA
53 if (!isNaN(num)) k += num - 1;
54 }
dac39588
BA
55 }
56 k++;
57 }
58 }
59 }
60 }
0b5fa571 61
dac39588 62 // Return the (standard) color+piece notation at a square for a board
6808d7a1
BA
63 getSquareOccupation(i, j, mirrorSide) {
64 const piece = this.getPiece(i, j);
65 if (mirrorSide == 1 && Object.keys(V.ALICE_CODES).includes(piece))
dac39588 66 return this.board[i][j];
6808d7a1
BA
67 if (mirrorSide == 2 && Object.keys(V.ALICE_PIECES).includes(piece))
68 return this.getColor(i, j) + V.ALICE_PIECES[piece];
dac39588
BA
69 return "";
70 }
364128d9 71
dac39588 72 // Build board of the given (mirror)side
6808d7a1 73 getSideBoard(mirrorSide) {
dac39588
BA
74 // Build corresponding board from complete board
75 let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
6808d7a1
BA
76 for (let i = 0; i < V.size.x; i++) {
77 for (let j = 0; j < V.size.y; j++)
dac39588
BA
78 sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
79 }
80 return sideBoard;
81 }
0cd8f2bd 82
dac39588 83 // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
6808d7a1 84 getPotentialMovesFrom([x, y], sideBoard) {
dac39588
BA
85 const pieces = Object.keys(V.ALICE_CODES);
86 const codes = Object.keys(V.ALICE_PIECES);
6808d7a1
BA
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);
0cd8f2bd 90
dac39588
BA
91 // Search valid moves on sideBoard
92 const saveBoard = this.board;
6808d7a1
BA
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 });
dac39588 105 this.board = saveBoard;
0cd8f2bd 106
dac39588
BA
107 // Finally filter impossible moves
108 const res = moves.filter(m => {
6808d7a1
BA
109 if (m.appear.length == 2) {
110 //castle
dac39588 111 // appear[i] must be an empty square on the other board
6808d7a1
BA
112 for (let psq of m.appear) {
113 if (this.getSquareOccupation(psq.x, psq.y, 3 - mirrorSide) != V.EMPTY)
dac39588
BA
114 return false;
115 }
6808d7a1 116 } else if (this.board[m.end.x][m.end.y] != V.EMPTY) {
dac39588 117 // Attempt to capture
6808d7a1
BA
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 ) {
dac39588
BA
123 return false;
124 }
125 }
126 // If the move is computed on board1, m.appear change for Alice pieces.
6808d7a1
BA
127 if (mirrorSide == 1) {
128 m.appear.forEach(psq => {
129 //forEach: castling taken into account
dac39588
BA
130 psq.p = V.ALICE_CODES[psq.p]; //goto board2
131 });
6808d7a1
BA
132 } //move on board2: mark vanishing pieces as Alice
133 else {
dac39588
BA
134 m.vanish.forEach(psq => {
135 psq.p = V.ALICE_CODES[psq.p];
136 });
137 }
138 // Fix en-passant captures
6808d7a1
BA
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));
dac39588
BA
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];
6808d7a1 149 if (mirrorSide == 1 && codes.includes(this.getPiece(van.x, van.y)))
dac39588 150 van.p = V.ALICE_CODES[van.p];
6808d7a1
BA
151 else if (
152 mirrorSide == 2 &&
153 pieces.includes(this.getPiece(van.x, van.y))
154 )
dac39588
BA
155 van.p = V.ALICE_PIECES[van.p];
156 }
157 return true;
158 });
159 return res;
160 }
b8121223 161
6808d7a1
BA
162 filterValid(moves, sideBoard) {
163 if (moves.length == 0) return [];
164 if (!sideBoard) sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
dac39588
BA
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 }
0cd8f2bd 173
6808d7a1 174 getAllValidMoves() {
dac39588 175 const color = this.turn;
dac39588
BA
176 let potentialMoves = [];
177 const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
6808d7a1
BA
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 );
dac39588
BA
185 }
186 }
187 }
188 return this.filterValid(potentialMoves, sideBoard);
189 }
b8121223 190
dac39588 191 // Play on sideboards [TODO: only one sideBoard required]
6808d7a1 192 playSide(move, sideBoard) {
dac39588
BA
193 const pieces = Object.keys(V.ALICE_CODES);
194 move.vanish.forEach(psq => {
6808d7a1
BA
195 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
196 sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY;
dac39588
BA
197 });
198 move.appear.forEach(psq => {
6808d7a1
BA
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];
dac39588
BA
203 });
204 }
4b353936 205
dac39588 206 // Undo on sideboards
6808d7a1 207 undoSide(move, sideBoard) {
dac39588
BA
208 const pieces = Object.keys(V.ALICE_CODES);
209 move.appear.forEach(psq => {
6808d7a1
BA
210 const mirrorSide = pieces.includes(psq.p) ? 1 : 2;
211 sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY;
dac39588
BA
212 });
213 move.vanish.forEach(psq => {
6808d7a1
BA
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];
dac39588
BA
218 });
219 }
4b353936 220
1c9f093d 221 // sideBoard: arg containing both boards (see getAllValidMoves())
6808d7a1 222 underCheck(color, sideBoard) {
dac39588 223 const kp = this.kingPos[color];
6808d7a1 224 const mirrorSide = sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2;
dac39588 225 let saveBoard = this.board;
6808d7a1 226 this.board = sideBoard[mirrorSide - 1];
dac39588
BA
227 let res = this.isAttacked(kp, [V.GetOppCol(color)]);
228 this.board = saveBoard;
229 return res;
230 }
0cd8f2bd 231
6808d7a1 232 getCheckSquares(color) {
dac39588
BA
233 const pieces = Object.keys(V.ALICE_CODES);
234 const kp = this.kingPos[color];
6808d7a1 235 const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
dac39588
BA
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)])
6808d7a1
BA
240 ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
241 : [];
dac39588
BA
242 this.board = saveBoard;
243 return res;
244 }
270968d6 245
6808d7a1 246 updateVariables(move) {
dac39588
BA
247 super.updateVariables(move); //standard king
248 const piece = move.vanish[0].p;
249 const c = move.vanish[0].c;
250 // "l" = Alice king
6808d7a1 251 if (piece == "l") {
dac39588
BA
252 this.kingPos[c][0] = move.appear[0].x;
253 this.kingPos[c][1] = move.appear[0].y;
6808d7a1 254 this.castleFlags[c] = [false, false];
dac39588
BA
255 }
256 }
0b5fa571 257
6808d7a1 258 unupdateVariables(move) {
dac39588
BA
259 super.unupdateVariables(move);
260 const c = move.vanish[0].c;
6808d7a1 261 if (move.vanish[0].p == "l") this.kingPos[c] = [move.start.x, move.start.y];
dac39588 262 }
0b5fa571 263
6808d7a1
BA
264 getCurrentScore() {
265 if (this.atLeastOneMove())
266 // game not over
0c3fe8a6
BA
267 return "*";
268
269 const pieces = Object.keys(V.ALICE_CODES);
dac39588
BA
270 const color = this.turn;
271 const kp = this.kingPos[color];
6808d7a1 272 const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
dac39588
BA
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";
6808d7a1 279 else res = color == "w" ? "0-1" : "1-0";
dac39588
BA
280 this.board = saveBoard;
281 return res;
282 }
9de73b71 283
6808d7a1 284 static get VALUES() {
a97bdbda
BA
285 return Object.assign(
286 {
287 s: 1,
288 u: 5,
289 o: 3,
290 c: 3,
291 t: 9,
292 l: 1000
293 },
294 ChessRules.VALUES
295 );
dac39588 296 }
0f51ef98 297
6808d7a1
BA
298 getNotation(move) {
299 if (move.appear.length == 2 && move.appear[0].p == V.KING) {
300 if (move.end.y < move.start.y) return "0-0-0";
301 return "0-0";
dac39588 302 }
0f51ef98 303
dac39588
BA
304 const finalSquare = V.CoordsToSquare(move.end);
305 const piece = this.getPiece(move.start.x, move.start.y);
0f51ef98 306
6808d7a1 307 const captureMark = move.vanish.length > move.appear.length ? "x" : "";
dac39588 308 let pawnMark = "";
6808d7a1 309 if (["p", "s"].includes(piece) && captureMark.length == 1)
dac39588 310 pawnMark = V.CoordToColumn(move.start.y); //start column
0f51ef98 311
dac39588
BA
312 // Piece or pawn movement
313 let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
6808d7a1 314 if (["s", "p"].includes(piece) && !["s", "p"].includes(move.appear[0].p)) {
dac39588
BA
315 // Promotion
316 notation += "=" + move.appear[0].p.toUpperCase();
317 }
318 return notation;
319 }
6808d7a1 320};