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 | ||
1d184b4c BA |
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 | { | |
d3334c3a BA |
279 | return ((color=='w' && this.moves.length%2==0) || color=='c' |
280 | || (color=='b' && this.moves.length%2==1)) | |
1d184b4c BA |
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 | ||
bd6ff57c BA |
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 | ||
1d184b4c BA |
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 | ||
1d184b4c BA |
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 | { | |
f3802fcd | 421 | return ChessRules.GenRandInitFen() + "1111111111111111"; //add 16 pawns flags |
1d184b4c BA |
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 | } |