1 import ChessRules
from "/base_rules.js";
2 import GiveawayRules
from "/variants/Giveaway/class.js";
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";
8 export default class ChakartRules
extends ChessRules
{
10 static get Options() {
15 variable: "randomness",
18 {label: "Deterministic", value: 0},
19 {label: "Symmetric random", value: 1},
20 {label: "Asymmetric random", value: 2}
28 get pawnPromotions() {
29 return ['q', 'r', 'n', 'b', 'k'];
45 static get IMMOBILIZE_CODE() {
56 static get IMMOBILIZE_DECODE() {
67 static get INVISIBLE_QUEEN() {
71 // Fictive color 'a', bomb banana mushroom egg
76 return 'd'; //"Donkey"
81 static get MUSHROOM() {
85 static get EGG_SURPRISE() {
87 "kingboo", "bowser", "daisy", "koopa",
88 "luigi", "waluigi", "toadette", "chomp"];
93 this.playerColor
!= this.turn
||
94 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(this.getPiece(x
, y
))
98 return this.egg
== "kingboo" || this.getColor(x
, y
) == this.turn
;
101 pieces(color
, x
, y
) {
103 'i': {"class": "invisible"}, //queen
104 'e': {"class": "egg"},
105 'm': {"class": "mushroom"},
106 'd': {"class": "banana"},
107 'w': {"class": "bomb"},
108 'z': {"class": "remote-capture"}
111 's': {"class": ["immobilized", "pawn"]},
112 'u': {"class": ["immobilized", "rook"]},
113 'o': {"class": ["immobilized", "knight"]},
114 'c': {"class": ["immobilized", "bishop"]},
115 't': {"class": ["immobilized", "queen"]},
116 'l': {"class": ["immobilized", "king"]}
118 return Object
.assign({}, specials
, bowsered
, super.pieces(color
, x
, y
));
121 genRandInitFen(seed
) {
122 const gr
= new GiveawayRules(
123 {mode: "suicide", options: {}, genFenOnly: true});
124 // Add Peach + mario flags
125 return gr
.genRandInitFen(seed
).slice(0, -17) + '{"flags":"1111"}';
131 ? "w" + f
.toLowerCase()
132 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
137 // King can send shell? Queen can be invisible?
139 w: {k: false, q: false},
140 b: {k: false, q: false}
142 for (let c
of ['w', 'b']) {
143 for (let p
of ['k', 'q']) {
144 this.powerFlags
[c
][p
] =
145 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
151 return this.powerFlags
;
154 disaggregateFlags(flags
) {
155 this.powerFlags
= flags
;
159 return ['w', 'b'].map(c
=> {
160 return ['k', 'q'].map(p
=> this.powerFlags
[c
][p
] ? "1" : "0").join("");
164 setOtherVariables(fenParsed
) {
165 this.setFlags(fenParsed
.flags
);
166 this.reserve
= {}; //to be filled later
169 // Change seed (after FEN generation!!)
170 // so that further calls differ between players:
171 Random
.setSeed(Math
.floor(10000 * Math
.random()));
174 // For Toadette bonus
175 getDropMovesFrom([c
, p
]) {
176 if (typeof c
!= "string" || this.reserve
[c
][p
] == 0)
179 const start
= (c
== 'w' && p
== 'p' ? 1 : 0);
180 const end
= (c
== 'b' && p
== 'p' ? 7 : 8);
181 for (let i
= start
; i
< end
; i
++) {
182 for (let j
= 0; j
< this.size
.y
; j
++) {
183 const pieceIJ
= this.getPiece(i
, j
);
184 const colIJ
= this.getColor(i
, j
);
186 this.board
[i
][j
] == "" ||
188 pieceIJ
== V
.INVISIBLE_QUEEN
192 appear: [new PiPo({x: i
, y: j
, c: c
, p: p
})],
195 // A drop move may remove a bonus (or hidden queen!)
196 if (this.board
[i
][j
] != "")
197 m
.vanish
.push(new PiPo({x: i
, y: j
, c: colIJ
, p: pieceIJ
}));
205 getPotentialMovesFrom([x
, y
]) {
207 const piece
= this.getPiece(x
, y
);
208 if (this.egg
== "toadette")
209 moves
= this.getDropMovesFrom([x
, y
]);
210 else if (this.egg
== "kingboo") {
211 const color
= this.turn
;
212 const oppCol
= C
.GetOppCol(color
);
213 // Only allow to swap (non-immobilized!) pieces
214 for (let i
=0; i
<this.size
.x
; i
++) {
215 for (let j
=0; j
<this.size
.y
; j
++) {
216 const colIJ
= this.getColor(i
, j
);
217 const pieceIJ
= this.getPiece(i
, j
);
219 (i
!= x
|| j
!= y
) &&
220 ['w', 'b'].includes(colIJ
) &&
221 !Object
.keys(V
.IMMOBILIZE_DECODE
).includes(pieceIJ
) &&
222 // Next conditions = no pawn on last rank
226 (color
!= 'w' || i
!= 0) &&
227 (color
!= 'b' || i
!= this.size
.x
- 1)
234 (colIJ
!= 'w' || x
!= 0) &&
235 (colIJ
!= 'b' || x
!= this.size
.x
- 1)
239 let m
= this.getBasicMove([x
, y
], [i
, j
]);
240 m
.appear
.push(new PiPo({x: x
, y: y
, p: pieceIJ
, c: colIJ
}));
241 m
.kingboo
= true; //avoid some side effects (bananas/bombs)
248 // Normal case (including bonus daisy)
251 moves
= this.getPawnMovesFrom([x
, y
]); //apply promotions
254 moves
= this.getQueenMovesFrom([x
, y
]);
257 moves
= this.getKingMovesFrom([x
, y
]);
260 moves
= this.getKnightMovesFrom([x
, y
]);
264 // Explicitely listing types to avoid moving immobilized piece
265 moves
= super.getPotentialMovesOf(piece
, [x
, y
]);
274 this.board
[i
][j
] == "" ||
275 [V
.MUSHROOM
, V
.EGG
].includes(this.getPiece(i
, j
)));
278 getPawnMovesFrom([x
, y
]) {
279 const color
= this.turn
;
280 const oppCol
= C
.GetOppCol(color
);
281 const shiftX
= (color
== 'w' ? -1 : 1);
282 const firstRank
= (color
== "w" ? this.size
.x
- 1 : 0);
285 this.board
[x
+ shiftX
][y
] == "" ||
286 this.getColor(x
+ shiftX
, y
) == 'a' ||
287 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
289 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
291 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
293 this.board
[x
+ 2 * shiftX
][y
] == "" ||
294 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
295 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
298 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
301 for (let shiftY
of [-1, 1]) {
304 y
+ shiftY
< this.size
.y
&&
305 this.board
[x
+ shiftX
][y
+ shiftY
] != "" &&
306 // Pawns cannot capture invisible queen this way!
307 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
308 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
310 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
313 this.pawnPostProcess(moves
, color
, oppCol
);
314 // Add mushroom on before-last square
316 let revStep
= [m
.start
.x
- m
.end
.x
, m
.start
.y
- m
.end
.y
];
317 for (let i
of [0, 1])
318 revStep
[i
] = revStep
[i
] / Math
.abs(revStep
[i
]) || 0;
319 const [blx
, bly
] = [m
.end
.x
+ revStep
[0], m
.end
.y
+ revStep
[1]];
320 m
.appear
.push(new PiPo({x: blx
, y: bly
, c: 'a', p: 'm'}));
321 if (blx
!= x
&& this.board
[blx
][bly
] != "") {
322 m
.vanish
.push(new PiPo({
325 c: this.getColor(blx
, bly
),
326 p: this.getPiece(blx
, bly
)
333 getKnightMovesFrom([x
, y
]) {
334 // Add egg on initial square:
335 return this.getPotentialMovesOf('n', [x
, y
]).map(m
=> {
336 m
.appear
.push(new PiPo({p: "e", c: "a", x: x
, y: y
}));
341 getQueenMovesFrom(sq
) {
342 const normalMoves
= this.getPotentialMovesOf('q', sq
);
343 // If flag allows it, add 'invisible movements'
344 let invisibleMoves
= [];
345 if (this.powerFlags
[this.turn
]['q']) {
346 normalMoves
.forEach(m
=> {
348 m
.appear
.length
== 1 &&
349 m
.vanish
.length
== 1 &&
350 // Only simple non-capturing moves:
353 let im
= JSON
.parse(JSON
.stringify(m
));
354 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
356 invisibleMoves
.push(im
);
360 return normalMoves
.concat(invisibleMoves
);
363 getKingMovesFrom([x
, y
]) {
364 let moves
= this.getPotentialMovesOf('k', [x
, y
]);
365 // If flag allows it, add 'remote shell captures'
366 if (this.powerFlags
[this.turn
]['k']) {
367 super.pieces()['k'].moves
[0].steps
.forEach(step
=> {
368 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
370 this.onBoard(i
, j
) &&
372 this.board
[i
][j
] == "" ||
373 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
375 this.getColor(i
, j
) == 'a' &&
376 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
383 if (this.onBoard(i
, j
)) {
384 const colIJ
= this.getColor(i
, j
);
385 if (colIJ
!= this.turn
) {
386 // May just destroy a bomb or banana:
387 let shellCapture
= new Move({
392 new PiPo({x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)})
395 shellCapture
.shell
= true; //easier play()
396 shellCapture
.choice
= 'z'; //to display in showChoices()
397 moves
.push(shellCapture
);
406 if (!move.nextComputed
) {
407 // Set potential random effects, so that play() is deterministic
408 // from opponent viewpoint:
409 const endPiece
= this.getPiece(move.end
.x
, move.end
.y
);
412 move.egg
= Random
.sample(V
.EGG_SURPRISE
);
413 move.next
= this.getEggEffect(move);
416 move.next
= this.getMushroomEffect(move);
420 move.next
= this.getBombBananaEffect(move, endPiece
);
423 if (!move.next
&& move.appear
.length
> 0 && !move.kingboo
) {
424 const movingPiece
= move.appear
[0].p
;
425 if (['b', 'r'].includes(movingPiece
)) {
426 // Drop a banana or bomb:
428 this.getRandomSquare([move.end
.x
, move.end
.y
],
430 ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
431 : [[1, 0], [-1, 0], [0, 1], [0, -1]],
439 p: movingPiece
== 'r' ? 'd' : 'w'
442 if (this.board
[bs
[0]][bs
[1]] != "") {
447 c: this.getColor(bs
[0], bs
[1]),
448 p: this.getPiece(bs
[0], bs
[1])
455 move.nextComputed
= true;
458 const color
= this.turn
;
459 const oppCol
= C
.GetOppCol(color
);
460 if (move.egg
== "toadette") {
461 this.reserve
= { w: {}, b: {} };
462 // Randomly select a piece in pawnPromotions
464 move.toadette
= Random
.sample(this.pawnPromotions
);
465 this.reserve
[color
][move.toadette
] = 1;
466 this.re_drawReserve([color
]);
468 else if (Object
.keys(this.reserve
).length
> 0) {
470 this.re_drawReserve([color
]);
473 this.powerFlags
[color
]['k'] = false;
474 else if (move.appear
.length
> 0 && move.appear
[0].p
== V
.INVISIBLE_QUEEN
) {
475 this.powerFlags
[move.appear
[0].c
]['q'] = false;
476 if (color
!= this.playerColor
)
477 alert("Invisible queen!");
479 if (color
== this.playerColor
) {
480 // Look for an immobilized piece of my color: it can now move
481 for (let i
=0; i
<8; i
++) {
482 for (let j
=0; j
<8; j
++) {
483 if ((i
!= move.end
.x
|| j
!= move.end
.y
) && this.board
[i
][j
] != "") {
484 const piece
= this.getPiece(i
, j
);
486 this.getColor(i
, j
) == color
&&
487 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
489 move.vanish
.push(new PiPo({
490 x: i
, y: j
, c: color
, p: piece
492 move.appear
.push(new PiPo({
493 x: i
, y: j
, c: color
, p: V
.IMMOBILIZE_DECODE
[piece
]
499 // Also make opponent invisible queen visible again, if any
500 for (let i
=0; i
<8; i
++) {
501 for (let j
=0; j
<8; j
++) {
503 this.board
[i
][j
] != "" &&
504 this.getColor(i
, j
) == oppCol
&&
505 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
507 move.vanish
.push(new PiPo({
508 x: i
, y: j
, c: oppCol
, p: V
.INVISIBLE_QUEEN
510 move.appear
.push(new PiPo({
511 x: i
, y: j
, c: oppCol
, p: 'q'
517 if (!move.next
&& !["daisy", "toadette", "kingboo"].includes(move.egg
)) {
522 this.displayBonus(move.egg
);
523 this.playOnBoard(move);
524 this.nextMove
= move.next
;
527 // Helper to set and apply banana/bomb effect
528 getRandomSquare([x
, y
], steps
, freeSquare
) {
529 let validSteps
= steps
.filter(s
=> this.onBoard(x
+ s
[0], y
+ s
[1]));
531 // Square to put banana/bomb cannot be occupied by a piece
532 validSteps
= validSteps
.filter(s
=> {
533 return ["", 'a'].includes(this.getColor(x
+ s
[0], y
+ s
[1]))
536 if (validSteps
.length
== 0)
538 const step
= validSteps
[Random
.randInt(validSteps
.length
)];
539 return [x
+ step
[0], y
+ step
[1]];
543 const getRandomPiece
= (c
) => {
544 let bagOfPieces
= [];
545 for (let i
=0; i
<this.size
.x
; i
++) {
546 for (let j
=0; j
<this.size
.y
; j
++) {
547 if (this.getColor(i
, j
) == c
&& this.getPiece(i
, j
) != 'k')
548 bagOfPieces
.push([i
, j
]);
551 if (bagOfPieces
.length
>= 1)
552 return Random
.sample(bagOfPieces
);
555 const color
= this.turn
;
560 // Change color of friendly or enemy piece, king excepted
561 const oldColor
= (move.egg
== "waluigi" ? color : C
.GetOppCol(color
));
562 const newColor
= C
.GetOppCol(oldColor
);
563 const coords
= getRandomPiece(oldColor
);
565 const piece
= this.getPiece(coords
[0], coords
[1]);
568 new PiPo({x: coords
[0], y: coords
[1], c: newColor
, p: piece
})
571 new PiPo({x: coords
[0], y: coords
[1], c: oldColor
, p: piece
})
583 p: V
.IMMOBILIZE_CODE
[move.appear
[0].p
]
601 x: move.start
.x
, y: move.start
.y
, c: color
, p: move.appear
[0].p
606 x: move.end
.x
, y: move.end
.y
, c: color
, p: move.appear
[0].p
610 if (this.board
[move.start
.x
][move.start
.y
] != "") {
611 // Pawn or knight let something on init square
612 em
.vanish
.push(new PiPo({
616 p: this.getPiece(move.start
.x
, move.start
.y
)
626 x: move.end
.x
, y: move.end
.y
, c: color
, p: move.appear
[0].p
629 end: {x: move.end
.x
, y: move.end
.y
}
633 if (em
&& move.egg
!= "koopa")
634 em
.noAnimate
= true; //static move
638 getMushroomEffect(move) {
639 let step
= [move.end
.x
- move.start
.x
, move.end
.y
- move.start
.y
];
640 if ([0, 1].some(i
=> Math
.abs(step
[i
]) >= 2 && Math
.abs(step
[1-i
]) != 1)) {
641 // Slider, multi-squares: normalize step
642 for (let j
of [0, 1])
643 step
[j
] = step
[j
] / Math
.abs(step
[j
]) || 0;
645 const nextSquare
= [move.end
.x
+ step
[0], move.end
.y
+ step
[1]];
647 [nextSquare
[0] + step
[0], nextSquare
[1] + step
[1]];
649 this.playOnBoard(move); //HACK for getBasicMove() below
651 this.onBoard(nextSquare
[0], nextSquare
[1]) &&
652 ['k', 'p', 'n'].includes(move.vanish
[0].p
) &&
653 !['w', 'b'].includes(this.getColor(nextSquare
[0], nextSquare
[1]))
655 // Speed up non-sliders
656 nextMove
= this.getBasicMove([move.end
.x
, move.end
.y
], nextSquare
);
659 this.onBoard(afterSquare
[0], afterSquare
[1]) &&
660 this.board
[nextSquare
[0]][nextSquare
[1]] != "" &&
661 this.getColor(nextSquare
[0], nextSquare
[1]) != 'a' &&
662 this.getColor(afterSquare
[0], afterSquare
[1]) != this.turn
664 nextMove
= this.getBasicMove([move.end
.x
, move.end
.y
], afterSquare
);
666 this.undoOnBoard(move);
670 getBombBananaEffect(move, item
) {
671 const steps
= item
== V
.BANANA
672 ? [[1, 0], [-1, 0], [0, 1], [0, -1]]
673 : [[1, 1], [1, -1], [-1, 1], [-1, -1]];
674 const nextSquare
= this.getRandomSquare([move.end
.x
, move.end
.y
], steps
);
675 this.playOnBoard(move); //HACK for getBasicMove()
676 const res
= this.getBasicMove([move.end
.x
, move.end
.y
], nextSquare
);
677 this.undoOnBoard(move);
682 alert(egg
); //TODO: nicer display
693 playPlusVisual(move, r
) {
694 this.moveStack
.push(move);
695 const nextLines
= () => {
697 this.playVisual(move, r
);
699 this.playPlusVisual(this.nextMove
, r
);
701 this.afterPlay(this.moveStack
);
705 if (this.moveStack
.length
== 1)
708 this.animate(move, nextLines
);