Commit | Line | Data |
---|---|---|
68e19a44 | 1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
0c3fe8a6 | 2 | |
af34341d | 3 | export class Checkered2Rules extends ChessRules { |
7e8a7ea1 | 4 | |
6808d7a1 | 5 | static board2fen(b) { |
dac39588 | 6 | const checkered_codes = { |
6808d7a1 BA |
7 | p: "s", |
8 | q: "t", | |
9 | r: "u", | |
10 | b: "c", | |
11 | n: "o" | |
dac39588 | 12 | }; |
6808d7a1 | 13 | if (b[0] == "c") return checkered_codes[b[1]]; |
dac39588 BA |
14 | return ChessRules.board2fen(b); |
15 | } | |
16 | ||
6808d7a1 | 17 | static fen2board(f) { |
dac39588 BA |
18 | // Tolerate upper-case versions of checkered pieces (why not?) |
19 | const checkered_pieces = { | |
6808d7a1 BA |
20 | s: "p", |
21 | S: "p", | |
22 | t: "q", | |
23 | T: "q", | |
24 | u: "r", | |
25 | U: "r", | |
26 | c: "b", | |
27 | C: "b", | |
28 | o: "n", | |
29 | O: "n" | |
dac39588 BA |
30 | }; |
31 | if (Object.keys(checkered_pieces).includes(f)) | |
6808d7a1 | 32 | return "c" + checkered_pieces[f]; |
dac39588 BA |
33 | return ChessRules.fen2board(f); |
34 | } | |
35 | ||
6808d7a1 BA |
36 | static get PIECES() { |
37 | return ChessRules.PIECES.concat(["s", "t", "u", "c", "o"]); | |
dac39588 | 38 | } |
7931e479 | 39 | |
241bf8f2 | 40 | getPpath(b) { |
d1be8046 | 41 | return (b[0] == "c" ? "Checkered/" : "") + b; |
241bf8f2 BA |
42 | } |
43 | ||
6808d7a1 | 44 | setOtherVariables(fen) { |
03cff0f7 BA |
45 | super.setOtherVariables(fen); |
46 | // Local stack of non-capturing checkered moves: | |
47 | this.cmoves = []; | |
3a2a7b5f | 48 | const cmove = V.ParseFen(fen).cmove; |
6808d7a1 BA |
49 | if (cmove == "-") this.cmoves.push(null); |
50 | else { | |
03cff0f7 | 51 | this.cmoves.push({ |
6808d7a1 BA |
52 | start: ChessRules.SquareToCoords(cmove.substr(0, 2)), |
53 | end: ChessRules.SquareToCoords(cmove.substr(2)) | |
03cff0f7 BA |
54 | }); |
55 | } | |
56 | } | |
57 | ||
6808d7a1 BA |
58 | static IsGoodFen(fen) { |
59 | if (!ChessRules.IsGoodFen(fen)) return false; | |
03cff0f7 | 60 | const fenParts = fen.split(" "); |
6808d7a1 | 61 | if (fenParts.length != 6) return false; |
03cff0f7 BA |
62 | if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) |
63 | return false; | |
64 | return true; | |
65 | } | |
66 | ||
6808d7a1 | 67 | static IsGoodFlags(flags) { |
dac39588 | 68 | // 4 for castle + 16 for pawns |
3a2a7b5f | 69 | return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/); |
dac39588 BA |
70 | } |
71 | ||
6808d7a1 | 72 | setFlags(fenflags) { |
dac39588 | 73 | super.setFlags(fenflags); //castleFlags |
6808d7a1 | 74 | this.pawnFlags = { |
305ede7e BA |
75 | w: [...Array(8)], //pawns can move 2 squares? |
76 | b: [...Array(8)] | |
dac39588 | 77 | }; |
3a2a7b5f | 78 | const flags = fenflags.substr(4); //skip first 4 letters, for castle |
6808d7a1 BA |
79 | for (let c of ["w", "b"]) { |
80 | for (let i = 0; i < 8; i++) | |
81 | this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1"; | |
dac39588 BA |
82 | } |
83 | } | |
84 | ||
6808d7a1 | 85 | aggregateFlags() { |
dac39588 BA |
86 | return [this.castleFlags, this.pawnFlags]; |
87 | } | |
88 | ||
6808d7a1 | 89 | disaggregateFlags(flags) { |
dac39588 BA |
90 | this.castleFlags = flags[0]; |
91 | this.pawnFlags = flags[1]; | |
92 | } | |
1d184b4c | 93 | |
e727fe31 | 94 | getEpSquare(moveOrSquare) { |
bbf66837 | 95 | if (typeof moveOrSquare !== "object" || moveOrSquare.appear[0].c != 'c') |
e727fe31 BA |
96 | return super.getEpSquare(moveOrSquare); |
97 | // Checkered move: no en-passant | |
98 | return undefined; | |
99 | } | |
100 | ||
6808d7a1 BA |
101 | getCmove(move) { |
102 | if (move.appear[0].c == "c" && move.vanish.length == 1) | |
103 | return { start: move.start, end: move.end }; | |
03cff0f7 BA |
104 | return null; |
105 | } | |
106 | ||
6808d7a1 BA |
107 | canTake([x1, y1], [x2, y2]) { |
108 | const color1 = this.getColor(x1, y1); | |
109 | const color2 = this.getColor(x2, y2); | |
dac39588 | 110 | // Checkered aren't captured |
6808d7a1 BA |
111 | return ( |
112 | color1 != color2 && | |
113 | color2 != "c" && | |
114 | (color1 != "c" || color2 != this.turn) | |
115 | ); | |
dac39588 BA |
116 | } |
117 | ||
118 | // Post-processing: apply "checkerization" of standard moves | |
6808d7a1 BA |
119 | getPotentialMovesFrom([x, y]) { |
120 | let standardMoves = super.getPotentialMovesFrom([x, y]); | |
dac39588 | 121 | const lastRank = this.turn == "w" ? 0 : 7; |
32f6285e | 122 | // King is treated differently: it never turn checkered |
71ef1664 | 123 | if (this.getPiece(x, y) == V.KING) return standardMoves; |
dac39588 BA |
124 | let moves = []; |
125 | standardMoves.forEach(m => { | |
6808d7a1 BA |
126 | if (m.vanish[0].p == V.PAWN) { |
127 | if ( | |
128 | Math.abs(m.end.x - m.start.x) == 2 && | |
129 | !this.pawnFlags[this.turn][m.start.y] | |
32f6285e | 130 | ) { |
dac39588 | 131 | return; //skip forbidden 2-squares jumps |
32f6285e | 132 | } |
6808d7a1 BA |
133 | if ( |
134 | this.board[m.end.x][m.end.y] == V.EMPTY && | |
135 | m.vanish.length == 2 && | |
136 | this.getColor(m.start.x, m.start.y) == "c" | |
137 | ) { | |
dac39588 BA |
138 | return; //checkered pawns cannot take en-passant |
139 | } | |
140 | } | |
32f6285e BA |
141 | if (m.vanish.length == 1) |
142 | // No capture | |
143 | moves.push(m); | |
6808d7a1 | 144 | else { |
dac39588 BA |
145 | // A capture occured (m.vanish.length == 2) |
146 | m.appear[0].c = "c"; | |
147 | moves.push(m); | |
6808d7a1 | 148 | if ( |
32f6285e BA |
149 | // Avoid promotions (already treated): |
150 | m.appear[0].p != m.vanish[1].p && | |
6808d7a1 BA |
151 | (m.vanish[0].p != V.PAWN || m.end.x != lastRank) |
152 | ) { | |
dac39588 BA |
153 | // Add transformation into captured piece |
154 | let m2 = JSON.parse(JSON.stringify(m)); | |
155 | m2.appear[0].p = m.vanish[1].p; | |
156 | moves.push(m2); | |
157 | } | |
158 | } | |
159 | }); | |
160 | return moves; | |
161 | } | |
162 | ||
e727fe31 | 163 | getPotentialPawnMoves([x, y]) { |
32f6285e BA |
164 | let moves = super.getPotentialPawnMoves([x, y]); |
165 | // Post-process: set right color for checkered moves | |
af34341d | 166 | if (this.getColor(x, y) == 'c') { |
32f6285e BA |
167 | moves.forEach(m => { |
168 | m.appear[0].c = 'c'; //may be done twice if capture | |
169 | m.vanish[0].c = 'c'; | |
e727fe31 | 170 | }); |
af34341d | 171 | } |
68e19a44 BA |
172 | return moves; |
173 | } | |
174 | ||
6808d7a1 BA |
175 | canIplay(side, [x, y]) { |
176 | return side == this.turn && [side, "c"].includes(this.getColor(x, y)); | |
dac39588 BA |
177 | } |
178 | ||
179 | // Does m2 un-do m1 ? (to disallow undoing checkered moves) | |
6808d7a1 BA |
180 | oppositeMoves(m1, m2) { |
181 | return ( | |
61656127 | 182 | !!m1 && |
6808d7a1 BA |
183 | m2.appear[0].c == "c" && |
184 | m2.appear.length == 1 && | |
185 | m2.vanish.length == 1 && | |
186 | m1.start.x == m2.end.x && | |
187 | m1.end.x == m2.start.x && | |
188 | m1.start.y == m2.end.y && | |
189 | m1.end.y == m2.start.y | |
190 | ); | |
dac39588 BA |
191 | } |
192 | ||
6808d7a1 BA |
193 | filterValid(moves) { |
194 | if (moves.length == 0) return []; | |
dac39588 | 195 | const color = this.turn; |
241bf8f2 | 196 | const L = this.cmoves.length; //at least 1: init from FEN |
dac39588 | 197 | return moves.filter(m => { |
6808d7a1 | 198 | if (this.oppositeMoves(this.cmoves[L - 1], m)) return false; |
dac39588 BA |
199 | this.play(m); |
200 | const res = !this.underCheck(color); | |
201 | this.undo(m); | |
202 | return res; | |
203 | }); | |
204 | } | |
205 | ||
d1be8046 BA |
206 | getAllValidMoves() { |
207 | const oppCol = V.GetOppCol(this.turn); | |
208 | let potentialMoves = []; | |
209 | for (let i = 0; i < V.size.x; i++) { | |
210 | for (let j = 0; j < V.size.y; j++) { | |
af34341d | 211 | // NOTE: just testing == color isn't enough because of checkered pieces |
d1be8046 BA |
212 | if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { |
213 | Array.prototype.push.apply( | |
214 | potentialMoves, | |
215 | this.getPotentialMovesFrom([i, j]) | |
216 | ); | |
217 | } | |
218 | } | |
219 | } | |
220 | return this.filterValid(potentialMoves); | |
221 | } | |
222 | ||
223 | atLeastOneMove() { | |
224 | const oppCol = V.GetOppCol(this.turn); | |
225 | for (let i = 0; i < V.size.x; i++) { | |
226 | for (let j = 0; j < V.size.y; j++) { | |
b83a675a | 227 | // NOTE: just testing == color isn't enough because of checkered pieces |
d1be8046 BA |
228 | if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { |
229 | const moves = this.getPotentialMovesFrom([i, j]); | |
230 | if (moves.length > 0) { | |
231 | for (let k = 0; k < moves.length; k++) { | |
232 | if (this.filterValid([moves[k]]).length > 0) return true; | |
233 | } | |
234 | } | |
235 | } | |
236 | } | |
237 | } | |
238 | return false; | |
239 | } | |
240 | ||
68e19a44 BA |
241 | // colors: array, generally 'w' and 'c' or 'b' and 'c' |
242 | isAttacked(sq, colors) { | |
32f6285e | 243 | if (!Array.isArray(colors)) colors = [colors]; |
68e19a44 BA |
244 | return ( |
245 | this.isAttackedByPawn(sq, colors) || | |
246 | this.isAttackedByRook(sq, colors) || | |
247 | this.isAttackedByKnight(sq, colors) || | |
248 | this.isAttackedByBishop(sq, colors) || | |
249 | this.isAttackedByQueen(sq, colors) || | |
250 | this.isAttackedByKing(sq, colors) | |
251 | ); | |
252 | } | |
253 | ||
6808d7a1 BA |
254 | isAttackedByPawn([x, y], colors) { |
255 | for (let c of colors) { | |
68e19a44 | 256 | const color = (c == "c" ? this.turn : c); |
6808d7a1 BA |
257 | let pawnShift = color == "w" ? 1 : -1; |
258 | if (x + pawnShift >= 0 && x + pawnShift < 8) { | |
259 | for (let i of [-1, 1]) { | |
260 | if ( | |
261 | y + i >= 0 && | |
262 | y + i < 8 && | |
263 | this.getPiece(x + pawnShift, y + i) == V.PAWN && | |
264 | this.getColor(x + pawnShift, y + i) == c | |
265 | ) { | |
dac39588 BA |
266 | return true; |
267 | } | |
268 | } | |
269 | } | |
270 | } | |
271 | return false; | |
272 | } | |
273 | ||
68e19a44 BA |
274 | isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { |
275 | for (let step of steps) { | |
276 | let rx = x + step[0], | |
277 | ry = y + step[1]; | |
278 | while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { | |
279 | rx += step[0]; | |
280 | ry += step[1]; | |
281 | } | |
282 | if ( | |
283 | V.OnBoard(rx, ry) && | |
284 | this.getPiece(rx, ry) === piece && | |
285 | colors.includes(this.getColor(rx, ry)) | |
286 | ) { | |
287 | return true; | |
288 | } | |
289 | } | |
290 | return false; | |
291 | } | |
292 | ||
293 | isAttackedByRook(sq, colors) { | |
294 | return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]); | |
295 | } | |
296 | ||
297 | isAttackedByKnight(sq, colors) { | |
298 | return this.isAttackedBySlideNJump( | |
299 | sq, | |
300 | colors, | |
301 | V.KNIGHT, | |
302 | V.steps[V.KNIGHT], | |
303 | "oneStep" | |
304 | ); | |
305 | } | |
306 | ||
307 | isAttackedByBishop(sq, colors) { | |
2c5d7b20 BA |
308 | return this.isAttackedBySlideNJump( |
309 | sq, colors, V.BISHOP, V.steps[V.BISHOP]); | |
68e19a44 BA |
310 | } |
311 | ||
312 | isAttackedByQueen(sq, colors) { | |
313 | return this.isAttackedBySlideNJump( | |
314 | sq, | |
315 | colors, | |
316 | V.QUEEN, | |
317 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]) | |
318 | ); | |
319 | } | |
320 | ||
321 | isAttackedByKing(sq, colors) { | |
322 | return this.isAttackedBySlideNJump( | |
323 | sq, | |
324 | colors, | |
325 | V.KING, | |
326 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), | |
327 | "oneStep" | |
328 | ); | |
329 | } | |
330 | ||
6808d7a1 BA |
331 | underCheck(color) { |
332 | return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]); | |
dac39588 BA |
333 | } |
334 | ||
af34341d BA |
335 | getCheckSquares() { |
336 | const color = this.turn; | |
dac39588 BA |
337 | // Artifically change turn, for checkered pawns |
338 | this.turn = V.GetOppCol(color); | |
af34341d BA |
339 | const kingAttacked = |
340 | this.isAttacked( | |
341 | this.kingPos[color], | |
342 | [this.turn, 'c'] | |
343 | ); | |
dac39588 | 344 | let res = kingAttacked |
2c5d7b20 | 345 | ? [JSON.parse(JSON.stringify(this.kingPos[color]))] |
dac39588 BA |
346 | : []; |
347 | this.turn = color; | |
348 | return res; | |
349 | } | |
350 | ||
3a2a7b5f BA |
351 | postPlay(move) { |
352 | super.postPlay(move); | |
dac39588 | 353 | // Does this move turn off a 2-squares pawn flag? |
5d74fcea BA |
354 | if ( |
355 | [1, 6].includes(move.start.x) && | |
356 | move.vanish[0].p == V.PAWN && | |
357 | Math.abs(move.end.x - move.start.x) == 2 | |
358 | ) { | |
6808d7a1 | 359 | this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; |
5d74fcea | 360 | } |
3a2a7b5f BA |
361 | this.cmoves.push(this.getCmove(move)); |
362 | } | |
363 | ||
364 | postUndo(move) { | |
365 | super.postUndo(move); | |
366 | this.cmoves.pop(); | |
dac39588 BA |
367 | } |
368 | ||
6808d7a1 | 369 | getCurrentScore() { |
bb688df5 | 370 | if (this.atLeastOneMove()) return "*"; |
0c3fe8a6 | 371 | const color = this.turn; |
dac39588 BA |
372 | // Artifically change turn, for checkered pawns |
373 | this.turn = V.GetOppCol(this.turn); | |
6808d7a1 BA |
374 | const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]) |
375 | ? color == "w" | |
376 | ? "0-1" | |
377 | : "1-0" | |
dac39588 BA |
378 | : "1/2"; |
379 | this.turn = V.GetOppCol(this.turn); | |
380 | return res; | |
381 | } | |
382 | ||
6808d7a1 | 383 | evalPosition() { |
dac39588 | 384 | let evaluation = 0; |
d1be8046 | 385 | // Just count material for now, considering checkered neutral (...) |
6808d7a1 BA |
386 | for (let i = 0; i < V.size.x; i++) { |
387 | for (let j = 0; j < V.size.y; j++) { | |
388 | if (this.board[i][j] != V.EMPTY) { | |
389 | const sqColor = this.getColor(i, j); | |
d1be8046 BA |
390 | if (["w","b"].includes(sqColor)) { |
391 | const sign = sqColor == "w" ? 1 : -1; | |
392 | evaluation += sign * V.VALUES[this.getPiece(i, j)]; | |
393 | } | |
dac39588 BA |
394 | } |
395 | } | |
396 | } | |
397 | return evaluation; | |
398 | } | |
399 | ||
7ba4a5bc | 400 | static GenRandInitFen(randomness) { |
3a2a7b5f | 401 | // Add 16 pawns flags + empty cmove: |
7ba4a5bc | 402 | return ChessRules.GenRandInitFen(randomness) |
3a2a7b5f | 403 | .slice(0, -2) + "1111111111111111 - -"; |
dac39588 | 404 | } |
1d184b4c | 405 | |
6808d7a1 | 406 | static ParseFen(fen) { |
6f2f9437 BA |
407 | return Object.assign( |
408 | ChessRules.ParseFen(fen), | |
409 | { cmove: fen.split(" ")[5] } | |
410 | ); | |
03cff0f7 BA |
411 | } |
412 | ||
af34341d | 413 | getCmoveFen() { |
03cff0f7 | 414 | const L = this.cmoves.length; |
af34341d | 415 | return ( |
305ede7e BA |
416 | !this.cmoves[L - 1] |
417 | ? "-" | |
418 | : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + | |
af34341d BA |
419 | ChessRules.CoordsToSquare(this.cmoves[L - 1].end) |
420 | ); | |
421 | } | |
422 | ||
423 | getFen() { | |
424 | return super.getFen() + " " + this.getCmoveFen(); | |
425 | } | |
426 | ||
427 | getFenForRepeat() { | |
428 | return super.getFenForRepeat() + "_" + this.getCmoveFen(); | |
03cff0f7 BA |
429 | } |
430 | ||
6808d7a1 | 431 | getFlagsFen() { |
dac39588 BA |
432 | let fen = super.getFlagsFen(); |
433 | // Add pawns flags | |
305ede7e BA |
434 | for (let c of ["w", "b"]) |
435 | for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0"); | |
dac39588 BA |
436 | return fen; |
437 | } | |
1d184b4c | 438 | |
b83a675a BA |
439 | static get SEARCH_DEPTH() { |
440 | return 2; | |
441 | } | |
442 | ||
6808d7a1 BA |
443 | getNotation(move) { |
444 | if (move.appear.length == 2) { | |
dac39588 | 445 | // Castle |
6808d7a1 BA |
446 | if (move.end.y < move.start.y) return "0-0-0"; |
447 | return "0-0"; | |
dac39588 BA |
448 | } |
449 | ||
dac39588 | 450 | const finalSquare = V.CoordsToSquare(move.end); |
dac39588 | 451 | const piece = this.getPiece(move.start.x, move.start.y); |
57d9b2c4 | 452 | let notation = ""; |
6808d7a1 | 453 | if (piece == V.PAWN) { |
dac39588 | 454 | // Pawn move |
6808d7a1 | 455 | if (move.vanish.length > 1) { |
dac39588 BA |
456 | // Capture |
457 | const startColumn = V.CoordToColumn(move.start.y); | |
57d9b2c4 BA |
458 | notation = startColumn + "x" + finalSquare; |
459 | } else notation = finalSquare; | |
460 | } else { | |
461 | // Piece movement | |
462 | notation = | |
463 | piece.toUpperCase() + | |
464 | (move.vanish.length > 1 ? "x" : "") + | |
465 | finalSquare; | |
dac39588 | 466 | } |
57d9b2c4 BA |
467 | if (move.appear[0].p != move.vanish[0].p) |
468 | notation += "=" + move.appear[0].p.toUpperCase(); | |
469 | return notation; | |
dac39588 | 470 | } |
7e8a7ea1 | 471 | |
6808d7a1 | 472 | }; |