Commit | Line | Data |
---|---|---|
0c3fe8a6 BA |
1 | import { ChessRules } from "@/base_rules"; |
2 | ||
6808d7a1 BA |
3 | export const VariantRules = class CheckeredRules extends ChessRules { |
4 | static getPpath(b) { | |
5 | return b[0] == "c" ? "Checkered/" + b : b; | |
dac39588 BA |
6 | } |
7 | ||
6808d7a1 | 8 | static board2fen(b) { |
dac39588 | 9 | const checkered_codes = { |
6808d7a1 BA |
10 | p: "s", |
11 | q: "t", | |
12 | r: "u", | |
13 | b: "c", | |
14 | n: "o" | |
dac39588 | 15 | }; |
6808d7a1 | 16 | if (b[0] == "c") return checkered_codes[b[1]]; |
dac39588 BA |
17 | return ChessRules.board2fen(b); |
18 | } | |
19 | ||
6808d7a1 | 20 | static fen2board(f) { |
dac39588 BA |
21 | // Tolerate upper-case versions of checkered pieces (why not?) |
22 | const checkered_pieces = { | |
6808d7a1 BA |
23 | s: "p", |
24 | S: "p", | |
25 | t: "q", | |
26 | T: "q", | |
27 | u: "r", | |
28 | U: "r", | |
29 | c: "b", | |
30 | C: "b", | |
31 | o: "n", | |
32 | O: "n" | |
dac39588 BA |
33 | }; |
34 | if (Object.keys(checkered_pieces).includes(f)) | |
6808d7a1 | 35 | return "c" + checkered_pieces[f]; |
dac39588 BA |
36 | return ChessRules.fen2board(f); |
37 | } | |
38 | ||
6808d7a1 BA |
39 | static get PIECES() { |
40 | return ChessRules.PIECES.concat(["s", "t", "u", "c", "o"]); | |
dac39588 | 41 | } |
7931e479 | 42 | |
6808d7a1 | 43 | setOtherVariables(fen) { |
03cff0f7 BA |
44 | super.setOtherVariables(fen); |
45 | // Local stack of non-capturing checkered moves: | |
46 | this.cmoves = []; | |
47 | const cmove = fen.split(" ")[5]; | |
6808d7a1 BA |
48 | if (cmove == "-") this.cmoves.push(null); |
49 | else { | |
03cff0f7 | 50 | this.cmoves.push({ |
6808d7a1 BA |
51 | start: ChessRules.SquareToCoords(cmove.substr(0, 2)), |
52 | end: ChessRules.SquareToCoords(cmove.substr(2)) | |
03cff0f7 BA |
53 | }); |
54 | } | |
55 | } | |
56 | ||
6808d7a1 BA |
57 | static IsGoodFen(fen) { |
58 | if (!ChessRules.IsGoodFen(fen)) return false; | |
03cff0f7 | 59 | const fenParts = fen.split(" "); |
6808d7a1 | 60 | if (fenParts.length != 6) return false; |
03cff0f7 BA |
61 | if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) |
62 | return false; | |
63 | return true; | |
64 | } | |
65 | ||
6808d7a1 | 66 | static IsGoodFlags(flags) { |
dac39588 BA |
67 | // 4 for castle + 16 for pawns |
68 | return !!flags.match(/^[01]{20,20}$/); | |
69 | } | |
70 | ||
6808d7a1 | 71 | setFlags(fenflags) { |
dac39588 | 72 | super.setFlags(fenflags); //castleFlags |
6808d7a1 BA |
73 | this.pawnFlags = { |
74 | w: [...Array(8).fill(true)], //pawns can move 2 squares? | |
75 | b: [...Array(8).fill(true)] | |
dac39588 | 76 | }; |
6808d7a1 | 77 | if (!fenflags) return; |
dac39588 | 78 | const flags = fenflags.substr(4); //skip first 4 digits, 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 | |
6808d7a1 BA |
94 | getCmove(move) { |
95 | if (move.appear[0].c == "c" && move.vanish.length == 1) | |
96 | return { start: move.start, end: move.end }; | |
03cff0f7 BA |
97 | return null; |
98 | } | |
99 | ||
6808d7a1 BA |
100 | canTake([x1, y1], [x2, y2]) { |
101 | const color1 = this.getColor(x1, y1); | |
102 | const color2 = this.getColor(x2, y2); | |
dac39588 | 103 | // Checkered aren't captured |
6808d7a1 BA |
104 | return ( |
105 | color1 != color2 && | |
106 | color2 != "c" && | |
107 | (color1 != "c" || color2 != this.turn) | |
108 | ); | |
dac39588 BA |
109 | } |
110 | ||
111 | // Post-processing: apply "checkerization" of standard moves | |
6808d7a1 BA |
112 | getPotentialMovesFrom([x, y]) { |
113 | let standardMoves = super.getPotentialMovesFrom([x, y]); | |
dac39588 | 114 | const lastRank = this.turn == "w" ? 0 : 7; |
6808d7a1 | 115 | if (this.getPiece(x, y) == V.KING) return standardMoves; //king has to be treated differently (for castles) |
dac39588 BA |
116 | let moves = []; |
117 | standardMoves.forEach(m => { | |
6808d7a1 BA |
118 | if (m.vanish[0].p == V.PAWN) { |
119 | if ( | |
120 | Math.abs(m.end.x - m.start.x) == 2 && | |
121 | !this.pawnFlags[this.turn][m.start.y] | |
122 | ) | |
dac39588 | 123 | return; //skip forbidden 2-squares jumps |
6808d7a1 BA |
124 | if ( |
125 | this.board[m.end.x][m.end.y] == V.EMPTY && | |
126 | m.vanish.length == 2 && | |
127 | this.getColor(m.start.x, m.start.y) == "c" | |
128 | ) { | |
dac39588 BA |
129 | return; //checkered pawns cannot take en-passant |
130 | } | |
131 | } | |
6808d7a1 BA |
132 | if (m.vanish.length == 1) moves.push(m); |
133 | //no capture | |
134 | else { | |
dac39588 BA |
135 | // A capture occured (m.vanish.length == 2) |
136 | m.appear[0].c = "c"; | |
137 | moves.push(m); | |
6808d7a1 BA |
138 | if ( |
139 | m.appear[0].p != m.vanish[1].p && //avoid promotions (already treated): | |
140 | (m.vanish[0].p != V.PAWN || m.end.x != lastRank) | |
141 | ) { | |
dac39588 BA |
142 | // Add transformation into captured piece |
143 | let m2 = JSON.parse(JSON.stringify(m)); | |
144 | m2.appear[0].p = m.vanish[1].p; | |
145 | moves.push(m2); | |
146 | } | |
147 | } | |
148 | }); | |
149 | return moves; | |
150 | } | |
151 | ||
6808d7a1 BA |
152 | canIplay(side, [x, y]) { |
153 | return side == this.turn && [side, "c"].includes(this.getColor(x, y)); | |
dac39588 BA |
154 | } |
155 | ||
156 | // Does m2 un-do m1 ? (to disallow undoing checkered moves) | |
6808d7a1 BA |
157 | oppositeMoves(m1, m2) { |
158 | return ( | |
159 | !!m1 && | |
160 | m2.appear[0].c == "c" && | |
161 | m2.appear.length == 1 && | |
162 | m2.vanish.length == 1 && | |
163 | m1.start.x == m2.end.x && | |
164 | m1.end.x == m2.start.x && | |
165 | m1.start.y == m2.end.y && | |
166 | m1.end.y == m2.start.y | |
167 | ); | |
dac39588 BA |
168 | } |
169 | ||
6808d7a1 BA |
170 | filterValid(moves) { |
171 | if (moves.length == 0) return []; | |
dac39588 BA |
172 | const color = this.turn; |
173 | return moves.filter(m => { | |
174 | const L = this.cmoves.length; //at least 1: init from FEN | |
6808d7a1 | 175 | if (this.oppositeMoves(this.cmoves[L - 1], m)) return false; |
dac39588 BA |
176 | this.play(m); |
177 | const res = !this.underCheck(color); | |
178 | this.undo(m); | |
179 | return res; | |
180 | }); | |
181 | } | |
182 | ||
6808d7a1 BA |
183 | isAttackedByPawn([x, y], colors) { |
184 | for (let c of colors) { | |
185 | const color = c == "c" ? this.turn : c; | |
186 | let pawnShift = color == "w" ? 1 : -1; | |
187 | if (x + pawnShift >= 0 && x + pawnShift < 8) { | |
188 | for (let i of [-1, 1]) { | |
189 | if ( | |
190 | y + i >= 0 && | |
191 | y + i < 8 && | |
192 | this.getPiece(x + pawnShift, y + i) == V.PAWN && | |
193 | this.getColor(x + pawnShift, y + i) == c | |
194 | ) { | |
dac39588 BA |
195 | return true; |
196 | } | |
197 | } | |
198 | } | |
199 | } | |
200 | return false; | |
201 | } | |
202 | ||
6808d7a1 BA |
203 | underCheck(color) { |
204 | return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]); | |
dac39588 BA |
205 | } |
206 | ||
6808d7a1 | 207 | getCheckSquares(color) { |
dac39588 BA |
208 | // Artifically change turn, for checkered pawns |
209 | this.turn = V.GetOppCol(color); | |
6808d7a1 BA |
210 | const kingAttacked = this.isAttacked(this.kingPos[color], [ |
211 | V.GetOppCol(color), | |
212 | "c" | |
213 | ]); | |
dac39588 BA |
214 | let res = kingAttacked |
215 | ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate! | |
216 | : []; | |
217 | this.turn = color; | |
218 | return res; | |
219 | } | |
220 | ||
6808d7a1 | 221 | updateVariables(move) { |
dac39588 BA |
222 | super.updateVariables(move); |
223 | // Does this move turn off a 2-squares pawn flag? | |
6808d7a1 | 224 | const secondRank = [1, 6]; |
dac39588 | 225 | if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN) |
6808d7a1 | 226 | this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; |
dac39588 BA |
227 | } |
228 | ||
6808d7a1 BA |
229 | getCurrentScore() { |
230 | if (this.atLeastOneMove()) | |
231 | // game not over | |
0c3fe8a6 BA |
232 | return "*"; |
233 | ||
234 | const color = this.turn; | |
dac39588 BA |
235 | // Artifically change turn, for checkered pawns |
236 | this.turn = V.GetOppCol(this.turn); | |
6808d7a1 BA |
237 | const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]) |
238 | ? color == "w" | |
239 | ? "0-1" | |
240 | : "1-0" | |
dac39588 BA |
241 | : "1/2"; |
242 | this.turn = V.GetOppCol(this.turn); | |
243 | return res; | |
244 | } | |
245 | ||
6808d7a1 | 246 | evalPosition() { |
dac39588 BA |
247 | let evaluation = 0; |
248 | //Just count material for now, considering checkered neutral (...) | |
6808d7a1 BA |
249 | for (let i = 0; i < V.size.x; i++) { |
250 | for (let j = 0; j < V.size.y; j++) { | |
251 | if (this.board[i][j] != V.EMPTY) { | |
252 | const sqColor = this.getColor(i, j); | |
253 | const sign = sqColor == "w" ? 1 : sqColor == "b" ? -1 : 0; | |
254 | evaluation += sign * V.VALUES[this.getPiece(i, j)]; | |
dac39588 BA |
255 | } |
256 | } | |
257 | } | |
258 | return evaluation; | |
259 | } | |
260 | ||
6808d7a1 | 261 | static GenRandInitFen() { |
dac39588 BA |
262 | const randFen = ChessRules.GenRandInitFen(); |
263 | // Add 16 pawns flags + empty cmove: | |
264 | return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -"); | |
265 | } | |
1d184b4c | 266 | |
6808d7a1 BA |
267 | static ParseFen(fen) { |
268 | return Object.assign({}, ChessRules.ParseFen(fen), { | |
269 | cmove: fen.split(" ")[5] | |
270 | }); | |
03cff0f7 BA |
271 | } |
272 | ||
6808d7a1 | 273 | getFen() { |
03cff0f7 | 274 | const L = this.cmoves.length; |
6808d7a1 | 275 | const cmoveFen = !this.cmoves[L - 1] |
03cff0f7 | 276 | ? "-" |
6808d7a1 BA |
277 | : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + |
278 | ChessRules.CoordsToSquare(this.cmoves[L - 1].end); | |
03cff0f7 BA |
279 | return super.getFen() + " " + cmoveFen; |
280 | } | |
281 | ||
6808d7a1 | 282 | getFlagsFen() { |
dac39588 BA |
283 | let fen = super.getFlagsFen(); |
284 | // Add pawns flags | |
6808d7a1 BA |
285 | for (let c of ["w", "b"]) { |
286 | for (let i = 0; i < 8; i++) fen += this.pawnFlags[c][i] ? "1" : "0"; | |
dac39588 BA |
287 | } |
288 | return fen; | |
289 | } | |
1d184b4c | 290 | |
03cff0f7 | 291 | // TODO (design): this cmove update here or in (un)updateVariables ? |
6808d7a1 BA |
292 | play(move) { |
293 | this.cmoves.push(this.getCmove(move)); | |
03cff0f7 BA |
294 | super.play(move); |
295 | } | |
296 | ||
6808d7a1 | 297 | undo(move) { |
03cff0f7 BA |
298 | this.cmoves.pop(); |
299 | super.undo(move); | |
300 | } | |
301 | ||
6808d7a1 BA |
302 | getNotation(move) { |
303 | if (move.appear.length == 2) { | |
dac39588 | 304 | // Castle |
6808d7a1 BA |
305 | if (move.end.y < move.start.y) return "0-0-0"; |
306 | return "0-0"; | |
dac39588 BA |
307 | } |
308 | ||
309 | // Translate final square | |
310 | const finalSquare = V.CoordsToSquare(move.end); | |
311 | ||
312 | const piece = this.getPiece(move.start.x, move.start.y); | |
6808d7a1 | 313 | if (piece == V.PAWN) { |
dac39588 BA |
314 | // Pawn move |
315 | let notation = ""; | |
6808d7a1 | 316 | if (move.vanish.length > 1) { |
dac39588 BA |
317 | // Capture |
318 | const startColumn = V.CoordToColumn(move.start.y); | |
6808d7a1 BA |
319 | notation = |
320 | startColumn + | |
321 | "x" + | |
322 | finalSquare + | |
323 | "=" + | |
324 | move.appear[0].p.toUpperCase(); | |
325 | } //no capture | |
326 | else { | |
dac39588 | 327 | notation = finalSquare; |
6808d7a1 BA |
328 | if (move.appear.length > 0 && piece != move.appear[0].p) |
329 | //promotion | |
dac39588 BA |
330 | notation += "=" + move.appear[0].p.toUpperCase(); |
331 | } | |
332 | return notation; | |
333 | } | |
6808d7a1 BA |
334 | // Piece movement |
335 | return ( | |
336 | piece.toUpperCase() + | |
337 | (move.vanish.length > 1 ? "x" : "") + | |
338 | finalSquare + | |
339 | (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "") | |
340 | ); | |
dac39588 | 341 | } |
6808d7a1 | 342 | }; |