Commit | Line | Data |
---|---|---|
b967d5ba | 1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
5d75c82c | 2 | import { SuicideRules } from "@/variants/Suicide"; |
6c7cbfed BA |
3 | |
4 | export class ChakartRules extends ChessRules { | |
5d75c82c BA |
5 | static get PawnSpecs() { |
6 | return SuicideRules.PawnSpecs; | |
7 | } | |
8 | ||
9 | static get HasCastle() { | |
10 | return false; | |
11 | } | |
12 | ||
ad030c7d BA |
13 | static get CorrConfirm() { |
14 | // Because of bonus effects | |
15 | return false; | |
16 | } | |
90df90bc | 17 | |
ad030c7d BA |
18 | static get CanAnalyze() { |
19 | return false; | |
20 | } | |
6c7cbfed | 21 | |
90df90bc | 22 | hoverHighlight(x, y) { |
ad030c7d BA |
23 | if ( |
24 | this.firstMove.appear.length == 0 || | |
25 | this.firstMove.vanish.length == 0 || | |
26 | this.board[x][y] != V.EMPTY | |
27 | ) { | |
28 | return false; | |
29 | } | |
30 | const deltaX = Math.abs(this.firstMove.end.x - x); | |
31 | const deltaY = Math.abs(this.firstMove.end.y - y); | |
32 | return ( | |
33 | this.subTurn == 2 && | |
34 | // Condition: rook or bishop move, may capture, but no bonus move | |
35 | [V.ROOK, V.BISHOP].includes(this.firstMove.vanish[0].p) && | |
36 | ( | |
37 | this.firstMove.vanish.length == 1 || | |
38 | ['w', 'b'].includes(this.firstMove.vanish[1].c) | |
39 | ) && | |
40 | ( | |
41 | this.firstMove.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1 || | |
42 | this.firstMove.vanish[0].p == V.BISHOP && deltaX + deltaY == 1 | |
43 | ) | |
44 | ); | |
90df90bc BA |
45 | } |
46 | ||
b9ce3d0f BA |
47 | static get IMMOBILIZE_CODE() { |
48 | return { | |
49 | 'p': 's', | |
50 | 'r': 'u', | |
51 | 'n': 'o', | |
52 | 'b': 'c', | |
53 | 'q': 't', | |
54 | 'k': 'l' | |
55 | }; | |
56 | } | |
57 | ||
58 | static get IMMOBILIZE_DECODE() { | |
59 | return { | |
60 | 's': 'p', | |
61 | 'u': 'r', | |
62 | 'o': 'n', | |
63 | 'c': 'b', | |
64 | 't': 'q', | |
65 | 'l': 'k' | |
66 | }; | |
67 | } | |
68 | ||
69 | static get INVISIBLE_QUEEN() { | |
70 | return 'i'; | |
71 | } | |
72 | ||
ad030c7d BA |
73 | // Fictive color 'a', bomb banana mushroom egg |
74 | static get BOMB() { | |
75 | // Doesn't collide with bishop because color 'a' | |
76 | return 'b'; | |
77 | } | |
78 | static get BANANA() { | |
79 | return 'n'; | |
80 | } | |
81 | static get EGG() { | |
82 | return 'e'; | |
83 | } | |
84 | static get MUSHROOM() { | |
85 | return 'm'; | |
86 | } | |
87 | ||
88 | static get PIECES() { | |
89 | return ( | |
90 | ChessRules.PIECES.concat( | |
91 | Object.keys(V.IMMOBILIZE_DECODE)).concat( | |
92 | [V.BANANA, V.BOMB, V.EGG, V.MUSHROOM, V.INVISIBLE_QUEEN]) | |
93 | ); | |
94 | } | |
95 | ||
b9ce3d0f BA |
96 | getPpath(b) { |
97 | let prefix = ""; | |
98 | if ( | |
ad030c7d | 99 | b[0] == 'a' || |
b9ce3d0f BA |
100 | b[1] == V.INVISIBLE_QUEEN || |
101 | Object.keys(V.IMMOBILIZE_DECODE).includes(b[1]) | |
102 | ) { | |
103 | prefix = "Chakart/"; | |
104 | } | |
105 | return prefix + b; | |
106 | } | |
107 | ||
108 | static ParseFen(fen) { | |
109 | const fenParts = fen.split(" "); | |
110 | return Object.assign( | |
111 | ChessRules.ParseFen(fen), | |
b967d5ba | 112 | { captured: fenParts[5] } |
b9ce3d0f BA |
113 | ); |
114 | } | |
115 | ||
116 | // King can be l or L (immobilized) --> similar to Alice variant | |
90df90bc BA |
117 | static IsGoodPosition(position) { |
118 | if (position.length == 0) return false; | |
119 | const rows = position.split("/"); | |
120 | if (rows.length != V.size.x) return false; | |
b9ce3d0f | 121 | let kings = { "k": 0, "K": 0, 'l': 0, 'L': 0 }; |
90df90bc BA |
122 | for (let row of rows) { |
123 | let sumElts = 0; | |
124 | for (let i = 0; i < row.length; i++) { | |
b9ce3d0f | 125 | if (['K','k','L','l'].includes(row[i])) kings[row[i]]++; |
90df90bc BA |
126 | if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; |
127 | else { | |
128 | const num = parseInt(row[i]); | |
129 | if (isNaN(num)) return false; | |
130 | sumElts += num; | |
131 | } | |
132 | } | |
133 | if (sumElts != V.size.y) return false; | |
134 | } | |
b9ce3d0f BA |
135 | if (kings['k'] + kings['l'] != 1 || kings['K'] + kings['L'] != 1) |
136 | return false; | |
90df90bc BA |
137 | return true; |
138 | } | |
139 | ||
b9ce3d0f | 140 | static IsGoodFlags(flags) { |
5d75c82c BA |
141 | // 4 for Peach + Mario w, b |
142 | return !!flags.match(/^[01]{4,4}$/); | |
b9ce3d0f BA |
143 | } |
144 | ||
145 | setFlags(fenflags) { | |
b967d5ba | 146 | // King can send shell? Queen can be invisible? |
b9ce3d0f | 147 | this.powerFlags = { |
b967d5ba BA |
148 | w: [{ 'k': false, 'q': false }], |
149 | b: [{ 'k': false, 'q': false }] | |
b9ce3d0f | 150 | }; |
b9ce3d0f | 151 | for (let c of ["w", "b"]) { |
b967d5ba BA |
152 | for (let p of ['k', 'q']) { |
153 | this.powerFlags[c][p] = | |
154 | fenFlags.charAt((c == "w" ? 0 : 2) + (p == 'k' ? 0 : 1)) == "1"; | |
155 | } | |
b9ce3d0f BA |
156 | } |
157 | } | |
158 | ||
159 | aggregateFlags() { | |
5d75c82c | 160 | return this.powerFlags; |
b9ce3d0f BA |
161 | } |
162 | ||
163 | disaggregateFlags(flags) { | |
5d75c82c | 164 | this.powerFlags = flags; |
b9ce3d0f BA |
165 | } |
166 | ||
167 | getFen() { | |
168 | return super.getFen() + " " + this.getCapturedFen(); | |
169 | } | |
170 | ||
171 | getFenForRepeat() { | |
172 | return super.getFenForRepeat() + "_" + this.getCapturedFen(); | |
173 | } | |
174 | ||
175 | getCapturedFen() { | |
176 | let counts = [...Array(10).fill(0)]; | |
177 | let i = 0; | |
5333b500 | 178 | for (let p of [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.PAWN]) { |
b9ce3d0f BA |
179 | counts[i] = this.captured["w"][p]; |
180 | counts[5 + i] = this.captured["b"][p]; | |
181 | i++; | |
182 | } | |
183 | return counts.join(""); | |
184 | } | |
185 | ||
6c7cbfed | 186 | setOtherVariables(fen) { |
b9ce3d0f BA |
187 | const fenParsed = V.ParseFen(fen); |
188 | // Initialize captured pieces' counts from FEN | |
189 | this.captured = { | |
190 | w: { | |
191 | [V.ROOK]: parseInt(fenParsed.captured[0]), | |
192 | [V.KNIGHT]: parseInt(fenParsed.captured[1]), | |
193 | [V.BISHOP]: parseInt(fenParsed.captured[2]), | |
194 | [V.QUEEN]: parseInt(fenParsed.captured[3]), | |
195 | [V.PAWN]: parseInt(fenParsed.captured[4]), | |
196 | }, | |
197 | b: { | |
198 | [V.ROOK]: parseInt(fenParsed.captured[5]), | |
199 | [V.KNIGHT]: parseInt(fenParsed.captured[6]), | |
200 | [V.BISHOP]: parseInt(fenParsed.captured[7]), | |
201 | [V.QUEEN]: parseInt(fenParsed.captured[8]), | |
202 | [V.PAWN]: parseInt(fenParsed.captured[9]), | |
203 | } | |
204 | }; | |
b967d5ba | 205 | this.firstMove = []; |
6c7cbfed BA |
206 | this.subTurn = 1; |
207 | } | |
208 | ||
b9ce3d0f | 209 | getFlagsFen() { |
5d75c82c | 210 | let fen = ""; |
b9ce3d0f BA |
211 | // Add power flags |
212 | for (let c of ["w", "b"]) | |
b967d5ba | 213 | for (let p of ['k', 'q']) fen += (this.powerFlags[c][p] ? "1" : "0"); |
b9ce3d0f BA |
214 | return fen; |
215 | } | |
216 | ||
b967d5ba BA |
217 | static get RESERVE_PIECES() { |
218 | return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; | |
219 | } | |
220 | ||
221 | getReserveMoves([x, y]) { | |
222 | const color = this.turn; | |
223 | const p = V.RESERVE_PIECES[y]; | |
224 | if (this.reserve[color][p] == 0) return []; | |
225 | let moves = []; | |
226 | const start = (color == 'w' && p == V.PAWN ? 1 : 0); | |
227 | const end = (color == 'b' && p == V.PAWN ? 7 : 8); | |
228 | for (let i = start; i < end; i++) { | |
229 | for (let j = 0; j < V.size.y; j++) { | |
230 | if (this.board[i][j] == V.EMPTY) { | |
231 | let mv = new Move({ | |
232 | appear: [ | |
233 | new PiPo({ | |
234 | x: i, | |
235 | y: j, | |
236 | c: color, | |
237 | p: p | |
238 | }) | |
239 | ], | |
240 | vanish: [], | |
241 | start: { x: x, y: y }, //a bit artificial... | |
242 | end: { x: i, y: j } | |
243 | }); | |
244 | moves.push(mv); | |
245 | } | |
246 | } | |
247 | } | |
248 | return moves; | |
249 | } | |
250 | ||
251 | getPotentialMovesFrom([x, y]) { | |
252 | if (this.subTurn == 1) return super.getPotentialMovesFrom([x, y]); | |
6c7cbfed | 253 | if (this.subTurn == 2) { |
b967d5ba BA |
254 | let moves = []; |
255 | const L = this.firstMove.length; | |
256 | const fm = this.firstMove[L-1]; | |
257 | switch (fm.end.effect) { | |
258 | // case 0: a click is required (banana or bomb) | |
259 | case 1: | |
260 | // Exchange position with any piece | |
261 | for (let i=0; i<8; i++) { | |
262 | for (let j=0; j<8; j++) { | |
263 | const colIJ = this.getColor(i, j); | |
264 | if ( | |
265 | i != x && | |
266 | j != y && | |
267 | this.board[i][j] != V.EMPTY && | |
268 | colIJ != 'a' | |
269 | ) { | |
270 | const movedUnit = new PiPo({ | |
271 | x: x, | |
272 | y: y, | |
273 | c: colIJ, | |
274 | p: this.getPiece(i, j) | |
275 | }); | |
276 | let mMove = this.getBasicMove([x, y], [i, j]); | |
277 | mMove.appear.push(movedUnit); | |
278 | moves.push(mMove); | |
279 | } | |
280 | } | |
281 | } | |
282 | break; | |
283 | case 2: | |
284 | // Resurrect a captured piece | |
285 | if (x >= V.size.x) moves = this.getReserveMoves([x, y]); | |
286 | break; | |
287 | case 3: | |
288 | // Play again with the same piece | |
289 | if (fm.end.x == x && fm.end.y == y) | |
290 | moves = super.getPotentialMovesFrom([x, y]); | |
291 | break; | |
292 | } | |
293 | return moves; | |
6c7cbfed BA |
294 | } |
295 | } | |
296 | ||
b967d5ba BA |
297 | getBasicMove([x1, y1], [x2, y2], tr) { |
298 | // TODO: if this.subTurn == 2 :: no mushroom effect | |
299 | // (first, transformation. then:) | |
5d75c82c BA |
300 | // Apply mushroom, bomb or banana effect (hidden to the player). |
301 | // Determine egg effect, too, and apply its first part if possible. | |
302 | // add egg + add mushroom for pawns. | |
303 | let move = super.getBasicMove([x1, y1], [x2, y2]); | |
304 | // TODO | |
305 | return move; | |
306 | // Infer move type based on its effects (used to decide subTurn 1 --> 2) | |
307 | // --> impossible étant donné juste first part (egg --> effect?) | |
308 | // => stocker l'effet (i, ii ou iii) dans le coup directement, | |
b967d5ba | 309 | // Pas terrible, mais y'aura pas 36 variantes comme ça. Disons end.effect == 0, 1, 2 ou 3 |
5d75c82c BA |
310 | // 0 => tour ou fou, pose potentielle. |
311 | // If queen can be invisible, add move same start + end but final type changes | |
312 | } | |
313 | ||
b967d5ba BA |
314 | getEnpassantCaptures([x, y], shiftX) { |
315 | const Lep = this.epSquares.length; | |
316 | const epSquare = this.epSquares[Lep - 1]; //always at least one element | |
317 | let enpassantMove = null; | |
318 | if ( | |
319 | !!epSquare && | |
320 | epSquare.x == x + shiftX && | |
321 | Math.abs(epSquare.y - y) == 1 | |
322 | ) { | |
323 | // Not using this.getBasicMove() because the mushroom has no effect | |
324 | enpassantMove = super.getBasicMove([x, y], [epSquare.x, epSquare.y]); | |
325 | enpassantMove.vanish.push({ | |
326 | x: x, | |
327 | y: epSquare.y, | |
328 | p: V.PAWN, | |
329 | c: this.getColor(x, epSquare.y) | |
330 | }); | |
331 | } | |
332 | return !!enpassantMove ? [enpassantMove] : []; | |
333 | } | |
334 | ||
335 | getPotentialQueenMoves(sq) { | |
336 | const normalMoves = super.getPotentialQueenMoves(sq); | |
337 | // If flag allows it, add 'invisible movements' | |
338 | let invisibleMoves = []; | |
339 | if (this.powerFlags[this.turn][V.QUEEN]) { | |
340 | normalMoves.forEach(m => { | |
341 | if (m.vanish.length == 1) { | |
342 | let im = JSON.parse(JSON.stringify(m)); | |
343 | m.appear[0].p = V.INVISIBLE_QUEEN; | |
344 | invisibleMoves.push(im); | |
345 | } | |
346 | }); | |
347 | } | |
348 | return normalMoves.concat(invisibleMoves); | |
349 | } | |
350 | ||
5d75c82c BA |
351 | getPotentialKingMoves([x, y]) { |
352 | let moves = super.getPotentialKingMoves([x, y]); | |
b967d5ba BA |
353 | const color = this.turn; |
354 | // If flag allows it, add 'remote shell captures' | |
355 | if (this.powerFlags[this.turn][V.KING]) { | |
356 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => { | |
357 | let [i, j] = [x + 2 * step[0], y + 2 * step[1]]; | |
358 | while ( | |
359 | V.OnBoard(i, j) && | |
360 | ( | |
361 | this.board[i][j] == V.EMPTY || | |
362 | ( | |
363 | this.getColor(i, j) == 'a' && | |
364 | [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) | |
365 | ) | |
366 | ) | |
367 | ) { | |
368 | i += step[0]; | |
369 | j += step[1]; | |
370 | } | |
371 | if (V.OnBoard(i, j) && this.getColor(i, j) != color) | |
372 | // May just destroy a bomb or banana: | |
373 | moves.push(this.getBasicMove([x, y], [i, j])); | |
374 | }); | |
375 | } | |
5d75c82c BA |
376 | return moves; |
377 | } | |
378 | ||
379 | getSlideNJumpMoves([x, y], steps, oneStep) { | |
380 | let moves = []; | |
381 | outerLoop: for (let step of steps) { | |
382 | let i = x + step[0]; | |
383 | let j = y + step[1]; | |
384 | while ( | |
385 | V.OnBoard(i, j) && | |
386 | ( | |
387 | this.board[i][j] == V.EMPTY || | |
388 | ( | |
389 | this.getColor(i, j) == 'a' && | |
390 | [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) | |
391 | ) | |
392 | ) | |
393 | ) { | |
394 | moves.push(this.getBasicMove([x, y], [i, j])); | |
395 | if (oneStep) continue outerLoop; | |
396 | i += step[0]; | |
397 | j += step[1]; | |
398 | } | |
399 | if (V.OnBoard(i, j) && this.canTake([x, y], [i, j])) | |
400 | moves.push(this.getBasicMove([x, y], [i, j])); | |
401 | } | |
402 | return moves; | |
90df90bc BA |
403 | } |
404 | ||
ad030c7d | 405 | getAllPotentialMoves() { |
5d75c82c | 406 | if (this.subTurn == 1) return super.getAllPotentialMoves(); |
b967d5ba BA |
407 | let moves = []; |
408 | const L = this.firstMove.length; | |
409 | const fm = this.firstMove[L-1]; | |
410 | //switch (fm.end.effect) { | |
411 | // case 0: //... | |
6c7cbfed BA |
412 | } |
413 | ||
1c15969e | 414 | doClick(square) { |
b967d5ba | 415 | // TODO: if click on x, y (piece), then return empty move same as Dynamo |
107dc1bd | 416 | if (isNaN(square[0])) return null; |
5333b500 | 417 | // TODO: If subTurn == 2: |
1c15969e | 418 | // if square is empty && firstMove is compatible, |
5333b500 | 419 | // complete the move (banana or bomb or piece exchange). |
1c15969e BA |
420 | // if square not empty, just complete with empty move |
421 | const Lf = this.firstMove.length; | |
422 | if (this.subTurn == 2) { | |
423 | if ( | |
424 | this.board[square[0]][square[1]] == V.EMPTY && | |
1c15969e BA |
425 | (La == 0 || !this.oppositeMoves(this.amoves[La-1], this.firstMove[Lf-1])) |
426 | ) { | |
427 | return { | |
428 | start: { x: -1, y: -1 }, | |
429 | end: { x: -1, y: -1 }, | |
430 | appear: [], | |
431 | vanish: [] | |
432 | }; | |
433 | } | |
434 | } | |
435 | return null; | |
436 | } | |
437 | ||
5d75c82c | 438 | play(move) { |
b967d5ba BA |
439 | move.flags = JSON.stringify(this.aggregateFlags()); |
440 | this.epSquares.push(this.getEpSquare(move)); | |
441 | V.PlayOnBoard(this.board, move); | |
442 | if (move.end.effect !== undefined) { | |
443 | this.firstMove.push(move); | |
444 | this.subTurn = 2; | |
445 | if (move.end.effect == 2) this.reserve = this.captured; | |
446 | } | |
447 | else { | |
448 | this.turn = V.GetOppCol(this.turn); | |
449 | this.subTurn = 1; | |
450 | this.reserve = null; | |
451 | } | |
1c15969e | 452 | } |
5d75c82c | 453 | |
b9ce3d0f | 454 | postPlay(move) { |
b967d5ba BA |
455 | if (move.vanish[0].p == V.KING) { } |
456 | //si roi et delta >= 2 ou dame et appear invisible queen : turn flag off | |
5d75c82c | 457 | if (move.vanish.length == 2 && move.vanish[1].c != 'a') |
b9ce3d0f BA |
458 | // Capture: update this.captured |
459 | this.captured[move.vanish[1].c][move.vanish[1].p]++; | |
5d75c82c BA |
460 | else if (move.vanish.length == 0) { |
461 | // A piece is back on board | |
462 | this.captured[move.vanish[1].c][move.vanish[1].p]++; | |
463 | this.reserve = null; | |
464 | } | |
465 | // si pièce immobilisée de ma couleur : elle redevient utilisable (changer status fin de play) | |
466 | // TODO: un-immobilize my formerly immobilized piece, if any. | |
467 | // Make invisible queen visible again, if any opponent invisible queen. | |
468 | } | |
469 | ||
470 | undo(move) { | |
471 | // TODO: should be easy once end.effect is set in getBasicMove() | |
b967d5ba BA |
472 | if (move.end.effect !== undefined) |
473 | this.firstMove.pop(); | |
b9ce3d0f BA |
474 | } |
475 | ||
476 | postUndo(move) { | |
5d75c82c | 477 | if (move.vanish.length == 2 && move.vanish[1].c != 'a') |
b9ce3d0f BA |
478 | this.captured[move.vanish[1].c][move.vanish[1].p]--; |
479 | } | |
1c15969e | 480 | |
5d75c82c BA |
481 | getCheckSquares() { |
482 | return []; | |
483 | } | |
484 | ||
6c7cbfed | 485 | getCurrentScore() { |
5d75c82c BA |
486 | // Find kings (not tracked in this variant) |
487 | let kingThere = { w: false, b: false }; | |
488 | for (let i=0; i<8; i++) { | |
489 | for (let j=0; j<8; j++) { | |
490 | if (this.board[i][j] != V.EMPTY && this.getPiece(i, j) == V.KING) | |
491 | kingThere[this.getColor(i, j)] = true; | |
492 | } | |
493 | } | |
494 | if (!kingThere['w']) return "0-1"; | |
495 | if (!kingThere['b']) return "1-0"; | |
496 | return "*"; | |
6c7cbfed BA |
497 | } |
498 | ||
b9ce3d0f BA |
499 | static GenRandInitFen(randomness) { |
500 | return ( | |
5d75c82c | 501 | SuicideRules.GenRandInitFen(randomness).slice(0, -1) + |
b9ce3d0f BA |
502 | // Add Peach + Mario flags, re-add en-passant + capture counts |
503 | "0000 - 0000000000" | |
504 | ); | |
505 | } | |
506 | ||
5d75c82c BA |
507 | filterValid(moves) { |
508 | return moves; | |
509 | } | |
510 | ||
1c15969e | 511 | getComputerMove() { |
b967d5ba | 512 | // Random mover: |
5d75c82c | 513 | const moves = this.getAllValidMoves(); |
b967d5ba BA |
514 | let move1 = moves[randInt(movs.length)]; |
515 | this.play(move1); | |
516 | let move2 = undefined; | |
517 | if (this.subTurn == 2) { | |
518 | const moves2 = this.getAllValidMoves(); | |
519 | move2 = moves2[randInt(moves2.length)]; | |
520 | } | |
521 | this.undo(move1); | |
522 | if (!move2) return move1; | |
523 | return [move1, move2]; | |
1c15969e | 524 | } |
b9ce3d0f BA |
525 | |
526 | getNotation(move) { | |
b967d5ba BA |
527 | // TODO: invisibility used => move notation Q?? |
528 | // Also, bonus should be clearly indicated + bomb/bananas locations | |
529 | return super.getNotation(move); | |
b9ce3d0f | 530 | } |
6c7cbfed | 531 | }; |