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