Commit | Line | Data |
---|---|---|
723262f9 BA |
1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
2 | import { randInt } from "@/utils/alea"; | |
3 | ||
4 | export class PandemoniumRules extends ChessRules { | |
5 | ||
6 | static get PawnSpecs() { | |
7 | return Object.assign( | |
8 | {}, | |
9 | ChessRules.PawnSpecs, | |
10 | { | |
11 | threeSquares: true, | |
12 | promotions: [V.GILDING] | |
13 | } | |
14 | ); | |
15 | } | |
16 | ||
17 | static get GILDING() { | |
18 | return "g"; | |
19 | } | |
20 | ||
21 | static get SCEPTER() { | |
22 | return "s"; | |
23 | } | |
24 | ||
25 | static get HORSE() { | |
26 | return "h"; | |
27 | } | |
28 | ||
29 | static get DRAGON() { | |
30 | return "d"; | |
31 | } | |
32 | ||
33 | static get CARDINAL() { | |
34 | return "c"; | |
35 | } | |
36 | ||
37 | static get WHOLE() { | |
38 | return "w"; | |
39 | } | |
40 | ||
41 | static get MARSHAL() { | |
42 | return "m"; | |
43 | } | |
44 | ||
45 | static get APRICOT() { | |
46 | return "a"; | |
47 | } | |
48 | ||
49 | static get PIECES() { | |
50 | return ( | |
51 | ChessRules.PIECES.concat([ | |
52 | V.GILDING, V.SCEPTER, V.HORSE, V.DRAGON, | |
53 | V.CARDINAL, V.WHOLE, V.MARSHAL, V.APRICOT]) | |
54 | ); | |
55 | } | |
56 | ||
57 | getPpath(b) { | |
58 | const prefix = (ChessRules.PIECES.includes(b[1]) ? "" : "Pandemonium/"); | |
59 | return prefix + b; | |
60 | } | |
61 | ||
62 | static get size() { | |
63 | return { x: 10, y: 10}; | |
64 | } | |
65 | ||
66 | getColor(i, j) { | |
67 | if (i >= V.size.x) return i == V.size.x ? "w" : "b"; | |
68 | return this.board[i][j].charAt(0); | |
69 | } | |
70 | ||
71 | getPiece(i, j) { | |
72 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; | |
73 | return this.board[i][j].charAt(1); | |
74 | } | |
75 | ||
76 | setOtherVariables(fen) { | |
77 | super.setOtherVariables(fen); | |
78 | // Sub-turn is useful only at first move... | |
79 | this.subTurn = 1; | |
80 | // Also init reserves (used by the interface to show landable pieces) | |
81 | const reserve = | |
82 | V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); | |
83 | this.reserve = { | |
84 | w: { | |
85 | [V.PAWN]: reserve[0], | |
86 | [V.ROOK]: reserve[1], | |
87 | [V.KNIGHT]: reserve[2], | |
88 | [V.BISHOP]: reserve[3], | |
89 | [V.QUEEN]: reserve[4], | |
90 | [V.CARDINAL]: reserve[5], | |
91 | [V.MARSHAL]: reserve[6], | |
92 | }, | |
93 | b: { | |
94 | [V.PAWN]: reserve[7], | |
95 | [V.ROOK]: reserve[8], | |
96 | [V.KNIGHT]: reserve[9], | |
97 | [V.BISHOP]: reserve[10], | |
98 | [V.QUEEN]: reserve[11], | |
99 | [V.CARDINAL]: reserve[12], | |
100 | [V.MARSHAL]: reserve[13] | |
101 | } | |
102 | }; | |
103 | } | |
104 | ||
105 | static IsGoodEnpassant(enpassant) { | |
106 | if (enpassant != "-") { | |
107 | const squares = enpassant.split(","); | |
108 | if (squares.length > 2) return false; | |
109 | for (let sq of squares) { | |
110 | if (!sq.match(/[a-j0-9]/)) return false; | |
111 | } | |
112 | } | |
113 | return true; | |
114 | } | |
115 | ||
116 | static IsGoodFen(fen) { | |
117 | if (!ChessRules.IsGoodFen(fen)) return false; | |
118 | const fenParsed = V.ParseFen(fen); | |
119 | // Check reserves | |
120 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{14,14}$/)) | |
121 | return false; | |
122 | return true; | |
123 | } | |
124 | ||
125 | static ParseFen(fen) { | |
126 | const fenParts = fen.split(" "); | |
127 | return Object.assign( | |
128 | ChessRules.ParseFen(fen), | |
129 | { reserve: fenParts[5] } | |
130 | ); | |
131 | } | |
132 | ||
133 | getFen() { | |
134 | return super.getFen() + " " + this.getReserveFen(); | |
135 | } | |
136 | ||
137 | getFenForRepeat() { | |
138 | return super.getFenForRepeat() + "_" + this.getReserveFen(); | |
139 | } | |
140 | ||
141 | getReserveFen() { | |
142 | let counts = new Array(14); | |
143 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
144 | counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]]; | |
145 | counts[7 + i] = this.reserve["b"][V.RESERVE_PIECES[i]]; | |
146 | } | |
147 | return counts.join(""); | |
148 | } | |
149 | ||
150 | setFlags(fenflags) { | |
151 | // white a-castle, h-castle, king pos, then same for black. | |
152 | this.castleFlags = { w: [-1, -1, -1], b: [-1, -1, -1] }; | |
153 | for (let i = 0; i < 6; i++) { | |
154 | this.castleFlags[i < 3 ? "w" : "b"][i % 3] = | |
155 | V.ColumnToCoord(fenflags.charAt(i)); | |
156 | } | |
157 | } | |
158 | ||
159 | static GenRandInitFen(randomness) { | |
160 | // No randomization here for now (but initial setup choice) | |
161 | return ( | |
162 | "rnbqkmcbnr/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/RNBQKMCBNR " + | |
163 | "w 0 ajeaje - 00000000000000" | |
164 | ); | |
165 | // TODO later: randomization too --> 2 bishops, not next to each other. | |
166 | // then knights next to bishops. Then other pieces (...). | |
167 | } | |
168 | ||
169 | getEnpassantFen() { | |
170 | const L = this.epSquares.length; | |
171 | if (!this.epSquares[L - 1]) return "-"; //no en-passant | |
172 | let res = ""; | |
173 | this.epSquares[L - 1].forEach(sq => { | |
174 | res += V.CoordsToSquare(sq) + ","; | |
175 | }); | |
176 | return res.slice(0, -1); //remove last comma | |
177 | } | |
178 | ||
179 | getEpSquare(moveOrSquare) { | |
180 | if (!moveOrSquare) return undefined; | |
181 | if (typeof moveOrSquare === "string") { | |
182 | const square = moveOrSquare; | |
183 | if (square == "-") return undefined; | |
184 | let res = []; | |
185 | square.split(",").forEach(sq => { | |
186 | res.push(V.SquareToCoords(sq)); | |
187 | }); | |
188 | return res; | |
189 | } | |
190 | // Argument is a move: | |
191 | const move = moveOrSquare; | |
192 | const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x]; | |
193 | if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) { | |
194 | const step = (ex - sx) / Math.abs(ex - sx); | |
195 | let res = [{ | |
196 | x: sx + step, | |
197 | y: sy | |
198 | }]; | |
199 | if (sx + 2 * step != ex) { | |
200 | // 3-squares jump | |
201 | res.push({ | |
202 | x: sx + 2 * step, | |
203 | y: sy | |
204 | }); | |
205 | } | |
206 | return res; | |
207 | } | |
208 | return undefined; //default | |
209 | } | |
210 | ||
211 | getReservePpath(index, color) { | |
212 | const p = V.RESERVE_PIECES[index]; | |
213 | const prefix = (ChessRules.PIECES.includes(p) ? "" : "Pandemonium/"); | |
214 | return prefix + color + p;; | |
215 | } | |
216 | ||
217 | // Ordering on reserve pieces | |
218 | static get RESERVE_PIECES() { | |
219 | return ( | |
220 | [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.CARDINAL, V.MARSHAL] | |
221 | ); | |
222 | } | |
223 | ||
224 | getReserveMoves([x, y]) { | |
225 | const color = this.turn; | |
822d71d6 | 226 | const oppCol = V.GetOppCol(color); |
723262f9 BA |
227 | const p = V.RESERVE_PIECES[y]; |
228 | if (this.reserve[color][p] == 0) return []; | |
229 | const bounds = (p == V.PAWN ? [1, V.size.x - 1] : [0, V.size.x]); | |
230 | let moves = []; | |
231 | for (let i = bounds[0]; i < bounds[1]; i++) { | |
232 | for (let j = 0; j < V.size.y; j++) { | |
233 | if (this.board[i][j] == V.EMPTY) { | |
234 | let mv = new Move({ | |
235 | appear: [ | |
236 | new PiPo({ | |
237 | x: i, | |
238 | y: j, | |
239 | c: color, | |
240 | p: p | |
241 | }) | |
242 | ], | |
243 | vanish: [], | |
244 | start: { x: x, y: y }, //a bit artificial... | |
245 | end: { x: i, y: j } | |
246 | }); | |
247 | if (p == V.PAWN) { | |
248 | // Do not drop on checkmate: | |
249 | this.play(mv); | |
250 | const res = ( | |
251 | this.underCheck(oppCol) && !this.atLeastOneMove("noReserve") | |
252 | ); | |
253 | this.undo(mv); | |
254 | if (res) continue; | |
255 | } | |
256 | moves.push(mv); | |
257 | } | |
258 | } | |
259 | } | |
260 | return moves; | |
261 | } | |
262 | ||
263 | static get PromoteMap() { | |
264 | return { | |
265 | r: 'd', | |
266 | n: 's', | |
267 | b: 'h', | |
268 | c: 'w', | |
269 | m: 'a' | |
270 | }; | |
271 | } | |
272 | ||
273 | getPotentialMovesFrom([x, y]) { | |
274 | const c = this.getColor(x, y); | |
275 | const oppCol = V.GetOppCol(c); | |
276 | if (this.movesCount <= 1) { | |
277 | if (this.kingPos[c][0] == x && this.kingPos[c][1] == y) { | |
278 | // Pass (if setup is ok) | |
279 | return [ | |
280 | new Move({ | |
281 | appear: [], | |
282 | vanish: [], | |
283 | start: { x: this.kingPos[c][0], y: this.kingPos[c][1] }, | |
284 | end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] } | |
285 | }) | |
286 | ]; | |
287 | } | |
288 | const firstRank = (this.movesCount == 0 ? 9 : 0); | |
289 | // TODO: initDestFile currently hardcoded for deterministic setup | |
290 | const initDestFile = new Map([[1, 2], [8, 7]]); | |
7721a36a BA |
291 | // Only option is knight --> bishop swap: |
292 | if ( | |
293 | x == firstRank && | |
294 | !!initDestFile.get(y) && | |
295 | this.getPiece(x, y) == V.KNIGHT | |
296 | ) { | |
723262f9 BA |
297 | const destFile = initDestFile.get(y); |
298 | return [ | |
299 | new Move({ | |
300 | appear: [ | |
301 | new PiPo({ | |
302 | x: x, | |
303 | y: destFile, | |
304 | c: c, | |
305 | p: V.KNIGHT | |
306 | }), | |
307 | new PiPo({ | |
308 | x: x, | |
309 | y: y, | |
310 | c: c, | |
311 | p: V.BISHOP | |
312 | }) | |
313 | ], | |
314 | vanish: [ | |
315 | new PiPo({ | |
316 | x: x, | |
317 | y: y, | |
318 | c: c, | |
319 | p: V.KNIGHT | |
320 | }), | |
321 | new PiPo({ | |
322 | x: x, | |
323 | y: destFile, | |
324 | c: c, | |
325 | p: V.BISHOP | |
326 | }) | |
327 | ], | |
328 | start: { x: x, y: y }, | |
329 | end: { x: x, y: destFile } | |
330 | }) | |
331 | ]; | |
332 | } | |
333 | return []; | |
334 | } | |
335 | // Normal move (after initial setup) | |
336 | if (x >= V.size.x) return this.getReserveMoves(x, y); | |
337 | const p = this.getPiece(x, y); | |
338 | const sq = [x, y]; | |
339 | let moves = []; | |
340 | if (ChessRules.PIECES.includes(p)) | |
341 | moves = super.getPotentialMovesFrom(sq); | |
342 | if ([V.GILDING, V.APRICOT, V.WHOLE].includes(p)) | |
343 | moves = super.getPotentialQueenMoves(sq); | |
344 | switch (p) { | |
345 | case V.SCEPTER: | |
346 | moves = this.getPotentialScepterMoves(sq); | |
347 | break; | |
348 | case V.HORSE: | |
349 | moves = this.getPotentialHorseMoves(sq); | |
350 | break; | |
351 | case V.DRAGON: | |
352 | moves = this.getPotentialDragonMoves(sq); | |
353 | break; | |
354 | case V.CARDINAL: | |
355 | moves = this.getPotentialCardinalMoves(sq); | |
356 | break; | |
357 | case V.MARSHAL: | |
358 | moves = this.getPotentialMarshalMoves(sq); | |
359 | break; | |
360 | } | |
361 | // Maybe apply promotions: | |
362 | if (Object.keys(V.PromoteMap).includes(p)) { | |
363 | const promoted = V.PromoteMap[p]; | |
364 | const lastRank = (c == 'w' ? 0 : 9); | |
365 | let promotions = []; | |
366 | moves.forEach(m => { | |
367 | if (m.start.x == lastRank || m.end.x == lastRank) { | |
368 | let pMove = JSON.parse(JSON.stringify(m)); | |
369 | pMove.appear[0].p = promoted; | |
370 | promotions.push(pMove); | |
371 | } | |
372 | }); | |
373 | Array.prototype.push.apply(moves, promotions); | |
374 | } | |
375 | return moves; | |
376 | } | |
377 | ||
378 | getPotentialPawnMoves([x, y]) { | |
379 | const color = this.turn; | |
380 | const shiftX = V.PawnSpecs.directions[color]; | |
381 | let moves = []; | |
382 | if (this.board[x + shiftX][y] == V.EMPTY) { | |
383 | this.addPawnMoves([x, y], [x + shiftX, y], moves); | |
384 | if ((color == 'w' && x >= V.size.x - 3) || (color == 'b' && x <= 3)) { | |
385 | if (this.board[x + 2 * shiftX][y] == V.EMPTY) { | |
386 | moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); | |
387 | if ( | |
388 | ( | |
389 | (color == 'w' && x >= V.size.x - 2) || | |
390 | (color == 'b' && x <= 2) | |
391 | ) | |
392 | && | |
393 | this.board[x + 3 * shiftX][y] == V.EMPTY | |
394 | ) { | |
395 | moves.push(this.getBasicMove([x, y], [x + 3 * shiftX, y])); | |
396 | } | |
397 | } | |
398 | } | |
399 | } | |
400 | for (let shiftY of [-1, 1]) { | |
401 | if (y + shiftY >= 0 && y + shiftY < V.size.y) { | |
402 | if ( | |
403 | this.board[x + shiftX][y + shiftY] != V.EMPTY && | |
404 | this.canTake([x, y], [x + shiftX, y + shiftY]) | |
405 | ) { | |
406 | this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves); | |
407 | } | |
408 | } | |
409 | } | |
410 | Array.prototype.push.apply( | |
411 | moves, | |
412 | this.getEnpassantCaptures([x, y], shiftX) | |
413 | ); | |
414 | return moves; | |
415 | } | |
416 | ||
417 | getPotentialMarshalMoves(sq) { | |
418 | return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( | |
419 | this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") | |
420 | ); | |
421 | } | |
422 | ||
423 | getPotentialCardinalMoves(sq) { | |
424 | return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( | |
425 | this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") | |
426 | ); | |
427 | } | |
428 | ||
429 | getPotentialScepterMoves(sq) { | |
430 | const steps = | |
431 | V.steps[V.KNIGHT].concat(V.steps[V.BISHOP]).concat(V.steps[V.ROOK]); | |
432 | return this.getSlideNJumpMoves(sq, steps, "oneStep"); | |
433 | } | |
434 | ||
435 | getPotentialHorseMoves(sq) { | |
436 | return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( | |
437 | this.getSlideNJumpMoves(sq, V.steps[V.ROOK], "oneStep")); | |
438 | } | |
439 | ||
440 | getPotentialDragonMoves(sq) { | |
441 | return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( | |
442 | this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep")); | |
443 | } | |
444 | ||
445 | getEnpassantCaptures([x, y], shiftX) { | |
446 | const Lep = this.epSquares.length; | |
447 | const epSquare = this.epSquares[Lep - 1]; | |
448 | let moves = []; | |
449 | if (!!epSquare) { | |
450 | for (let epsq of epSquare) { | |
451 | // TODO: some redundant checks | |
452 | if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) { | |
453 | let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); | |
454 | // WARNING: the captured pawn may be diagonally behind us, | |
455 | // if it's a 3-squares jump and we take on 1st passing square | |
456 | const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX; | |
457 | enpassantMove.vanish.push({ | |
458 | x: px, | |
459 | y: epsq.y, | |
460 | p: "p", | |
461 | c: this.getColor(px, epsq.y) | |
462 | }); | |
463 | moves.push(enpassantMove); | |
464 | } | |
465 | } | |
466 | } | |
467 | return moves; | |
468 | } | |
469 | ||
470 | getPotentialKingMoves(sq) { | |
471 | // Initialize with normal moves | |
472 | let moves = this.getSlideNJumpMoves( | |
473 | sq, | |
474 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), | |
475 | "oneStep" | |
476 | ); | |
477 | const c = this.turn; | |
478 | if ( | |
479 | this.castleFlags[c][0] < V.size.y || | |
480 | this.castleFlags[c][1] < V.size.y | |
481 | ) { | |
482 | moves = moves.concat(this.getCastleMoves(sq)); | |
483 | } | |
484 | return moves; | |
485 | } | |
486 | ||
487 | getCastleMoves([x, y]) { | |
488 | const c = this.getColor(x, y); | |
489 | if ( | |
490 | ((c == 'w' && x == 9) || (c == 'b' && x == 0)) && | |
491 | y == this.castleFlags[c][2] | |
492 | ) { | |
493 | const finalSquares = [ | |
494 | [1, 2], | |
495 | [7, 6] | |
496 | ]; | |
497 | return super.getCastleMoves([x, y], finalSquares, false, [V.ROOK]); | |
498 | } | |
499 | return []; | |
500 | } | |
501 | ||
502 | isAttacked(sq, color) { | |
503 | return ( | |
504 | this.isAttackedByPawn(sq, color) || | |
505 | this.isAttackedByRook(sq, color) || | |
506 | this.isAttackedByKnight(sq, color) || | |
507 | this.isAttackedByBishop(sq, color) || | |
508 | this.isAttackedByKing(sq, color) || | |
509 | this.isAttackedByQueens(sq, color) || | |
510 | this.isAttackedByScepter(sq, color) || | |
511 | this.isAttackedByDragon(sq, color) || | |
512 | this.isAttackedByHorse(sq, color) || | |
513 | this.isAttackedByMarshal(sq, color) || | |
514 | this.isAttackedByCardinal(sq, color) | |
515 | ); | |
516 | } | |
517 | ||
518 | isAttackedByQueens([x, y], color) { | |
519 | // pieces: because queen = gilding = whole = apricot | |
520 | const pieces = [V.QUEEN, V.GILDING, V.WHOLE, V.APRICOT]; | |
521 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
522 | for (let step of steps) { | |
523 | let rx = x + step[0], | |
524 | ry = y + step[1]; | |
525 | while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY) { | |
526 | rx += step[0]; | |
527 | ry += step[1]; | |
528 | } | |
529 | if ( | |
530 | V.OnBoard(rx, ry) && | |
531 | this.board[rx][ry] != V.EMPTY && | |
532 | pieces.includes(this.getPiece(rx, ry)) && | |
533 | this.getColor(rx, ry) == color | |
534 | ) { | |
535 | return true; | |
536 | } | |
537 | } | |
538 | return false; | |
539 | } | |
540 | ||
541 | isAttackedByScepter(sq, color) { | |
542 | const steps = | |
543 | V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]); | |
544 | return ( | |
b2511e7e | 545 | super.isAttackedBySlideNJump(sq, color, V.SCEPTER, steps, "oneStep") |
723262f9 BA |
546 | ); |
547 | } | |
548 | ||
549 | isAttackedByHorse(sq, color) { | |
550 | return ( | |
551 | super.isAttackedBySlideNJump(sq, color, V.steps[V.BISHOP], V.HORSE) || | |
552 | super.isAttackedBySlideNJump( | |
b2511e7e | 553 | sq, color, V.HORSE, V.steps[V.ROOK], "oneStep") |
723262f9 BA |
554 | ); |
555 | } | |
556 | ||
557 | isAttackedByDragon(sq, color) { | |
558 | return ( | |
559 | super.isAttackedBySlideNJump(sq, color, V.steps[V.ROOK], V.DRAGON) || | |
560 | super.isAttackedBySlideNJump( | |
b2511e7e | 561 | sq, color, V.DRAGON, V.steps[V.BISHOP], "oneStep") |
723262f9 BA |
562 | ); |
563 | } | |
564 | ||
565 | isAttackedByMarshal(sq, color) { | |
566 | return ( | |
567 | super.isAttackedBySlideNJump(sq, color, V.MARSHAL, V.steps[V.ROOK]) || | |
568 | super.isAttackedBySlideNJump( | |
569 | sq, | |
570 | color, | |
571 | V.MARSHAL, | |
572 | V.steps[V.KNIGHT], | |
573 | "oneStep" | |
574 | ) | |
575 | ); | |
576 | } | |
577 | ||
578 | isAttackedByCardinal(sq, color) { | |
579 | return ( | |
580 | super.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) || | |
581 | super.isAttackedBySlideNJump( | |
582 | sq, | |
583 | color, | |
584 | V.CARDINAL, | |
585 | V.steps[V.KNIGHT], | |
586 | "oneStep" | |
587 | ) | |
588 | ); | |
589 | } | |
590 | ||
591 | getAllValidMoves() { | |
592 | let moves = super.getAllPotentialMoves(); | |
1220a5b9 BA |
593 | if (this.movesCount >= 2) { |
594 | const color = this.turn; | |
595 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
596 | moves = moves.concat( | |
597 | this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) | |
598 | ); | |
599 | } | |
723262f9 BA |
600 | } |
601 | return this.filterValid(moves); | |
602 | } | |
603 | ||
604 | atLeastOneMove(noReserve) { | |
605 | if (!super.atLeastOneMove()) { | |
606 | if (!noReserve) { | |
607 | // Search one reserve move | |
608 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
609 | let moves = this.filterValid( | |
610 | this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) | |
611 | ); | |
612 | if (moves.length > 0) return true; | |
613 | } | |
614 | } | |
615 | return false; | |
616 | } | |
617 | return true; | |
618 | } | |
619 | ||
620 | // Reverse 'PromoteMap' | |
621 | static get P_CORRESPONDANCES() { | |
622 | return { | |
623 | d: 'r', | |
624 | s: 'n', | |
625 | h: 'b', | |
626 | w: 'c', | |
627 | a: 'm' | |
628 | }; | |
629 | } | |
630 | ||
631 | static MayDecode(piece) { | |
632 | if (Object.keys(V.P_CORRESPONDANCES).includes(piece)) | |
633 | return V.P_CORRESPONDANCES[piece]; | |
634 | return piece; | |
635 | } | |
636 | ||
637 | play(move) { | |
638 | move.subTurn = this.subTurn; //much easier | |
639 | if (this.movesCount >= 2 || this.subTurn == 2 || move.vanish.length == 0) { | |
640 | this.turn = V.GetOppCol(this.turn); | |
641 | this.subTurn = 1; | |
642 | this.movesCount++; | |
643 | } | |
644 | else this.subTurn = 2; | |
645 | move.flags = JSON.stringify(this.aggregateFlags()); | |
646 | this.epSquares.push(this.getEpSquare(move)); | |
647 | V.PlayOnBoard(this.board, move); | |
648 | this.postPlay(move); | |
649 | } | |
650 | ||
651 | updateCastleFlags(move, piece) { | |
acf20712 | 652 | if (piece == V.KING && move.appear.length == 2) { |
723262f9 BA |
653 | // Castling (only move which disable flags) |
654 | this.castleFlags[move.appear[0].c][0] = 10; | |
655 | this.castleFlags[move.appear[0].c][1] = 10; | |
656 | } | |
657 | } | |
658 | ||
659 | postPlay(move) { | |
660 | if (move.vanish.length == 0 && move.appear.length == 0) return; | |
661 | super.postPlay(move); | |
662 | const color = move.appear[0].c; | |
663 | if (move.vanish.length == 0) | |
664 | // Drop unpromoted piece: | |
665 | this.reserve[color][move.appear[0].p]--; | |
1220a5b9 | 666 | else if (move.vanish.length == 2 && move.appear.length == 1) |
723262f9 BA |
667 | // May capture a promoted piece: |
668 | this.reserve[color][V.MayDecode(move.vanish[1].p)]++; | |
669 | } | |
670 | ||
671 | undo(move) { | |
672 | this.epSquares.pop(); | |
673 | this.disaggregateFlags(JSON.parse(move.flags)); | |
674 | V.UndoOnBoard(this.board, move); | |
675 | if (this.movesCount >= 2 || this.subTurn == 1 || move.vanish.length == 0) { | |
676 | this.turn = V.GetOppCol(this.turn); | |
677 | this.movesCount--; | |
678 | } | |
679 | this.subTurn = move.subTurn; | |
680 | this.postUndo(move); | |
681 | } | |
682 | ||
683 | postUndo(move) { | |
684 | if (move.vanish.length == 0 && move.appear.length == 0) return; | |
685 | super.postUndo(move); | |
686 | const color = move.appear[0].c; | |
687 | if (move.vanish.length == 0) | |
688 | this.reserve[color][move.appear[0].p]++; | |
1220a5b9 | 689 | else if (move.vanish.length == 2 && move.appear.length == 1) |
723262f9 BA |
690 | this.reserve[color][V.MayDecode(move.vanish[1].p)]--; |
691 | } | |
692 | ||
693 | getCurrentScore() { | |
694 | const c = this.turn, | |
695 | oppCol = V.GetOppCol(this.turn); | |
696 | let facingKings = false; | |
697 | if ( | |
698 | this.kingPos[c][0] == this.kingPos[oppCol][0] || | |
699 | this.kingPos[c][1] == this.kingPos[oppCol][1] | |
700 | ) { | |
701 | facingKings = true; | |
702 | let step = [ | |
703 | this.kingPos[oppCol][0] - this.kingPos[c][0], | |
704 | this.kingPos[oppCol][1] - this.kingPos[c][1] | |
705 | ]; | |
706 | if (step[0] != 0) step[0] /= Math.abs(step[0]); | |
707 | else step[1] /= Math.abs(step[1]); | |
708 | let [x, y] = | |
709 | [ this.kingPos[c][0] + step[0], this.kingPos[c][1] + step[1] ]; | |
710 | while (x != this.kingPos[oppCol][0] || y != this.kingPos[oppCol][1]) { | |
711 | if (this.board[x][y] != V.EMPTY) { | |
712 | facingKings = false; | |
713 | break; | |
714 | } | |
715 | x += step[0]; | |
716 | y += step[1]; | |
717 | } | |
718 | } | |
719 | if (facingKings) return (c == "w" ? "1-0" : "0-1"); | |
720 | if (!this.atLeastOneMove()) return (c == "w" ? "0-1" : "1-0"); | |
721 | return "*"; | |
722 | } | |
723 | ||
724 | static get VALUES() { | |
725 | return Object.assign( | |
99ba622a BA |
726 | {}, |
727 | ChessRules.VALUES, | |
723262f9 | 728 | { |
99ba622a | 729 | n: 2.5, //knight is weaker |
723262f9 BA |
730 | g: 9, |
731 | s: 5, | |
732 | h: 6, | |
733 | d: 7, | |
734 | c: 7, | |
735 | w: 9, | |
736 | m: 8, | |
737 | a: 9 | |
99ba622a | 738 | } |
723262f9 BA |
739 | ); |
740 | } | |
741 | ||
742 | static get SEARCH_DEPTH() { | |
743 | return 2; | |
744 | } | |
745 | ||
746 | getComputerMove() { | |
747 | if (this.movesCount <= 1) { | |
748 | // Special case: swap and pass at random | |
749 | const moves1 = this.getAllValidMoves(); | |
750 | const m1 = moves1[randInt(moves1.length)]; | |
751 | this.play(m1); | |
752 | if (m1.vanish.length == 0) { | |
753 | this.undo(m1); | |
754 | return m1; | |
755 | } | |
756 | const moves2 = this.getAllValidMoves(); | |
757 | const m2 = moves2[randInt(moves2.length)]; | |
758 | this.undo(m1); | |
759 | return [m1, m2]; | |
760 | } | |
761 | return super.getComputerMove(); | |
762 | } | |
763 | ||
764 | evalPosition() { | |
765 | let evaluation = super.evalPosition(); | |
766 | // Add reserves: | |
767 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
768 | const p = V.RESERVE_PIECES[i]; | |
769 | evaluation += this.reserve["w"][p] * V.VALUES[p]; | |
770 | evaluation -= this.reserve["b"][p] * V.VALUES[p]; | |
771 | } | |
772 | return evaluation; | |
773 | } | |
774 | ||
775 | getNotation(move) { | |
776 | if (move.vanish.length == 0) { | |
777 | if (move.appear.length == 0) return "pass"; | |
778 | const pieceName = | |
779 | (move.appear[0].p == V.PAWN ? "" : move.appear[0].p.toUpperCase()); | |
780 | return pieceName + "@" + V.CoordsToSquare(move.end); | |
781 | } | |
782 | if (move.appear.length == 2) { | |
783 | if (move.appear[0].p != V.KING) | |
784 | return V.CoordsToSquare(move.start) + "S" + V.CoordsToSquare(move.end); | |
785 | return (move.end.y < move.start.y ? "0-0" : "0-0-0"); | |
786 | } | |
787 | let notation = super.getNotation(move); | |
788 | if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p) | |
789 | // Add promotion indication: | |
790 | notation += "=" + move.appear[0].p.toUpperCase(); | |
791 | return notation; | |
792 | } | |
793 | ||
794 | }; |