Commit | Line | Data |
---|---|---|
0c3fe8a6 BA |
1 | import { ChessRules } from "@/base_rules"; |
2 | import { ArrayFun } from "@/utils/array"; | |
3 | import { randInt } from "@/utils/alea"; | |
4 | ||
92342261 BA |
5 | // NOTE: initial setup differs from the original; see |
6 | // https://www.chessvariants.com/large.dir/freeling.html | |
32f6285e | 7 | export class GrandRules extends ChessRules { |
7e8a7ea1 | 8 | |
6808d7a1 BA |
9 | static IsGoodFen(fen) { |
10 | if (!ChessRules.IsGoodFen(fen)) return false; | |
dac39588 BA |
11 | const fenParsed = V.ParseFen(fen); |
12 | // 5) Check captures | |
472c0c4f | 13 | if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{12,12}$/)) |
dac39588 BA |
14 | return false; |
15 | return true; | |
16 | } | |
17 | ||
6808d7a1 | 18 | static IsGoodEnpassant(enpassant) { |
472c0c4f | 19 | if (enpassant != "-") return !!enpassant.match(/^([a-j][0-9]{1,2},?)+$/); |
dac39588 BA |
20 | return true; |
21 | } | |
22 | ||
6808d7a1 | 23 | static ParseFen(fen) { |
dac39588 | 24 | const fenParts = fen.split(" "); |
6f2f9437 BA |
25 | return Object.assign( |
26 | ChessRules.ParseFen(fen), | |
27 | { captured: fenParts[5] } | |
28 | ); | |
dac39588 BA |
29 | } |
30 | ||
241bf8f2 BA |
31 | getPpath(b) { |
32 | return ([V.MARSHALL, V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b; | |
33 | } | |
34 | ||
6808d7a1 | 35 | getFen() { |
dac39588 BA |
36 | return super.getFen() + " " + this.getCapturedFen(); |
37 | } | |
38 | ||
f9c36b2d | 39 | getFenForRepeat() { |
90e814b6 | 40 | return super.getFenForRepeat() + "_" + this.getCapturedFen(); |
f9c36b2d BA |
41 | } |
42 | ||
6808d7a1 | 43 | getCapturedFen() { |
6e47d367 | 44 | let counts = [...Array(12).fill(0)]; |
dac39588 | 45 | let i = 0; |
6808d7a1 | 46 | for (let j = 0; j < V.PIECES.length; j++) { |
6e47d367 BA |
47 | if ([V.KING, V.PAWN].includes(V.PIECES[j])) |
48 | // No king captured, and pawns don't promote in pawns | |
dac39588 | 49 | continue; |
6e47d367 BA |
50 | counts[i] = this.captured["w"][V.PIECES[j]]; |
51 | counts[6 + i] = this.captured["b"][V.PIECES[j]]; | |
dac39588 BA |
52 | i++; |
53 | } | |
54 | return counts.join(""); | |
55 | } | |
56 | ||
6808d7a1 | 57 | setOtherVariables(fen) { |
dac39588 | 58 | super.setOtherVariables(fen); |
e50a8025 BA |
59 | const captured = |
60 | V.ParseFen(fen).captured.split("").map(x => parseInt(x, 10)); | |
dac39588 | 61 | // Initialize captured pieces' counts from FEN |
6808d7a1 BA |
62 | this.captured = { |
63 | w: { | |
6e0f2842 BA |
64 | [V.ROOK]: captured[0], |
65 | [V.KNIGHT]: captured[1], | |
66 | [V.BISHOP]: captured[2], | |
67 | [V.QUEEN]: captured[3], | |
68 | [V.MARSHALL]: captured[4], | |
69 | [V.CARDINAL]: captured[5] | |
dac39588 | 70 | }, |
6808d7a1 | 71 | b: { |
6e0f2842 BA |
72 | [V.ROOK]: captured[6], |
73 | [V.KNIGHT]: captured[7], | |
74 | [V.BISHOP]: captured[8], | |
75 | [V.QUEEN]: captured[9], | |
76 | [V.MARSHALL]: captured[10], | |
77 | [V.CARDINAL]: captured[11] | |
dac39588 BA |
78 | } |
79 | }; | |
80 | } | |
81 | ||
6808d7a1 BA |
82 | static get size() { |
83 | return { x: 10, y: 10 }; | |
84 | } | |
dac39588 | 85 | |
a6836242 | 86 | // Rook + knight: |
6808d7a1 BA |
87 | static get MARSHALL() { |
88 | return "m"; | |
a6836242 BA |
89 | } |
90 | ||
91 | // Bishop + knight | |
6808d7a1 BA |
92 | static get CARDINAL() { |
93 | return "c"; | |
a6836242 | 94 | } |
dac39588 | 95 | |
6808d7a1 BA |
96 | static get PIECES() { |
97 | return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]); | |
dac39588 BA |
98 | } |
99 | ||
100 | // There may be 2 enPassant squares (if pawn jump 3 squares) | |
6808d7a1 | 101 | getEnpassantFen() { |
dac39588 | 102 | const L = this.epSquares.length; |
6808d7a1 | 103 | if (!this.epSquares[L - 1]) return "-"; //no en-passant |
dac39588 | 104 | let res = ""; |
6808d7a1 | 105 | this.epSquares[L - 1].forEach(sq => { |
dac39588 BA |
106 | res += V.CoordsToSquare(sq) + ","; |
107 | }); | |
6808d7a1 | 108 | return res.slice(0, -1); //remove last comma |
dac39588 BA |
109 | } |
110 | ||
111 | // En-passant after 2-sq or 3-sq jumps | |
6808d7a1 BA |
112 | getEpSquare(moveOrSquare) { |
113 | if (!moveOrSquare) return undefined; | |
114 | if (typeof moveOrSquare === "string") { | |
dac39588 | 115 | const square = moveOrSquare; |
6808d7a1 | 116 | if (square == "-") return undefined; |
dac39588 BA |
117 | let res = []; |
118 | square.split(",").forEach(sq => { | |
119 | res.push(V.SquareToCoords(sq)); | |
120 | }); | |
121 | return res; | |
122 | } | |
123 | // Argument is a move: | |
124 | const move = moveOrSquare; | |
6808d7a1 BA |
125 | const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x]; |
126 | if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) { | |
127 | const step = (ex - sx) / Math.abs(ex - sx); | |
128 | let res = [ | |
129 | { | |
130 | x: sx + step, | |
131 | y: sy | |
132 | } | |
133 | ]; | |
134 | if (sx + 2 * step != ex) { | |
472c0c4f | 135 | // 3-squares jump |
dac39588 | 136 | res.push({ |
6808d7a1 | 137 | x: sx + 2 * step, |
dac39588 BA |
138 | y: sy |
139 | }); | |
140 | } | |
141 | return res; | |
142 | } | |
143 | return undefined; //default | |
144 | } | |
145 | ||
6808d7a1 BA |
146 | getPotentialMovesFrom([x, y]) { |
147 | switch (this.getPiece(x, y)) { | |
dac39588 | 148 | case V.MARSHALL: |
6808d7a1 | 149 | return this.getPotentialMarshallMoves([x, y]); |
dac39588 | 150 | case V.CARDINAL: |
6808d7a1 | 151 | return this.getPotentialCardinalMoves([x, y]); |
dac39588 | 152 | default: |
6808d7a1 | 153 | return super.getPotentialMovesFrom([x, y]); |
dac39588 BA |
154 | } |
155 | } | |
156 | ||
157 | // Special pawn rules: promotions to captured friendly pieces, | |
158 | // optional on ranks 8-9 and mandatory on rank 10. | |
6808d7a1 | 159 | getPotentialPawnMoves([x, y]) { |
dac39588 BA |
160 | const color = this.turn; |
161 | let moves = []; | |
6808d7a1 BA |
162 | const [sizeX, sizeY] = [V.size.x, V.size.y]; |
163 | const shiftX = color == "w" ? -1 : 1; | |
164 | const startRanks = color == "w" ? [sizeX - 2, sizeX - 3] : [1, 2]; | |
165 | const lastRanks = | |
166 | color == "w" ? [0, 1, 2] : [sizeX - 1, sizeX - 2, sizeX - 3]; | |
167 | const promotionPieces = [ | |
168 | V.ROOK, | |
169 | V.KNIGHT, | |
170 | V.BISHOP, | |
171 | V.QUEEN, | |
172 | V.MARSHALL, | |
173 | V.CARDINAL | |
174 | ]; | |
dac39588 BA |
175 | |
176 | // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank | |
177 | let finalPieces = undefined; | |
6808d7a1 | 178 | if (lastRanks.includes(x + shiftX)) { |
dac39588 | 179 | finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0); |
6808d7a1 BA |
180 | if (x + shiftX != lastRanks[0]) finalPieces.push(V.PAWN); |
181 | } else finalPieces = [V.PAWN]; | |
182 | if (this.board[x + shiftX][y] == V.EMPTY) { | |
dac39588 BA |
183 | // One square forward |
184 | for (let piece of finalPieces) | |
6808d7a1 BA |
185 | moves.push( |
186 | this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) | |
187 | ); | |
188 | if (startRanks.includes(x)) { | |
189 | if (this.board[x + 2 * shiftX][y] == V.EMPTY) { | |
dac39588 | 190 | // Two squares jump |
6808d7a1 BA |
191 | moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); |
192 | if (x == startRanks[0] && this.board[x + 3 * shiftX][y] == V.EMPTY) { | |
dac39588 | 193 | // Three squares jump |
6808d7a1 | 194 | moves.push(this.getBasicMove([x, y], [x + 3 * shiftX, y])); |
dac39588 BA |
195 | } |
196 | } | |
197 | } | |
198 | } | |
199 | // Captures | |
6808d7a1 BA |
200 | for (let shiftY of [-1, 1]) { |
201 | if ( | |
202 | y + shiftY >= 0 && | |
203 | y + shiftY < sizeY && | |
204 | this.board[x + shiftX][y + shiftY] != V.EMPTY && | |
205 | this.canTake([x, y], [x + shiftX, y + shiftY]) | |
206 | ) { | |
207 | for (let piece of finalPieces) { | |
208 | moves.push( | |
209 | this.getBasicMove([x, y], [x + shiftX, y + shiftY], { | |
210 | c: color, | |
211 | p: piece | |
212 | }) | |
213 | ); | |
dac39588 BA |
214 | } |
215 | } | |
216 | } | |
217 | ||
218 | // En passant | |
219 | const Lep = this.epSquares.length; | |
6808d7a1 | 220 | const epSquare = this.epSquares[Lep - 1]; |
472c0c4f | 221 | if (!!epSquare) { |
6808d7a1 | 222 | for (let epsq of epSquare) { |
dac39588 | 223 | // TODO: some redundant checks |
6808d7a1 | 224 | if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) { |
32f6285e | 225 | let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); |
dac39588 BA |
226 | // WARNING: the captured pawn may be diagonally behind us, |
227 | // if it's a 3-squares jump and we take on 1st passing square | |
6808d7a1 | 228 | const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX; |
dac39588 BA |
229 | enpassantMove.vanish.push({ |
230 | x: px, | |
231 | y: epsq.y, | |
6808d7a1 BA |
232 | p: "p", |
233 | c: this.getColor(px, epsq.y) | |
dac39588 BA |
234 | }); |
235 | moves.push(enpassantMove); | |
236 | } | |
237 | } | |
238 | } | |
239 | ||
240 | return moves; | |
241 | } | |
242 | ||
243 | // TODO: different castle? | |
244 | ||
6808d7a1 | 245 | getPotentialMarshallMoves(sq) { |
dac39588 | 246 | return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( |
6808d7a1 BA |
247 | this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") |
248 | ); | |
dac39588 BA |
249 | } |
250 | ||
6808d7a1 | 251 | getPotentialCardinalMoves(sq) { |
dac39588 | 252 | return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( |
6808d7a1 BA |
253 | this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") |
254 | ); | |
dac39588 BA |
255 | } |
256 | ||
68e19a44 | 257 | isAttacked(sq, color) { |
6808d7a1 | 258 | return ( |
68e19a44 BA |
259 | super.isAttacked(sq, color) || |
260 | this.isAttackedByMarshall(sq, color) || | |
261 | this.isAttackedByCardinal(sq, color) | |
6808d7a1 | 262 | ); |
dac39588 BA |
263 | } |
264 | ||
68e19a44 | 265 | isAttackedByMarshall(sq, color) { |
6808d7a1 | 266 | return ( |
68e19a44 | 267 | this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) || |
6808d7a1 BA |
268 | this.isAttackedBySlideNJump( |
269 | sq, | |
68e19a44 | 270 | color, |
6808d7a1 BA |
271 | V.MARSHALL, |
272 | V.steps[V.KNIGHT], | |
273 | "oneStep" | |
274 | ) | |
275 | ); | |
dac39588 BA |
276 | } |
277 | ||
68e19a44 | 278 | isAttackedByCardinal(sq, color) { |
6808d7a1 | 279 | return ( |
68e19a44 | 280 | this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) || |
6808d7a1 BA |
281 | this.isAttackedBySlideNJump( |
282 | sq, | |
68e19a44 | 283 | color, |
6808d7a1 BA |
284 | V.CARDINAL, |
285 | V.steps[V.KNIGHT], | |
286 | "oneStep" | |
287 | ) | |
288 | ); | |
dac39588 BA |
289 | } |
290 | ||
3a2a7b5f BA |
291 | postPlay(move) { |
292 | super.postPlay(move); | |
6e47d367 | 293 | if (move.vanish.length == 2 && move.appear.length == 1) |
dac39588 BA |
294 | // Capture: update this.captured |
295 | this.captured[move.vanish[1].c][move.vanish[1].p]++; | |
dac39588 BA |
296 | } |
297 | ||
3a2a7b5f BA |
298 | postUndo(move) { |
299 | super.postUndo(move); | |
dac39588 BA |
300 | if (move.vanish.length == 2 && move.appear.length == 1) |
301 | this.captured[move.vanish[1].c][move.vanish[1].p]--; | |
dac39588 BA |
302 | } |
303 | ||
6808d7a1 | 304 | static get VALUES() { |
dac39588 | 305 | return Object.assign( |
a97bdbda BA |
306 | { c: 5, m: 7 }, //experimental |
307 | ChessRules.VALUES | |
dac39588 BA |
308 | ); |
309 | } | |
310 | ||
6808d7a1 BA |
311 | static get SEARCH_DEPTH() { |
312 | return 2; | |
313 | } | |
dac39588 | 314 | |
7ba4a5bc | 315 | static GenRandInitFen(randomness) { |
7ba4a5bc | 316 | if (randomness == 0) { |
2c5d7b20 BA |
317 | return ( |
318 | "r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " + | |
319 | // No castling in the official initial setup | |
320 | "w 0 zzzz - 00000000000000" | |
321 | ); | |
7ba4a5bc BA |
322 | } |
323 | ||
6808d7a1 | 324 | let pieces = { w: new Array(10), b: new Array(10) }; |
6f2f9437 | 325 | let flags = ""; |
dac39588 | 326 | // Shuffle pieces on first and last rank |
6808d7a1 | 327 | for (let c of ["w", "b"]) { |
7ba4a5bc BA |
328 | if (c == 'b' && randomness == 1) { |
329 | pieces['b'] = pieces['w']; | |
6f2f9437 | 330 | flags += flags; |
7ba4a5bc BA |
331 | break; |
332 | } | |
333 | ||
dac39588 BA |
334 | let positions = ArrayFun.range(10); |
335 | ||
336 | // Get random squares for bishops | |
337 | let randIndex = 2 * randInt(5); | |
338 | let bishop1Pos = positions[randIndex]; | |
339 | // The second bishop must be on a square of different color | |
340 | let randIndex_tmp = 2 * randInt(5) + 1; | |
341 | let bishop2Pos = positions[randIndex_tmp]; | |
342 | // Remove chosen squares | |
6808d7a1 BA |
343 | positions.splice(Math.max(randIndex, randIndex_tmp), 1); |
344 | positions.splice(Math.min(randIndex, randIndex_tmp), 1); | |
dac39588 BA |
345 | |
346 | // Get random squares for knights | |
347 | randIndex = randInt(8); | |
348 | let knight1Pos = positions[randIndex]; | |
349 | positions.splice(randIndex, 1); | |
350 | randIndex = randInt(7); | |
351 | let knight2Pos = positions[randIndex]; | |
352 | positions.splice(randIndex, 1); | |
353 | ||
354 | // Get random square for queen | |
355 | randIndex = randInt(6); | |
356 | let queenPos = positions[randIndex]; | |
357 | positions.splice(randIndex, 1); | |
358 | ||
359 | // ...random square for marshall | |
360 | randIndex = randInt(5); | |
361 | let marshallPos = positions[randIndex]; | |
362 | positions.splice(randIndex, 1); | |
363 | ||
364 | // ...random square for cardinal | |
365 | randIndex = randInt(4); | |
366 | let cardinalPos = positions[randIndex]; | |
367 | positions.splice(randIndex, 1); | |
368 | ||
2c5d7b20 BA |
369 | // Rooks and king positions are now fixed, |
370 | // because of the ordering rook-king-rook | |
dac39588 BA |
371 | let rook1Pos = positions[0]; |
372 | let kingPos = positions[1]; | |
373 | let rook2Pos = positions[2]; | |
374 | ||
375 | // Finally put the shuffled pieces in the board array | |
6808d7a1 BA |
376 | pieces[c][rook1Pos] = "r"; |
377 | pieces[c][knight1Pos] = "n"; | |
378 | pieces[c][bishop1Pos] = "b"; | |
379 | pieces[c][queenPos] = "q"; | |
380 | pieces[c][marshallPos] = "m"; | |
381 | pieces[c][cardinalPos] = "c"; | |
382 | pieces[c][kingPos] = "k"; | |
383 | pieces[c][bishop2Pos] = "b"; | |
384 | pieces[c][knight2Pos] = "n"; | |
385 | pieces[c][rook2Pos] = "r"; | |
6f2f9437 | 386 | flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos); |
dac39588 | 387 | } |
6808d7a1 BA |
388 | return ( |
389 | pieces["b"].join("") + | |
6f2f9437 | 390 | "/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/" + |
dac39588 | 391 | pieces["w"].join("").toUpperCase() + |
6f2f9437 | 392 | " w 0 " + flags + " - 00000000000000" |
6808d7a1 | 393 | ); |
dac39588 | 394 | } |
7e8a7ea1 | 395 | |
6808d7a1 | 396 | }; |