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