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) | |
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 | } |