Commit | Line | Data |
---|---|---|
1d184b4c BA |
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) | |
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 | } |