Commit | Line | Data |
---|---|---|
0c3fe8a6 BA |
1 | import { ChessRules } from "@/base_rules"; |
2 | import { ArrayFun } from "@/utils/array"; | |
3 | import { randInt } from "@/utils/alea"; | |
4 | ||
32f6285e | 5 | export class GrandRules extends ChessRules { |
7e8a7ea1 | 6 | |
0fb43db7 BA |
7 | static get HasCastle() { |
8 | return false; | |
9 | } | |
10 | ||
6808d7a1 BA |
11 | static IsGoodFen(fen) { |
12 | if (!ChessRules.IsGoodFen(fen)) return false; | |
dac39588 BA |
13 | const fenParsed = V.ParseFen(fen); |
14 | // 5) Check captures | |
472c0c4f | 15 | if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{12,12}$/)) |
dac39588 BA |
16 | return false; |
17 | return true; | |
18 | } | |
19 | ||
6808d7a1 | 20 | static IsGoodEnpassant(enpassant) { |
472c0c4f | 21 | if (enpassant != "-") return !!enpassant.match(/^([a-j][0-9]{1,2},?)+$/); |
dac39588 BA |
22 | return true; |
23 | } | |
24 | ||
6808d7a1 | 25 | static ParseFen(fen) { |
dac39588 | 26 | const fenParts = fen.split(" "); |
6f2f9437 BA |
27 | return Object.assign( |
28 | ChessRules.ParseFen(fen), | |
0fb43db7 | 29 | { captured: fenParts[4] } |
6f2f9437 | 30 | ); |
dac39588 BA |
31 | } |
32 | ||
241bf8f2 BA |
33 | getPpath(b) { |
34 | return ([V.MARSHALL, V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b; | |
35 | } | |
36 | ||
6808d7a1 | 37 | getFen() { |
dac39588 BA |
38 | return super.getFen() + " " + this.getCapturedFen(); |
39 | } | |
40 | ||
f9c36b2d | 41 | getFenForRepeat() { |
90e814b6 | 42 | return super.getFenForRepeat() + "_" + this.getCapturedFen(); |
f9c36b2d BA |
43 | } |
44 | ||
6808d7a1 | 45 | getCapturedFen() { |
6e47d367 | 46 | let counts = [...Array(12).fill(0)]; |
dac39588 | 47 | let i = 0; |
6808d7a1 | 48 | for (let j = 0; j < V.PIECES.length; j++) { |
6e47d367 BA |
49 | if ([V.KING, V.PAWN].includes(V.PIECES[j])) |
50 | // No king captured, and pawns don't promote in pawns | |
dac39588 | 51 | continue; |
6e47d367 BA |
52 | counts[i] = this.captured["w"][V.PIECES[j]]; |
53 | counts[6 + i] = this.captured["b"][V.PIECES[j]]; | |
dac39588 BA |
54 | i++; |
55 | } | |
56 | return counts.join(""); | |
57 | } | |
58 | ||
6808d7a1 | 59 | setOtherVariables(fen) { |
dac39588 | 60 | super.setOtherVariables(fen); |
e50a8025 BA |
61 | const captured = |
62 | V.ParseFen(fen).captured.split("").map(x => parseInt(x, 10)); | |
dac39588 | 63 | // Initialize captured pieces' counts from FEN |
6808d7a1 BA |
64 | this.captured = { |
65 | w: { | |
6e0f2842 BA |
66 | [V.ROOK]: captured[0], |
67 | [V.KNIGHT]: captured[1], | |
68 | [V.BISHOP]: captured[2], | |
69 | [V.QUEEN]: captured[3], | |
70 | [V.MARSHALL]: captured[4], | |
71 | [V.CARDINAL]: captured[5] | |
dac39588 | 72 | }, |
6808d7a1 | 73 | b: { |
6e0f2842 BA |
74 | [V.ROOK]: captured[6], |
75 | [V.KNIGHT]: captured[7], | |
76 | [V.BISHOP]: captured[8], | |
77 | [V.QUEEN]: captured[9], | |
78 | [V.MARSHALL]: captured[10], | |
79 | [V.CARDINAL]: captured[11] | |
dac39588 BA |
80 | } |
81 | }; | |
82 | } | |
83 | ||
6808d7a1 BA |
84 | static get size() { |
85 | return { x: 10, y: 10 }; | |
86 | } | |
dac39588 | 87 | |
a6836242 | 88 | // Rook + knight: |
6808d7a1 BA |
89 | static get MARSHALL() { |
90 | return "m"; | |
a6836242 BA |
91 | } |
92 | ||
93 | // Bishop + knight | |
6808d7a1 BA |
94 | static get CARDINAL() { |
95 | return "c"; | |
a6836242 | 96 | } |
dac39588 | 97 | |
6808d7a1 BA |
98 | static get PIECES() { |
99 | return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]); | |
dac39588 BA |
100 | } |
101 | ||
6808d7a1 BA |
102 | getPotentialMovesFrom([x, y]) { |
103 | switch (this.getPiece(x, y)) { | |
dac39588 | 104 | case V.MARSHALL: |
6808d7a1 | 105 | return this.getPotentialMarshallMoves([x, y]); |
dac39588 | 106 | case V.CARDINAL: |
6808d7a1 | 107 | return this.getPotentialCardinalMoves([x, y]); |
dac39588 | 108 | default: |
6808d7a1 | 109 | return super.getPotentialMovesFrom([x, y]); |
dac39588 BA |
110 | } |
111 | } | |
112 | ||
113 | // Special pawn rules: promotions to captured friendly pieces, | |
114 | // optional on ranks 8-9 and mandatory on rank 10. | |
6808d7a1 | 115 | getPotentialPawnMoves([x, y]) { |
dac39588 BA |
116 | const color = this.turn; |
117 | let moves = []; | |
6808d7a1 BA |
118 | const [sizeX, sizeY] = [V.size.x, V.size.y]; |
119 | const shiftX = color == "w" ? -1 : 1; | |
0fb43db7 | 120 | const startRank = (color == "w" ? sizeX - 3 : 2); |
6808d7a1 BA |
121 | const lastRanks = |
122 | color == "w" ? [0, 1, 2] : [sizeX - 1, sizeX - 2, sizeX - 3]; | |
123 | const promotionPieces = [ | |
124 | V.ROOK, | |
125 | V.KNIGHT, | |
126 | V.BISHOP, | |
127 | V.QUEEN, | |
128 | V.MARSHALL, | |
129 | V.CARDINAL | |
130 | ]; | |
dac39588 BA |
131 | |
132 | // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank | |
133 | let finalPieces = undefined; | |
6808d7a1 | 134 | if (lastRanks.includes(x + shiftX)) { |
dac39588 | 135 | finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0); |
6808d7a1 | 136 | if (x + shiftX != lastRanks[0]) finalPieces.push(V.PAWN); |
0fb43db7 BA |
137 | } |
138 | else finalPieces = [V.PAWN]; | |
6808d7a1 | 139 | if (this.board[x + shiftX][y] == V.EMPTY) { |
dac39588 BA |
140 | // One square forward |
141 | for (let piece of finalPieces) | |
6808d7a1 BA |
142 | moves.push( |
143 | this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) | |
144 | ); | |
0fb43db7 BA |
145 | if (x == startRank && this.board[x + 2 * shiftX][y] == V.EMPTY) |
146 | // Two squares jump | |
147 | moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); | |
dac39588 BA |
148 | } |
149 | // Captures | |
6808d7a1 BA |
150 | for (let shiftY of [-1, 1]) { |
151 | if ( | |
152 | y + shiftY >= 0 && | |
153 | y + shiftY < sizeY && | |
154 | this.board[x + shiftX][y + shiftY] != V.EMPTY && | |
155 | this.canTake([x, y], [x + shiftX, y + shiftY]) | |
156 | ) { | |
157 | for (let piece of finalPieces) { | |
158 | moves.push( | |
159 | this.getBasicMove([x, y], [x + shiftX, y + shiftY], { | |
160 | c: color, | |
161 | p: piece | |
162 | }) | |
163 | ); | |
dac39588 BA |
164 | } |
165 | } | |
166 | } | |
167 | ||
168 | // En passant | |
0fb43db7 BA |
169 | Array.prototype.push.apply( |
170 | moves, | |
171 | this.getEnpassantCaptures([x, y], shiftX) | |
172 | ); | |
dac39588 BA |
173 | |
174 | return moves; | |
175 | } | |
176 | ||
6808d7a1 | 177 | getPotentialMarshallMoves(sq) { |
dac39588 | 178 | return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( |
6808d7a1 BA |
179 | this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") |
180 | ); | |
dac39588 BA |
181 | } |
182 | ||
6808d7a1 | 183 | getPotentialCardinalMoves(sq) { |
dac39588 | 184 | return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( |
6808d7a1 BA |
185 | this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") |
186 | ); | |
dac39588 BA |
187 | } |
188 | ||
68e19a44 | 189 | isAttacked(sq, color) { |
6808d7a1 | 190 | return ( |
68e19a44 BA |
191 | super.isAttacked(sq, color) || |
192 | this.isAttackedByMarshall(sq, color) || | |
193 | this.isAttackedByCardinal(sq, color) | |
6808d7a1 | 194 | ); |
dac39588 BA |
195 | } |
196 | ||
68e19a44 | 197 | isAttackedByMarshall(sq, color) { |
6808d7a1 | 198 | return ( |
68e19a44 | 199 | this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) || |
6808d7a1 BA |
200 | this.isAttackedBySlideNJump( |
201 | sq, | |
68e19a44 | 202 | color, |
6808d7a1 BA |
203 | V.MARSHALL, |
204 | V.steps[V.KNIGHT], | |
205 | "oneStep" | |
206 | ) | |
207 | ); | |
dac39588 BA |
208 | } |
209 | ||
68e19a44 | 210 | isAttackedByCardinal(sq, color) { |
6808d7a1 | 211 | return ( |
68e19a44 | 212 | this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) || |
6808d7a1 BA |
213 | this.isAttackedBySlideNJump( |
214 | sq, | |
68e19a44 | 215 | color, |
6808d7a1 BA |
216 | V.CARDINAL, |
217 | V.steps[V.KNIGHT], | |
218 | "oneStep" | |
219 | ) | |
220 | ); | |
dac39588 BA |
221 | } |
222 | ||
3a2a7b5f BA |
223 | postPlay(move) { |
224 | super.postPlay(move); | |
6e47d367 | 225 | if (move.vanish.length == 2 && move.appear.length == 1) |
dac39588 BA |
226 | // Capture: update this.captured |
227 | this.captured[move.vanish[1].c][move.vanish[1].p]++; | |
dac39588 BA |
228 | } |
229 | ||
3a2a7b5f BA |
230 | postUndo(move) { |
231 | super.postUndo(move); | |
dac39588 BA |
232 | if (move.vanish.length == 2 && move.appear.length == 1) |
233 | this.captured[move.vanish[1].c][move.vanish[1].p]--; | |
dac39588 BA |
234 | } |
235 | ||
6808d7a1 | 236 | static get VALUES() { |
dac39588 | 237 | return Object.assign( |
a97bdbda BA |
238 | { c: 5, m: 7 }, //experimental |
239 | ChessRules.VALUES | |
dac39588 BA |
240 | ); |
241 | } | |
242 | ||
6808d7a1 BA |
243 | static get SEARCH_DEPTH() { |
244 | return 2; | |
245 | } | |
dac39588 | 246 | |
7ba4a5bc | 247 | static GenRandInitFen(randomness) { |
7ba4a5bc | 248 | if (randomness == 0) { |
2c5d7b20 BA |
249 | return ( |
250 | "r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " + | |
0fb43db7 | 251 | "w 0 - 00000000000000" |
2c5d7b20 | 252 | ); |
7ba4a5bc BA |
253 | } |
254 | ||
0fb43db7 BA |
255 | let pieces = { w: new Array(8), b: new Array(8) }; |
256 | // Shuffle pieces on second and before-last rank | |
6808d7a1 | 257 | for (let c of ["w", "b"]) { |
7ba4a5bc BA |
258 | if (c == 'b' && randomness == 1) { |
259 | pieces['b'] = pieces['w']; | |
260 | break; | |
261 | } | |
262 | ||
0fb43db7 | 263 | let positions = ArrayFun.range(8); |
dac39588 BA |
264 | |
265 | // Get random squares for bishops | |
0fb43db7 | 266 | let randIndex = 2 * randInt(4); |
3208c667 | 267 | const bishop1Pos = positions[randIndex]; |
dac39588 | 268 | // The second bishop must be on a square of different color |
0fb43db7 | 269 | let randIndex_tmp = 2 * randInt(4) + 1; |
3208c667 | 270 | const bishop2Pos = positions[randIndex_tmp]; |
dac39588 | 271 | // Remove chosen squares |
6808d7a1 BA |
272 | positions.splice(Math.max(randIndex, randIndex_tmp), 1); |
273 | positions.splice(Math.min(randIndex, randIndex_tmp), 1); | |
dac39588 BA |
274 | |
275 | // Get random squares for knights | |
0fb43db7 | 276 | randIndex = randInt(6); |
3208c667 | 277 | const knight1Pos = positions[randIndex]; |
dac39588 | 278 | positions.splice(randIndex, 1); |
0fb43db7 | 279 | randIndex = randInt(5); |
3208c667 | 280 | const knight2Pos = positions[randIndex]; |
dac39588 BA |
281 | positions.splice(randIndex, 1); |
282 | ||
283 | // Get random square for queen | |
0fb43db7 | 284 | randIndex = randInt(4); |
3208c667 | 285 | const queenPos = positions[randIndex]; |
dac39588 BA |
286 | positions.splice(randIndex, 1); |
287 | ||
288 | // ...random square for marshall | |
0fb43db7 | 289 | randIndex = randInt(3); |
3208c667 | 290 | const marshallPos = positions[randIndex]; |
dac39588 BA |
291 | positions.splice(randIndex, 1); |
292 | ||
293 | // ...random square for cardinal | |
0fb43db7 | 294 | randIndex = randInt(2); |
3208c667 | 295 | const cardinalPos = positions[randIndex]; |
dac39588 BA |
296 | positions.splice(randIndex, 1); |
297 | ||
0fb43db7 | 298 | // King position is now fixed, |
3208c667 | 299 | const kingPos = positions[0]; |
dac39588 BA |
300 | |
301 | // Finally put the shuffled pieces in the board array | |
6808d7a1 BA |
302 | pieces[c][knight1Pos] = "n"; |
303 | pieces[c][bishop1Pos] = "b"; | |
304 | pieces[c][queenPos] = "q"; | |
305 | pieces[c][marshallPos] = "m"; | |
306 | pieces[c][cardinalPos] = "c"; | |
307 | pieces[c][kingPos] = "k"; | |
308 | pieces[c][bishop2Pos] = "b"; | |
309 | pieces[c][knight2Pos] = "n"; | |
dac39588 | 310 | } |
6808d7a1 | 311 | return ( |
0fb43db7 BA |
312 | "r8r/1" + pieces["b"].join("") + "1/" + |
313 | "pppppppppp/91/91/91/91/PPPPPPPPPP/" + | |
314 | "1" + pieces["w"].join("").toUpperCase() + "1/R8R" + | |
315 | " w 0 " + " - 00000000000000" | |
6808d7a1 | 316 | ); |
dac39588 | 317 | } |
7e8a7ea1 | 318 | |
6808d7a1 | 319 | }; |