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