Add TODO for pawns promotions in Magnetic + slightly improve rules description
[vchess.git] / public / javascripts / variants / Checkered.js
CommitLineData
1d184b4c
BA
1class CheckeredRules extends ChessRules
2{
3 // Path to pieces
4 static getPpath(b)
5 {
6 return b[0]=='c' ? "Checkered/"+b : b;
7 }
8 static board2fen(b)
9 {
10 const checkered_codes = {
11 'p': 's',
12 'q': 't',
13 'r': 'u',
14 'b': 'c',
15 'n': 'o',
16 };
17 if (b[0]=="c")
18 return checkered_codes[b[1]];
19 return ChessRules.board2fen(b);
20 }
21 static fen2board(f)
22 {
23 const checkered_pieces = {
24 's': 'p',
25 't': 'q',
26 'u': 'r',
27 'c': 'b',
28 'o': 'n',
29 };
30 if (Object.keys(checkered_pieces).includes(f))
31 return 'c'+checkered_pieces[f];
32 return ChessRules.fen2board(f);
33 }
34
1d184b4c
BA
35 static GetFlags(fen)
36 {
37 let flags = [
38 ChessRules.GetFlags(fen), //castle
39 {
40 "w": new Array(8), //pawns can move 2 squares
41 "b": new Array(8)
42 }
43 ];
44 const fenFlags = fen.split(" ")[1].substr(4); //skip first 4 digits, for castle
45 for (let c of ['w','b'])
46 {
47 for (let i=0; i<8; i++)
48 flags[1][c][i] = (fenFlags.charAt((c=='w'?0:8)+i) == '1');
49 }
50 return flags;
51 }
52
46302e64 53 canTake([x1,y1], [x2,y2])
1d184b4c 54 {
46302e64
BA
55 const color1 = this.getColor(x1,y1);
56 const color2 = this.getColor(x2,y2);
1d184b4c
BA
57 // Checkered aren't captured
58 return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
59 }
60
46302e64 61 addCaptures([sx,sy], [ex,ey], moves)
1d184b4c 62 {
46302e64
BA
63 const piece = this.getPiece(sx,sy);
64 if (piece != VariantRules.KING)
1d184b4c 65 {
46302e64
BA
66 moves.push(this.getBasicMove([sx,sy], [ex,ey], {c:'c',p:piece}));
67 const takePiece = this.getPiece(ex,ey);
68 if (takePiece != piece)
69 moves.push(this.getBasicMove([sx,sy], [ex,ey], {c:'c',p:takePiece}));
1d184b4c 70 }
46302e64
BA
71 else
72 moves.push(this.getBasicMove([sx,sy], [ex,ey]));
1d184b4c
BA
73 }
74
75 // Generic method to find possible moves of non-pawn pieces ("sliding or jumping")
46302e64 76 getSlideNJumpMoves([x,y], steps, oneStep)
1d184b4c 77 {
46302e64
BA
78 const color = this.getColor(x,y);
79 let moves = [];
80 const [sizeX,sizeY] = VariantRules.size;
1d184b4c
BA
81 outerLoop:
82 for (var loop=0; loop<steps.length; loop++)
83 {
46302e64
BA
84 let step = steps[loop];
85 let i = x + step[0];
86 let j = y + step[1];
87 while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == VariantRules.EMPTY)
1d184b4c 88 {
46302e64 89 moves.push(this.getBasicMove([x,y], [i,j])); //no capture
1d184b4c
BA
90 if (oneStep !== undefined)
91 continue outerLoop;
92 i += step[0];
93 j += step[1];
94 }
46302e64
BA
95 if (i>=0 && i<8 && j>=0 && j<8 && this.canTake([x,y], [i,j]))
96 this.addCaptures([x,y], [i,j], moves);
1d184b4c
BA
97 }
98 return moves;
99 }
100
101 // What are the pawn moves from square x,y considering color "color" ?
46302e64 102 getPotentialPawnMoves([x,y])
1d184b4c 103 {
46302e64 104 const color = this.getColor(x,y);
1d184b4c
BA
105 var moves = [];
106 var V = VariantRules;
107 let [sizeX,sizeY] = VariantRules.size;
108 const c = (color == 'c' ? this.turn : color);
109 const shift = (c == "w" ? -1 : 1);
110 let startRank = (c == "w" ? sizeY-2 : 1);
111 let lastRank = (c == "w" ? 0 : sizeY-1);
112
113 if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
114 {
115 // Normal moves
116 if (this.board[x+shift][y] == V.EMPTY)
117 {
46302e64 118 moves.push(this.getBasicMove([x,y], [x+shift,y]));
1d184b4c
BA
119 if (x==startRank && this.board[x+2*shift][y] == V.EMPTY && this.flags[1][c][y])
120 {
121 // Two squares jump
46302e64 122 moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
1d184b4c
BA
123 }
124 }
125 // Captures
46302e64
BA
126 if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
127 this.addCaptures([x,y], [x+shift,y-1], moves);
128 if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
129 this.addCaptures([x,y], [x+shift,y+1], moves);
1d184b4c
BA
130 }
131
132 if (x+shift == lastRank)
133 {
134 // Promotion
135 let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
136 promotionPieces.forEach(p => {
137 // Normal move
138 if (this.board[x+shift][y] == V.EMPTY)
46302e64 139 moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
1d184b4c 140 // Captures
46302e64
BA
141 if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
142 moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:'c',p:p}));
143 if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
144 moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:'c',p:p}));
1d184b4c
BA
145 });
146 }
147
148 // En passant
149 const Lep = this.epSquares.length;
150 const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined;
151 if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1)
152 {
153 let epStep = epSquare.y - y;
46302e64 154 var enpassantMove = this.getBasicMove([x,y], [x+shift,y+epStep]);
1d184b4c
BA
155 enpassantMove.vanish.push({
156 x: x,
157 y: y+epStep,
158 p: 'p',
159 c: this.getColor(x,y+epStep)
160 });
161 enpassantMove.appear[0].c = 'c';
162 moves.push(enpassantMove);
163 }
164
165 return moves;
166 }
167
46302e64 168 getCastleMoves([x,y])
1d184b4c 169 {
46302e64 170 const c = this.getColor(x,y);
1d184b4c
BA
171 if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
172 return []; //x isn't first rank, or king has moved (shortcut)
173
174 const V = VariantRules;
175
176 // Castling ?
177 const oppCol = this.getOppCol(c);
178 let moves = [];
179 let i = 0;
180 const finalSquares = [ [2,3], [6,5] ]; //king, then rook
181 castlingCheck:
182 for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
183 {
184 if (!this.flags[0][c][castleSide])
185 continue;
186 // If this code is reached, rooks and king are on initial position
187
188 // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
189 let step = finalSquares[castleSide][0] < y ? -1 : 1;
190 for (i=y; i!=finalSquares[castleSide][0]; i+=step)
191 {
192 if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY &&
193 // NOTE: next check is enough, because of chessboard constraints
194 (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
195 {
196 continue castlingCheck;
197 }
198 }
199
200 // Nothing on the path to the rook?
201 step = castleSide == 0 ? -1 : 1;
202 for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step)
203 {
204 if (this.board[x][i] != V.EMPTY)
205 continue castlingCheck;
206 }
207 const rookPos = this.INIT_COL_ROOK[c][castleSide];
208
209 // Nothing on final squares, except maybe king and castling rook?
210 for (i=0; i<2; i++)
211 {
212 if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
213 this.getPiece(x,finalSquares[castleSide][i]) != V.KING &&
214 finalSquares[castleSide][i] != rookPos)
215 {
216 continue castlingCheck;
217 }
218 }
219
220 // If this code is reached, castle is valid
221 moves.push( new Move({
222 appear: [
223 new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}),
224 new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})],
225 vanish: [
226 new PiPo({x:x,y:y,p:V.KING,c:c}),
227 new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})],
228 end: Math.abs(y - rookPos) <= 2
229 ? {x:x, y:rookPos}
230 : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)}
231 }) );
232 }
233
234 return moves;
235 }
236
46302e64 237 canIplay(side, [x,y])
1d184b4c 238 {
46302e64
BA
239 return ((side=='w' && this.moves.length%2==0) || (side=='b' && this.moves.length%2==1))
240 && [side,'c'].includes(this.getColor(x,y));
1d184b4c
BA
241 }
242
243 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
244 oppositeMoves(m1, m2)
245 {
246 return m1.appear.length == 1 && m2.appear.length == 1
247 && m1.vanish.length == 1 && m2.vanish.length == 1
1d184b4c
BA
248 && m1.start.x == m2.end.x && m1.end.x == m2.start.x
249 && m1.start.y == m2.end.y && m1.end.y == m2.start.y
250 && m1.appear[0].c == m2.vanish[0].c && m1.appear[0].p == m2.vanish[0].p
251 && m1.vanish[0].c == m2.appear[0].c && m1.vanish[0].p == m2.appear[0].p;
252 }
253
254 filterValid(moves)
255 {
256 if (moves.length == 0)
257 return [];
46302e64 258 const color = this.turn;
1d184b4c
BA
259 return moves.filter(m => {
260 const L = this.moves.length;
261 if (L > 0 && this.oppositeMoves(this.moves[L-1], m))
262 return false;
46302e64 263 return !this.underCheck(m);
1d184b4c
BA
264 });
265 }
266
46302e64
BA
267 isAttackedByPawn([x,y], colors)
268 {
269 for (let c of colors)
270 {
271 const color = (c=="c" ? this.turn : c);
272 let pawnShift = (color=="w" ? 1 : -1);
273 if (x+pawnShift>=0 && x+pawnShift<8)
274 {
275 for (let i of [-1,1])
276 {
277 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN
278 && this.getColor(x+pawnShift,y+i)==c)
279 {
280 return true;
281 }
282 }
283 }
284 }
285 return false;
286 }
1d184b4c 287
46302e64 288 underCheck(move)
1d184b4c 289 {
46302e64 290 const color = this.turn;
1d184b4c 291 this.play(move);
46302e64 292 let res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']);
1d184b4c
BA
293 this.undo(move);
294 return res;
295 }
296
46302e64 297 getCheckSquares(move)
bd6ff57c
BA
298 {
299 this.play(move);
46302e64 300 const color = this.turn;
cd4cad04 301 this.moves.push(move); //artifically change turn, for checkered pawns (TODO)
46302e64 302 const kingAttacked = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']);
bd6ff57c 303 let res = kingAttacked
46302e64 304 ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate!
bd6ff57c 305 : [ ];
cd4cad04 306 this.moves.pop();
bd6ff57c
BA
307 this.undo(move);
308 return res;
309 }
310
1d184b4c
BA
311 updateVariables(move)
312 {
313 const piece = this.getPiece(move.start.x,move.start.y);
314 const c = this.getColor(move.start.x,move.start.y);
315
316 if (c != 'c') //checkered not concerned by castle flags
317 {
318 const firstRank = (c == "w" ? 7 : 0);
319 // Update king position + flags
320 if (piece == VariantRules.KING && move.appear.length > 0)
321 {
322 this.kingPos[c][0] = move.appear[0].x;
323 this.kingPos[c][1] = move.appear[0].y;
324 this.flags[0][c] = [false,false];
325 return;
326 }
327 const oppCol = this.getOppCol(c);
328 const oppFirstRank = 7 - firstRank;
329 if (move.start.x == firstRank //our rook moves?
330 && this.INIT_COL_ROOK[c].includes(move.start.y))
331 {
332 const flagIdx = move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1;
333 this.flags[0][c][flagIdx] = false;
334 }
335 else if (move.end.x == oppFirstRank //we took opponent rook?
336 && this.INIT_COL_ROOK[c].includes(move.end.y))
337 {
338 const flagIdx = move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1;
339 this.flags[0][oppCol][flagIdx] = false;
340 }
341 }
342
343 // Does it turn off a 2-squares pawn flag?
344 const secondRank = [1,6];
345 if (secondRank.includes(move.start.x) && move.vanish[0].p == VariantRules.PAWN)
346 this.flags[1][move.start.x==6 ? "w" : "b"][move.start.y] = false;
347 }
348
46302e64 349 checkGameEnd()
1d184b4c 350 {
46302e64 351 const color = this.turn;
1d184b4c
BA
352 if (!this.isAttacked(this.kingPos[color], this.getOppCol(color))
353 && !this.isAttacked(this.kingPos[color], 'c'))
354 {
355 return "1/2";
356 }
357 // OK, checkmate
358 return color == "w" ? "0-1" : "1-0";
359 }
360
361 evalPosition()
362 {
363 const [sizeX,sizeY] = VariantRules.size;
364 let evaluation = 0;
365 //Just count material for now, considering checkered neutral (...)
366 for (let i=0; i<sizeX; i++)
367 {
368 for (let j=0; j<sizeY; j++)
369 {
370 if (this.board[i][j] != VariantRules.EMPTY)
371 {
372 const sqColor = this.getColor(i,j);
373 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
374 evaluation += sign * VariantRules.VALUES[this.getPiece(i,j)];
375 }
376 }
377 }
378 return evaluation;
379 }
380
381 static GenRandInitFen()
382 {
f3802fcd 383 return ChessRules.GenRandInitFen() + "1111111111111111"; //add 16 pawns flags
1d184b4c
BA
384 }
385
386 getFlagsFen()
387 {
388 let fen = "";
389 // Add castling flags
390 for (let c of ['w','b'])
391 {
392 for (let i=0; i<2; i++)
393 fen += this.flags[0][c][i] ? '1' : '0';
394 }
395 // Add pawns flags
396 for (let c of ['w','b'])
397 {
398 for (let i=0; i<8; i++)
399 fen += this.flags[1][c][i] ? '1' : '0';
400 }
401 return fen;
402 }
403
404 getNotation(move)
405 {
406 if (move.appear.length == 2)
407 {
408 // Castle
409 if (move.end.y < move.start.y)
410 return "0-0-0";
411 else
412 return "0-0";
413 }
414
415 // Translate final square
416 let finalSquare =
417 String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x);
418
419 let piece = this.getPiece(move.start.x, move.start.y);
420 if (piece == VariantRules.PAWN)
421 {
422 // Pawn move
423 let notation = "";
424 if (move.vanish.length > 1)
425 {
426 // Capture
427 let startColumn = String.fromCharCode(97 + move.start.y);
428 notation = startColumn + "x" + finalSquare + "=" + move.appear[0].p.toUpperCase();
429 }
430 else //no capture
431 notation = finalSquare;
432 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
433 notation += "=" + move.appear[0].p.toUpperCase();
434 return notation;
435 }
436
437 else
438 {
439 // Piece movement
440 return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
441 + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
442 }
443 }
444}