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