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