Commit | Line | Data |
---|---|---|
2b9b90da | 1 | import ChessRules from "/base_rules"; |
f382c57b | 2 | import GiveawayRules from "/variants/Giveaway"; |
8f57fbf2 BA |
3 | import { ArrayFun } from "/utils/array.js"; |
4 | import { Random } from "/utils/alea.js"; | |
5 | import PiPo from "/utils/PiPo.js"; | |
6 | import Move from "/utils/Move.js"; | |
f382c57b | 7 | |
f8b43ef7 BA |
8 | export class ChakartRules extends ChessRules { |
9 | ||
10 | static get Options() { | |
11 | return { | |
12 | select: [ | |
13 | { | |
14 | label: "Randomness", | |
15 | variable: "randomness", | |
16 | defaut: 2, | |
17 | options: [ | |
37481d1e BA |
18 | {label: "Deterministic", value: 0}, |
19 | {label: "Symmetric random", value: 1}, | |
20 | {label: "Asymmetric random", value: 2} | |
f8b43ef7 BA |
21 | ] |
22 | } | |
23 | ] | |
24 | }; | |
25 | } | |
26 | ||
8f57fbf2 BA |
27 | get pawnPromotions() { |
28 | return ['q', 'r', 'n', 'b', 'k']; | |
f8b43ef7 | 29 | } |
8f57fbf2 | 30 | |
2b9b90da | 31 | get hasCastle() { |
f8b43ef7 BA |
32 | return false; |
33 | } | |
2b9b90da | 34 | get hasEnpassant() { |
f8b43ef7 BA |
35 | return false; |
36 | } | |
37 | ||
f8b43ef7 BA |
38 | static get IMMOBILIZE_CODE() { |
39 | return { | |
40 | 'p': 's', | |
41 | 'r': 'u', | |
42 | 'n': 'o', | |
43 | 'b': 'c', | |
44 | 'q': 't', | |
45 | 'k': 'l' | |
46 | }; | |
47 | } | |
48 | ||
49 | static get IMMOBILIZE_DECODE() { | |
50 | return { | |
51 | 's': 'p', | |
52 | 'u': 'r', | |
53 | 'o': 'n', | |
54 | 'c': 'b', | |
55 | 't': 'q', | |
56 | 'l': 'k' | |
57 | }; | |
58 | } | |
59 | ||
60 | static get INVISIBLE_QUEEN() { | |
61 | return 'i'; | |
62 | } | |
63 | ||
64 | // Fictive color 'a', bomb banana mushroom egg | |
65 | static get BOMB() { | |
66 | return 'w'; //"Wario" | |
67 | } | |
68 | static get BANANA() { | |
69 | return 'd'; //"Donkey" | |
70 | } | |
71 | static get EGG() { | |
72 | return 'e'; | |
73 | } | |
74 | static get MUSHROOM() { | |
75 | return 'm'; | |
76 | } | |
77 | ||
91339921 BA |
78 | genRandInitFen(seed) { |
79 | const gr = new GiveawayRules({mode: "suicide"}, true); | |
80 | return ( | |
81 | gr.genRandInitFen(seed).slice(0, -1) + | |
82 | // Add Peach + Mario flags + capture counts | |
7562d2c2 | 83 | '{"flags":"1111","ccount":"000000000000"}' |
91339921 BA |
84 | ); |
85 | } | |
86 | ||
8f57fbf2 | 87 | fen2board(f) { |
f8b43ef7 BA |
88 | return ( |
89 | f.charCodeAt() <= 90 | |
90 | ? "w" + f.toLowerCase() | |
91 | : (['w', 'd', 'e', 'm'].includes(f) ? "a" : "b") + f | |
92 | ); | |
93 | } | |
94 | ||
f8b43ef7 BA |
95 | setFlags(fenflags) { |
96 | // King can send shell? Queen can be invisible? | |
97 | this.powerFlags = { | |
8f57fbf2 BA |
98 | w: {k: false, q: false}, |
99 | b: {k: false, q: false} | |
f8b43ef7 | 100 | }; |
8f57fbf2 | 101 | for (let c of ['w', 'b']) { |
f8b43ef7 BA |
102 | for (let p of ['k', 'q']) { |
103 | this.powerFlags[c][p] = | |
104 | fenflags.charAt((c == "w" ? 0 : 2) + (p == 'k' ? 0 : 1)) == "1"; | |
105 | } | |
106 | } | |
107 | } | |
108 | ||
109 | aggregateFlags() { | |
110 | return this.powerFlags; | |
111 | } | |
112 | ||
113 | disaggregateFlags(flags) { | |
114 | this.powerFlags = flags; | |
115 | } | |
116 | ||
117 | getFen() { | |
118 | return super.getFen() + " " + this.getCapturedFen(); | |
119 | } | |
120 | ||
91339921 BA |
121 | getFlagsFen() { |
122 | return ['w', 'b'].map(c => { | |
123 | return ['k', 'q'].map(p => this.powerFlags[c][p] ? "1" : "0").join(""); | |
124 | }).join(""); | |
125 | } | |
126 | ||
f8b43ef7 | 127 | getCapturedFen() { |
8f57fbf2 BA |
128 | const res = ['w', 'b'].map(c => { |
129 | Object.values(this.captured[c]) | |
130 | }); | |
131 | return res[0].concat(res[1]).join(""); | |
f8b43ef7 BA |
132 | } |
133 | ||
8f57fbf2 BA |
134 | setOtherVariables(fenParsed) { |
135 | super.setOtherVariables(fenParsed); | |
f8b43ef7 | 136 | // Initialize captured pieces' counts from FEN |
8f57fbf2 BA |
137 | const allCapts = fenParsed.captured.split("").map(x => parseInt(x, 10)); |
138 | const pieces = ['p', 'r', 'n', 'b', 'q', 'k']; | |
f8b43ef7 | 139 | this.captured = { |
8f57fbf2 BA |
140 | w: Array.toObject(pieces, allCapts.slice(0, 6)), |
141 | b: Array.toObject(pieces, allCapts.slice(6, 12)) | |
f8b43ef7 | 142 | }; |
91339921 | 143 | this.reserve = { w: {}, b: {} }; //to be replaced by this.captured |
b0cf998b | 144 | this.moveStack = []; |
f8b43ef7 BA |
145 | } |
146 | ||
c7c2f41c | 147 | // For Toadette bonus |
91339921 | 148 | getDropMovesFrom([c, p]) { |
be3cb9d1 | 149 | if (typeof c != "string" || this.reserve[c][p] == 0) |
c7c2f41c | 150 | return []; |
f8b43ef7 | 151 | let moves = []; |
91339921 BA |
152 | const start = (c == 'w' && p == 'p' ? 1 : 0); |
153 | const end = (color == 'b' && p == 'p' ? 7 : 8); | |
f8b43ef7 | 154 | for (let i = start; i < end; i++) { |
91339921 BA |
155 | for (let j = 0; j < this.size.y; j++) { |
156 | const pieceIJ = this.getPiece(i, j); | |
f8b43ef7 | 157 | if ( |
91339921 | 158 | this.board[i][j] == "" || |
f8b43ef7 | 159 | this.getColor(i, j) == 'a' || |
91339921 | 160 | pieceIJ == V.INVISIBLE_QUEEN |
f8b43ef7 | 161 | ) { |
91339921 BA |
162 | let m = new Move({ |
163 | start: {x: c, y: p}, | |
164 | end: {x: i, y: j}, | |
165 | appear: [new PiPo({x: i, y: j, c: c, p: p})], | |
166 | vanish: [] | |
167 | }); | |
168 | // A drop move may remove a bonus (or hidden queen!) | |
169 | if (this.board[i][j] != "") | |
170 | m.vanish.push(new PiPo({x: i, y: j, c: 'a', p: pieceIJ})); | |
f8b43ef7 BA |
171 | moves.push(m); |
172 | } | |
173 | } | |
174 | } | |
175 | return moves; | |
176 | } | |
177 | ||
be3cb9d1 BA |
178 | // Moving something. Potential effects resolved after playing |
179 | getPotentialMovesFrom([x, y], bonus) { | |
f8b43ef7 | 180 | let moves = []; |
be3cb9d1 BA |
181 | if (bonus == "toadette") |
182 | return this.getDropMovesFrom([x, y]); | |
7562d2c2 | 183 | if (bonus == "kingboo") { |
37481d1e BA |
184 | const initPiece = this.getPiece(x, y); |
185 | const color = this.getColor(x, y); | |
186 | const oppCol = C.GetOppCol(color); | |
187 | // Only allow to swap pieces (TODO: restrict for pawns) | |
188 | for (let i=0; i<this.size.x; i++) { | |
189 | for (let j=0; j<this.size.y; j++) { | |
190 | if ((i != x || j != y) && this.board[i][j] != "") { | |
37481d1e BA |
191 | let m = this.getBasicMove([x, y], [i, j]); |
192 | m.appear.push( | |
193 | new PiPo({x: x, y: y, p: this.getPiece(i, j), c: oppCol})); | |
194 | moves.push(m); | |
195 | } | |
196 | } | |
197 | } | |
be3cb9d1 BA |
198 | return moves; |
199 | } | |
200 | // Normal case (including bonus daisy) | |
b0cf998b BA |
201 | switch (this.getPiece(x, y)) { |
202 | case 'p': | |
203 | moves = this.getPawnMovesFrom([x, y]); //apply promotions | |
204 | break; | |
205 | case 'q': | |
206 | moves = this.getQueenMovesFrom([x, y]); | |
207 | break; | |
208 | case 'k', | |
37481d1e BA |
209 | moves = this.getKingMovesFrom([x, y]); |
210 | break; | |
211 | case 'n': | |
7562d2c2 | 212 | moves = this.getKnightMovesFrom([x, y]); |
37481d1e | 213 | break; |
7562d2c2 BA |
214 | case 'b': |
215 | case 'r': | |
216 | // explicitely listing types to avoid moving immobilized piece | |
b0cf998b BA |
217 | moves = super.getPotentialMovesFrom([x, y]); |
218 | } | |
219 | return moves; | |
220 | } | |
221 | ||
37481d1e | 222 | getPawnMovesFrom([x, y]) { |
f8b43ef7 | 223 | const color = this.turn; |
be3cb9d1 BA |
224 | const oppCol = C.GetOppCol(color); |
225 | const shiftX = (color == 'w' ? -1 : 1); | |
226 | const firstRank = (color == "w" ? this.size.x - 1 : 0); | |
f8b43ef7 BA |
227 | let moves = []; |
228 | if ( | |
be3cb9d1 | 229 | this.board[x + shiftX][y] == "" || |
f8b43ef7 BA |
230 | this.getColor(x + shiftX, y) == 'a' || |
231 | this.getPiece(x + shiftX, y) == V.INVISIBLE_QUEEN | |
232 | ) { | |
7562d2c2 | 233 | moves.push(this.getBasicMove([x, y], [x + shiftX, y])); |
f8b43ef7 BA |
234 | if ( |
235 | [firstRank, firstRank + shiftX].includes(x) && | |
236 | ( | |
7562d2c2 | 237 | this.board[x + 2 * shiftX][y] == "" || |
f8b43ef7 BA |
238 | this.getColor(x + 2 * shiftX, y) == 'a' || |
239 | this.getPiece(x + 2 * shiftX, y) == V.INVISIBLE_QUEEN | |
240 | ) | |
241 | ) { | |
7562d2c2 | 242 | moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); |
f8b43ef7 BA |
243 | } |
244 | } | |
245 | for (let shiftY of [-1, 1]) { | |
246 | if ( | |
247 | y + shiftY >= 0 && | |
248 | y + shiftY < sizeY && | |
7562d2c2 | 249 | this.board[x + shiftX][y + shiftY] != "" && |
f8b43ef7 BA |
250 | // Pawns cannot capture invisible queen this way! |
251 | this.getPiece(x + shiftX, y + shiftY) != V.INVISIBLE_QUEEN && | |
252 | ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY)) | |
253 | ) { | |
7562d2c2 | 254 | moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY])); |
f8b43ef7 BA |
255 | } |
256 | } | |
7562d2c2 | 257 | super.pawnPostProcess(moves, color, oppCol); |
f8b43ef7 BA |
258 | return moves; |
259 | } | |
260 | ||
37481d1e | 261 | getQueenMovesFrom(sq) { |
7562d2c2 | 262 | const normalMoves = super.getPotentialMovesOf('q', sq); |
f8b43ef7 BA |
263 | // If flag allows it, add 'invisible movements' |
264 | let invisibleMoves = []; | |
7562d2c2 | 265 | if (this.powerFlags[this.turn]['q']) { |
f8b43ef7 BA |
266 | normalMoves.forEach(m => { |
267 | if ( | |
268 | m.appear.length == 1 && | |
269 | m.vanish.length == 1 && | |
270 | // Only simple non-capturing moves: | |
271 | m.vanish[0].c != 'a' | |
272 | ) { | |
273 | let im = JSON.parse(JSON.stringify(m)); | |
274 | im.appear[0].p = V.INVISIBLE_QUEEN; | |
275 | im.end.noHighlight = true; | |
276 | invisibleMoves.push(im); | |
277 | } | |
278 | }); | |
279 | } | |
280 | return normalMoves.concat(invisibleMoves); | |
281 | } | |
282 | ||
37481d1e | 283 | getKingMovesFrom([x, y]) { |
7562d2c2 | 284 | let moves = super.getPotentialMovesOf('k', [x, y]); |
f8b43ef7 | 285 | // If flag allows it, add 'remote shell captures' |
7562d2c2 BA |
286 | if (this.powerFlags[this.turn]['k']) { |
287 | super.pieces()['k'].moves[0].steps.forEach(step => { | |
f8b43ef7 BA |
288 | let [i, j] = [x + step[0], y + step[1]]; |
289 | while ( | |
7562d2c2 | 290 | this.onBoard(i, j) && |
f8b43ef7 | 291 | ( |
7562d2c2 | 292 | this.board[i][j] == "" || |
f8b43ef7 BA |
293 | this.getPiece(i, j) == V.INVISIBLE_QUEEN || |
294 | ( | |
295 | this.getColor(i, j) == 'a' && | |
296 | [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) | |
297 | ) | |
298 | ) | |
299 | ) { | |
300 | i += step[0]; | |
301 | j += step[1]; | |
302 | } | |
7562d2c2 | 303 | if (this.onBoard(i, j)) { |
f8b43ef7 | 304 | const colIJ = this.getColor(i, j); |
7562d2c2 | 305 | if (colIJ != this.turn) { |
f8b43ef7 BA |
306 | // May just destroy a bomb or banana: |
307 | moves.push( | |
308 | new Move({ | |
7562d2c2 BA |
309 | start: {x: x, y: y}, |
310 | end: {x: i, y: j}, | |
f8b43ef7 BA |
311 | appear: [], |
312 | vanish: [ | |
7562d2c2 | 313 | new PiPo({x: i, y: j, c: colIJ, p: this.getPiece(i, j)}) |
f8b43ef7 BA |
314 | ] |
315 | }) | |
316 | ); | |
317 | } | |
318 | } | |
319 | }); | |
320 | } | |
321 | return moves; | |
322 | } | |
323 | ||
7562d2c2 BA |
324 | getKnightMovesFrom([x, y]) { |
325 | // Add egg on initial square: | |
326 | return super.getPotentialMovesOf('n', [x, y]).map(m => { | |
327 | m.appear.push(new PiPo({p: "e", c: "a", x: x, y: y})); | |
328 | return m; | |
329 | }); | |
330 | } | |
331 | ||
b0cf998b BA |
332 | /// if any of my pieces was immobilized, it's not anymore. |
333 | //if play set a piece immobilized, then mark it | |
7562d2c2 BA |
334 | play(move) { |
335 | if (move.effect == "toadette") { | |
91339921 | 336 | this.reserve = this.captured; |
7562d2c2 BA |
337 | this.re_drawReserve([this.turn]); |
338 | } | |
339 | else if (this.reserve) { | |
340 | this.reserve = { w: {}, b: {} }; | |
341 | this.re_drawReserve([this.turn]); | |
342 | } | |
91339921 | 343 | const color = this.turn; |
f8b43ef7 BA |
344 | if ( |
345 | move.vanish.length == 2 && | |
346 | move.vanish[1].c != 'a' && | |
347 | move.appear.length == 1 //avoid king Boo! | |
348 | ) { | |
349 | // Capture: update this.captured | |
350 | let capturedPiece = move.vanish[1].p; | |
91339921 BA |
351 | if (capturedPiece == V.INVISIBLE_QUEEN) |
352 | capturedPiece = V.QUEEN; | |
f8b43ef7 BA |
353 | else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece)) |
354 | capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece]; | |
355 | this.captured[move.vanish[1].c][capturedPiece]++; | |
356 | } | |
357 | else if (move.vanish.length == 0) { | |
7562d2c2 BA |
358 | if (move.appear.length == 0 || move.appear[0].c == 'a') |
359 | return; | |
f8b43ef7 BA |
360 | // A piece is back on board |
361 | this.captured[move.appear[0].c][move.appear[0].p]--; | |
362 | } | |
363 | if (move.appear.length == 0) { | |
364 | // Three cases: king "shell capture", Chomp or Koopa | |
365 | if (this.getPiece(move.start.x, move.start.y) == V.KING) | |
366 | // King remote capture: | |
367 | this.powerFlags[color][V.KING] = false; | |
368 | else if (move.end.effect == "chomp") | |
369 | this.captured[color][move.vanish[0].p]++; | |
370 | } | |
371 | else if (move.appear[0].p == V.INVISIBLE_QUEEN) | |
372 | this.powerFlags[move.appear[0].c][V.QUEEN] = false; | |
373 | if (this.subTurn == 2) return; | |
374 | if ( | |
375 | move.turn[1] == 1 && | |
376 | move.appear.length == 0 || | |
377 | !(Object.keys(V.IMMOBILIZE_DECODE).includes(move.appear[0].p)) | |
378 | ) { | |
379 | // Look for an immobilized piece of my color: it can now move | |
380 | for (let i=0; i<8; i++) { | |
381 | for (let j=0; j<8; j++) { | |
382 | if (this.board[i][j] != V.EMPTY) { | |
383 | const piece = this.getPiece(i, j); | |
384 | if ( | |
385 | this.getColor(i, j) == color && | |
386 | Object.keys(V.IMMOBILIZE_DECODE).includes(piece) | |
387 | ) { | |
388 | this.board[i][j] = color + V.IMMOBILIZE_DECODE[piece]; | |
389 | move.wasImmobilized = [i, j]; | |
390 | } | |
391 | } | |
392 | } | |
393 | } | |
394 | } | |
395 | // Also make opponent invisible queen visible again, if any | |
396 | const oppCol = V.GetOppCol(color); | |
397 | for (let i=0; i<8; i++) { | |
398 | for (let j=0; j<8; j++) { | |
399 | if ( | |
400 | this.board[i][j] != V.EMPTY && | |
401 | this.getColor(i, j) == oppCol && | |
402 | this.getPiece(i, j) == V.INVISIBLE_QUEEN | |
403 | ) { | |
404 | this.board[i][j] = oppCol + V.QUEEN; | |
405 | move.wasInvisible = [i, j]; | |
406 | } | |
407 | } | |
408 | } | |
91339921 BA |
409 | this.playOnBoard(move); |
410 | if (["kingboo", "toadette", "daisy"].includes(move.effect)) { | |
411 | this.effect = move.effect; | |
412 | this.subTurn = 2; | |
f8b43ef7 | 413 | } |
91339921 BA |
414 | else { |
415 | this.turn = C.GetOppCol(this.turn); | |
416 | this.movesCount++; | |
417 | this.subTurn = 1; | |
f8b43ef7 | 418 | } |
f8b43ef7 BA |
419 | } |
420 | ||
421 | filterValid(moves) { | |
422 | return moves; | |
423 | } | |
424 | ||
37481d1e BA |
425 | // idée : on joue le coup, puis son effet est déterminé, puis la suite (si suite) |
426 | // est jouée automatiquement ou demande action utilisateur, etc jusqu'à coup terminal. | |
427 | tryMoveFollowup(move, cb) { | |
428 | if (this.getColor(move.end.x, move.end.y) == 'a') { | |
429 | // effect, or bonus/malus | |
430 | const endType = this.getPiece(m.end.x, m.end.y); | |
431 | switch (endType) { | |
432 | case V.EGG: | |
433 | this.applyRandomBonus(move, cb); | |
434 | break; | |
435 | case V.BANANA: | |
436 | case V.BOMB: { | |
437 | const dest = | |
438 | this.getRandomSquare([m.end.x, m.end.y], | |
439 | endType == V.BANANA | |
440 | ? [[1, 1], [1, -1], [-1, 1], [-1, -1]] | |
441 | : [[1, 0], [-1, 0], [0, 1], [0, -1]]); | |
442 | const nextMove = this.getBasicMove([move.end.x, move.end.y], dest); | |
443 | cb(nextMove); | |
444 | break; | |
445 | } | |
446 | case V.MUSHROOM: | |
447 | // aller dans direction, saut par dessus pièce adverse | |
448 | // ou amie (tjours), new step si roi caval pion | |
449 | break; | |
450 | } | |
451 | } | |
452 | } | |
453 | ||
7562d2c2 BA |
454 | playVisual(move, r) { |
455 | super.playVisual(move, r); | |
456 | if (move.bonus) | |
457 | alert(move.bonus); //TODO: nicer display | |
458 | } | |
459 | ||
460 | applyRandomBonus(move, cb) { | |
37481d1e BA |
461 | // TODO: determine bonus/malus, and then |
462 | } | |
463 | ||
464 | // Helper to apply banana/bomb effect | |
465 | getRandomSquare([x, y], steps) { | |
466 | const validSteps = steps.filter(s => this.onBoard(x + s[0], y + s[1])); | |
467 | const step = validSteps[Random.randInt(validSteps.length)]; | |
468 | return [x + step[0], y + step[1]]; | |
469 | } | |
7562d2c2 BA |
470 | |
471 | // Warning: if play() is called, then move.end changed. | |
be3cb9d1 | 472 | playPlusVisual(move, r) { |
37481d1e | 473 | this.moveStack.push(move); |
be3cb9d1 BA |
474 | this.play(move); |
475 | this.playVisual(move, r); | |
37481d1e BA |
476 | this.tryMoveFollowup(move, (nextMove) => { |
477 | if (nextMove) | |
478 | this.playPlusVisual(nextMove, r); | |
479 | else | |
480 | this.afterPlay(this.moveStack); | |
481 | }); | |
be3cb9d1 | 482 | } |
f8b43ef7 BA |
483 | |
484 | }; |