First commit
[vchess.git] / public / javascripts / variants / Checkered.js
1 class 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
35 initVariables(fen)
36 {
37 super.initVariables(fen);
38 // Decode last non-capturing checkered move (if any)
39 const cmove = fen.split(" ")[4];
40 if (cmove != "-")
41 {
42 const piece = cmove.charAt(0);
43 const startEnd = cmove.substr(1).split(";");
44 const start = startEnd[0].split(",");
45 const end = startEnd[1].split(",");
46 this.moves.push(new Move({
47 appear: [ new PiPo({c:"c", p:piece, x:end[0], y:end[1]}) ],
48 vanish: [ new PiPo({c:"c", p:piece, x:start[0], y:start[1]}) ]
49 }));
50 }
51 }
52
53 static GetFlags(fen)
54 {
55 let flags = [
56 ChessRules.GetFlags(fen), //castle
57 {
58 "w": new Array(8), //pawns can move 2 squares
59 "b": new Array(8)
60 }
61 ];
62 const fenFlags = fen.split(" ")[1].substr(4); //skip first 4 digits, for castle
63 for (let c of ['w','b'])
64 {
65 for (let i=0; i<8; i++)
66 flags[1][c][i] = (fenFlags.charAt((c=='w'?0:8)+i) == '1');
67 }
68 return flags;
69 }
70
71 // can color1 take color2?
72 canTake(color1, color2)
73 {
74 // Checkered aren't captured
75 return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
76 }
77
78 // Build regular move(s) from its initial and destination squares; tr: transformation
79 getBasicMove(sx, sy, ex, ey, tr)
80 {
81 if (this.board[ex][ey] == VariantRules.EMPTY)
82 {
83 // No capture, standard move construction
84 return [super.getBasicMove(sx,sy,ex,ey,tr)];
85 }
86 let moves = []; //captures: generally 2 choices, unless 'tr' is specified or piece==king
87 const startPiece = this.getPiece(sx,sy);
88 const endPiece = this.getPiece(ex,ey);
89 const startColor = this.getColor(sx,sy);
90 const endColor = this.getColor(ex,ey);
91 for (let piece of !!tr ? [tr] :
92 (startPiece==VariantRules.KING ? VariantRules.KING : _.uniq([startPiece,endPiece])))
93 {
94 var mv = new Move({
95 appear: [
96 new PiPo({
97 x: ex,
98 y: ey,
99 c: startPiece==VariantRules.KING ? startColor : 'c',
100 p: piece
101 })
102 ],
103 vanish: [
104 new PiPo({
105 x: sx,
106 y: sy,
107 c: startColor,
108 p: startPiece
109 }),
110 new PiPo({
111 x: ex,
112 y: ey,
113 c: endColor,
114 p: endPiece
115 })
116 ]
117 });
118 moves.push(mv);
119 }
120 return moves;
121 }
122
123 // Generic method to find possible moves of non-pawn pieces ("sliding or jumping")
124 getSlideNJumpMoves(x, y, color, steps, oneStep)
125 {
126 var moves = [];
127 let [sizeX,sizeY] = VariantRules.size;
128 outerLoop:
129 for (var loop=0; loop<steps.length; loop++)
130 {
131 var step = steps[loop];
132 var i = x + step[0];
133 var j = y + step[1];
134 while (i>=0 && i<sizeX && j>=0 && j<sizeY
135 && this.board[i][j] == VariantRules.EMPTY)
136 {
137 moves.push(this.getBasicMove(x, y, i, j)[0]); //no capture
138 if (oneStep !== undefined)
139 continue outerLoop;
140 i += step[0];
141 j += step[1];
142 }
143 if (i>=0 && i<8 && j>=0 && j<8 && this.canTake(color, this.getColor(i,j)))
144 moves = moves.concat(this.getBasicMove(x, y, i, j));
145 }
146 return moves;
147 }
148
149 // What are the pawn moves from square x,y considering color "color" ?
150 getPotentialPawnMoves(x, y, color)
151 {
152 var moves = [];
153 var V = VariantRules;
154 let [sizeX,sizeY] = VariantRules.size;
155 const c = (color == 'c' ? this.turn : color);
156 const shift = (c == "w" ? -1 : 1);
157 let startRank = (c == "w" ? sizeY-2 : 1);
158 let lastRank = (c == "w" ? 0 : sizeY-1);
159
160 if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
161 {
162 // Normal moves
163 if (this.board[x+shift][y] == V.EMPTY)
164 {
165 moves.push(this.getBasicMove(x, y, x+shift, y)[0]);
166 if (x==startRank && this.board[x+2*shift][y] == V.EMPTY && this.flags[1][c][y])
167 {
168 // Two squares jump
169 moves.push(this.getBasicMove(x, y, x+2*shift, y)[0]);
170 }
171 }
172 // Captures
173 if (y>0 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y-1))
174 && this.board[x+shift][y-1] != V.EMPTY)
175 {
176 moves = moves.concat(this.getBasicMove(x, y, x+shift, y-1));
177 }
178 if (y<sizeY-1 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y+1))
179 && this.board[x+shift][y+1] != V.EMPTY)
180 {
181 moves = moves.concat(this.getBasicMove(x, y, x+shift, y+1));
182 }
183 }
184
185 if (x+shift == lastRank)
186 {
187 // Promotion
188 let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
189 promotionPieces.forEach(p => {
190 // Normal move
191 if (this.board[x+shift][y] == V.EMPTY)
192 moves.push(this.getBasicMove(x, y, x+shift, y, p)[0]);
193 // Captures
194 if (y>0 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y-1))
195 && this.board[x+shift][y-1] != V.EMPTY)
196 {
197 moves = moves.concat(this.getBasicMove(x, y, x+shift, y-1, p));
198 }
199 if (y<sizeY-1 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y+1))
200 && this.board[x+shift][y+1] != V.EMPTY)
201 {
202 moves = moves.concat(this.getBasicMove(x, y, x+shift, y+1, p));
203 }
204 });
205 }
206
207 // En passant
208 const Lep = this.epSquares.length;
209 const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined;
210 if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1)
211 {
212 let epStep = epSquare.y - y;
213 var enpassantMove = this.getBasicMove(x, y, x+shift, y+epStep)[0];
214 enpassantMove.vanish.push({
215 x: x,
216 y: y+epStep,
217 p: 'p',
218 c: this.getColor(x,y+epStep)
219 });
220 enpassantMove.appear[0].c = 'c';
221 moves.push(enpassantMove);
222 }
223
224 return moves;
225 }
226
227 getCastleMoves(x,y,c)
228 {
229 if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
230 return []; //x isn't first rank, or king has moved (shortcut)
231
232 const V = VariantRules;
233
234 // Castling ?
235 const oppCol = this.getOppCol(c);
236 let moves = [];
237 let i = 0;
238 const finalSquares = [ [2,3], [6,5] ]; //king, then rook
239 castlingCheck:
240 for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
241 {
242 if (!this.flags[0][c][castleSide])
243 continue;
244 // If this code is reached, rooks and king are on initial position
245
246 // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
247 let step = finalSquares[castleSide][0] < y ? -1 : 1;
248 for (i=y; i!=finalSquares[castleSide][0]; i+=step)
249 {
250 if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY &&
251 // NOTE: next check is enough, because of chessboard constraints
252 (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
253 {
254 continue castlingCheck;
255 }
256 }
257
258 // Nothing on the path to the rook?
259 step = castleSide == 0 ? -1 : 1;
260 for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step)
261 {
262 if (this.board[x][i] != V.EMPTY)
263 continue castlingCheck;
264 }
265 const rookPos = this.INIT_COL_ROOK[c][castleSide];
266
267 // Nothing on final squares, except maybe king and castling rook?
268 for (i=0; i<2; i++)
269 {
270 if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
271 this.getPiece(x,finalSquares[castleSide][i]) != V.KING &&
272 finalSquares[castleSide][i] != rookPos)
273 {
274 continue castlingCheck;
275 }
276 }
277
278 // If this code is reached, castle is valid
279 moves.push( new Move({
280 appear: [
281 new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}),
282 new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})],
283 vanish: [
284 new PiPo({x:x,y:y,p:V.KING,c:c}),
285 new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})],
286 end: Math.abs(y - rookPos) <= 2
287 ? {x:x, y:rookPos}
288 : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)}
289 }) );
290 }
291
292 return moves;
293 }
294
295 canIplay(color, sq)
296 {
297 return ((color=='w' && this.movesCount%2==0) || color=='c'
298 || (color=='b' && this.movesCount%2==1))
299 && [color,'c'].includes(this.getColor(sq[0], sq[1]));
300 }
301
302 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
303 oppositeMoves(m1, m2)
304 {
305 return m1.appear.length == 1 && m2.appear.length == 1
306 && m1.vanish.length == 1 && m2.vanish.length == 1
307 // && _.isEqual(m1.appear[0], m2.vanish[0]) //fails in HH case
308 // && _.isEqual(m1.vanish[0], m2.appear[0]);
309 && m1.start.x == m2.end.x && m1.end.x == m2.start.x
310 && m1.start.y == m2.end.y && m1.end.y == m2.start.y
311 && m1.appear[0].c == m2.vanish[0].c && m1.appear[0].p == m2.vanish[0].p
312 && m1.vanish[0].c == m2.appear[0].c && m1.vanish[0].p == m2.appear[0].p;
313 }
314
315 filterValid(moves)
316 {
317 if (moves.length == 0)
318 return [];
319 let color = this.getColor( moves[0].start.x, moves[0].start.y );
320 return moves.filter(m => {
321 const L = this.moves.length;
322 if (L > 0 && this.oppositeMoves(this.moves[L-1], m))
323 return false;
324 return !this.underCheck(m, color);
325 });
326 }
327
328 isAttackedByPawn([x,y], c)
329 {
330 const color = (c=="c" ? this.turn : c);
331 let pawnShift = (color=="w" ? 1 : -1);
332 if (x+pawnShift>=0 && x+pawnShift<8)
333 {
334 for (let i of [-1,1])
335 {
336 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN
337 && this.getColor(x+pawnShift,y+i)==c)
338 {
339 return true;
340 }
341 }
342 }
343 return false;
344 }
345
346 underCheck(move, c)
347 {
348 const color = c == 'c' ? this.turn : c;
349 this.play(move);
350 let res = this.isAttacked(this.kingPos[color], this.getOppCol(color))
351 || this.isAttacked(this.kingPos[color], 'c'); //TODO: quite inefficient...
352 this.undo(move);
353 return res;
354 }
355
356 updateVariables(move)
357 {
358 const piece = this.getPiece(move.start.x,move.start.y);
359 const c = this.getColor(move.start.x,move.start.y);
360
361 if (c != 'c') //checkered not concerned by castle flags
362 {
363 const firstRank = (c == "w" ? 7 : 0);
364 // Update king position + flags
365 if (piece == VariantRules.KING && move.appear.length > 0)
366 {
367 this.kingPos[c][0] = move.appear[0].x;
368 this.kingPos[c][1] = move.appear[0].y;
369 this.flags[0][c] = [false,false];
370 return;
371 }
372 const oppCol = this.getOppCol(c);
373 const oppFirstRank = 7 - firstRank;
374 if (move.start.x == firstRank //our rook moves?
375 && this.INIT_COL_ROOK[c].includes(move.start.y))
376 {
377 const flagIdx = move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1;
378 this.flags[0][c][flagIdx] = false;
379 }
380 else if (move.end.x == oppFirstRank //we took opponent rook?
381 && this.INIT_COL_ROOK[c].includes(move.end.y))
382 {
383 const flagIdx = move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1;
384 this.flags[0][oppCol][flagIdx] = false;
385 }
386 }
387
388 // Does it turn off a 2-squares pawn flag?
389 const secondRank = [1,6];
390 if (secondRank.includes(move.start.x) && move.vanish[0].p == VariantRules.PAWN)
391 this.flags[1][move.start.x==6 ? "w" : "b"][move.start.y] = false;
392 }
393
394 play(move, ingame)
395 {
396 super.play(move, ingame);
397 if (!ingame)
398 this.moves.push(move); //needed for turn indication for checkered pieces
399 }
400
401 undo(move)
402 {
403 super.undo(move);
404 this.moves.pop();
405 }
406
407 checkGameEnd(color)
408 {
409 if (!this.isAttacked(this.kingPos[color], this.getOppCol(color))
410 && !this.isAttacked(this.kingPos[color], 'c'))
411 {
412 return "1/2";
413 }
414 // OK, checkmate
415 return color == "w" ? "0-1" : "1-0";
416 }
417
418 evalPosition()
419 {
420 const [sizeX,sizeY] = VariantRules.size;
421 let evaluation = 0;
422 //Just count material for now, considering checkered neutral (...)
423 for (let i=0; i<sizeX; i++)
424 {
425 for (let j=0; j<sizeY; j++)
426 {
427 if (this.board[i][j] != VariantRules.EMPTY)
428 {
429 const sqColor = this.getColor(i,j);
430 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
431 evaluation += sign * VariantRules.VALUES[this.getPiece(i,j)];
432 }
433 }
434 }
435 return evaluation;
436 }
437
438 static GenRandInitFen()
439 {
440 let fen = ChessRules.GenRandInitFen();
441 return fen.replace(/ - 0$/, "1111111111111111 - 0 -");
442 }
443
444 getFen()
445 {
446 let fen = super.getFen() + " ";
447 const L = this.moves.length;
448 if (L > 0 && this.moves[L-1].vanish.length==1 && this.moves[L-1].appear[0].c=="c")
449 {
450 // Ok, un-cancellable checkered move
451 fen += this.moves[L-1].appear[0].p
452 + this.moves[L-1].start.x + "," + this.moves[L-1].start.y + ";"
453 + this.moves[L-1].end.x + "," + this.moves[L-1].end.y;
454 }
455 else fen += "-";
456 return fen;
457 }
458
459 getFlagsFen()
460 {
461 let fen = "";
462 // Add castling flags
463 for (let c of ['w','b'])
464 {
465 for (let i=0; i<2; i++)
466 fen += this.flags[0][c][i] ? '1' : '0';
467 }
468 // Add pawns flags
469 for (let c of ['w','b'])
470 {
471 for (let i=0; i<8; i++)
472 fen += this.flags[1][c][i] ? '1' : '0';
473 }
474 return fen;
475 }
476
477 getNotation(move)
478 {
479 if (move.appear.length == 2)
480 {
481 // Castle
482 if (move.end.y < move.start.y)
483 return "0-0-0";
484 else
485 return "0-0";
486 }
487
488 // Translate final square
489 let finalSquare =
490 String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x);
491
492 let piece = this.getPiece(move.start.x, move.start.y);
493 if (piece == VariantRules.PAWN)
494 {
495 // Pawn move
496 let notation = "";
497 if (move.vanish.length > 1)
498 {
499 // Capture
500 let startColumn = String.fromCharCode(97 + move.start.y);
501 notation = startColumn + "x" + finalSquare + "=" + move.appear[0].p.toUpperCase();
502 }
503 else //no capture
504 notation = finalSquare;
505 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
506 notation += "=" + move.appear[0].p.toUpperCase();
507 return notation;
508 }
509
510 else
511 {
512 // Piece movement
513 return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
514 + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
515 }
516 }
517 }