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