Fix Alice rules (en passant)
[vchess.git] / public / javascripts / variants / Alice.js
1 class AliceRules extends ChessRules
2 {
3 static get ALICE_PIECES()
4 {
5 return {
6 's': 'p',
7 't': 'q',
8 'u': 'r',
9 'c': 'b',
10 'o': 'n',
11 'l': 'k',
12 };
13 }
14 static get ALICE_CODES()
15 {
16 return {
17 'p': 's',
18 'q': 't',
19 'r': 'u',
20 'b': 'c',
21 'n': 'o',
22 'k': 'l',
23 };
24 }
25
26 static getPpath(b)
27 {
28 return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
29 }
30
31 initVariables(fen)
32 {
33 super.initVariables(fen);
34 const fenParts = fen.split(" ");
35 const position = fenParts[0].split("/");
36 if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
37 {
38 // INIT_COL_XXX won't be used, so no need to set them for Alice kings
39 for (let i=0; i<position.length; i++)
40 {
41 let k = 0; //column index on board
42 for (let j=0; j<position[i].length; j++)
43 {
44 switch (position[i].charAt(j))
45 {
46 case 'l':
47 this.kingPos['b'] = [i,k];
48 break;
49 case 'L':
50 this.kingPos['w'] = [i,k];
51 break;
52 default:
53 let num = parseInt(position[i].charAt(j));
54 if (!isNaN(num))
55 k += (num-1);
56 }
57 k++;
58 }
59 }
60 }
61 }
62
63 getBoardOfPiece([x,y])
64 {
65 const V = VariantRules;
66 // Build board where the piece is
67 const mirrorSide = (Object.keys(V.ALICE_CODES).includes(this.getPiece(x,y)) ? 1 : 2);
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 // TODO: move board building one level up (findAllMoves()) to avoid re-building at every piece...
86 // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
87 // --> Should be OK as is.
88 getPotentialMovesFrom([x,y])
89 {
90 let sideBoard = this.getBoardOfPiece([x,y]);
91
92 // Search valid moves on sideBoard
93 let saveBoard = this.board;
94 this.board = sideBoard;
95 let moves = super.getPotentialMovesFrom([x,y]);
96 this.board = saveBoard;
97
98 const pieces = Object.keys(VariantRules.ALICE_CODES);
99 const codes = Object.keys(VariantRules.ALICE_PIECES);
100
101 // Finally filter impossible moves
102 const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
103 return moves.filter(m => {
104 if (m.appear.length == 2) //castle
105 {
106 // If appear[i] not in vanish array, then must be empty square on other board
107 m.appear.forEach(psq => {
108 if (this.board[psq.x][psq.y] != VariantRules.EMPTY &&
109 ![m.vanish[0].y,m.vanish[1].y].includes(psq.y))
110 {
111 return false;
112 }
113 });
114 }
115 else if (this.board[m.end.x][m.end.y] != VariantRules.EMPTY)
116 {
117 // Attempt to capture
118 const piece = this.getPiece(m.end.x,m.end.y);
119 if ((mirrorSide==1 && codes.includes(piece))
120 || (mirrorSide==2 && pieces.includes(piece)))
121 {
122 return false;
123 }
124 }
125 // If the move is computed on board1, m.appear change for Alice pieces.
126 if (mirrorSide==1)
127 {
128 m.appear.forEach(psq => { //forEach: castling taken into account
129 psq.p = VariantRules.ALICE_CODES[psq.p]; //goto board2
130 });
131 }
132 else //move on board2: mark vanishing pieces as Alice
133 {
134 m.vanish.forEach(psq => {
135 psq.p = VariantRules.ALICE_CODES[psq.p];
136 });
137 }
138 // Fix en-passant captures
139 if (m.vanish.length == 2 && this.board[m.end.x][m.end.y] == VariantRules.EMPTY)
140 {
141 m.vanish[1].c = this.getOppCol(this.getColor(x,y));
142 // In the special case of en-passant, if
143 // - board1 takes board2 : vanish[1] --> Alice
144 // - board2 takes board1 : vanish[1] --> normal
145 let van = m.vanish[1];
146 if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
147 van.p = VariantRules.ALICE_CODES[van.p];
148 else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
149 van.p = VariantRules.ALICE_PIECES[van.p];
150 }
151 return true;
152 });
153 }
154
155 underCheck(move)
156 {
157 const color = this.turn;
158 this.play(move);
159 let sideBoard = this.getBoardOfPiece(this.kingPos[color]);
160 let saveBoard = this.board;
161 this.board = sideBoard;
162 let res = this.isAttacked(this.kingPos[color], this.getOppCol(color));
163 this.board = saveBoard;
164 this.undo(move);
165 return res;
166 }
167
168 getCheckSquares(move)
169 {
170 this.play(move);
171 const color = this.turn; //opponent
172 let sideBoard = this.getBoardOfPiece(this.kingPos[color]);
173 let saveBoard = this.board;
174 this.board = sideBoard;
175 let res = this.isAttacked(this.kingPos[color], this.getOppCol(color))
176 ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
177 : [ ];
178 this.board = saveBoard;
179 this.undo(move);
180 return res;
181 }
182
183 updateVariables(move)
184 {
185 super.updateVariables(move); //standard king
186 const piece = this.getPiece(move.start.x,move.start.y);
187 const c = this.getColor(move.start.x,move.start.y);
188 // "l" = Alice king
189 if (piece == "l")
190 {
191 this.kingPos[c][0] = move.appear[0].x;
192 this.kingPos[c][1] = move.appear[0].y;
193 this.castleFlags[c] = [false,false];
194 }
195 }
196
197 unupdateVariables(move)
198 {
199 super.unupdateVariables(move);
200 const c = this.getColor(move.start.x,move.start.y);
201 if (this.getPiece(move.start.x,move.start.y) == "l")
202 this.kingPos[c] = [move.start.x, move.start.y];
203 }
204
205 checkGameEnd()
206 {
207 const color = this.turn;
208 let sideBoard = this.getBoardOfPiece(this.kingPos[color]);
209 let saveBoard = this.board;
210 this.board = sideBoard;
211 let res = "*";
212 if (!this.isAttacked(this.kingPos[color], this.getOppCol(color)))
213 res = "1/2";
214 else
215 res = (color == "w" ? "0-1" : "1-0");
216 this.board = saveBoard;
217 return res;
218 }
219
220 static get VALUES() {
221 return {
222 'p': 1,
223 's': 1,
224 'r': 5,
225 'u': 5,
226 'n': 3,
227 'o': 3,
228 'b': 3,
229 'c': 3,
230 'q': 9,
231 't': 9,
232 'k': 1000,
233 'l': 1000
234 };
235 }
236
237 getNotation(move)
238 {
239 if (move.appear.length == 2 && move.appear[0].p == VariantRules.KING)
240 {
241 if (move.end.y < move.start.y)
242 return "0-0-0";
243 else
244 return "0-0";
245 }
246
247 const finalSquare =
248 String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x);
249 const piece = this.getPiece(move.start.x, move.start.y);
250
251 const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
252 let pawnMark = "";
253 if (["p","s"].includes(piece) && captureMark.length == 1)
254 pawnMark = String.fromCharCode(97 + move.start.y); //start column
255
256 // Piece or pawn movement
257 let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
258 if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
259 {
260 // Promotion
261 notation += "=" + move.appear[0].p.toUpperCase();
262 }
263 return notation;
264 }
265 }