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}
27 get pawnPromotions() {
28 return ['q', 'r', 'n', 'b', 'k'];
44 static get IMMOBILIZE_CODE() {
55 static get IMMOBILIZE_DECODE() {
66 static get INVISIBLE_QUEEN() {
70 // Fictive color 'a', bomb banana mushroom egg
75 return 'd'; //"Donkey"
80 static get MUSHROOM() {
84 static get EGG_SURPRISE() {
86 "kingboo", "bowser", "daisy", "koopa",
87 "luigi", "waluigi", "toadette", "chomp"];
92 'i': {"class": "invisible"}, //queen
93 'e': {"class": "egg"},
94 'm': {"class": "mushroom"},
95 'd': {"class": "banana"},
96 'w': {"class": "bomb"}
98 return Object
.assign(specials
, super.pieces(color
, x
, y
));
101 genRandInitFen(seed
) {
102 const gr
= new GiveawayRules(
103 {mode: "suicide", options: {}, genFenOnly: true});
104 // Add Peach + mario flags
105 return gr
.genRandInitFen(seed
).slice(0, -17) + '{"flags":"1111"}';
111 ? "w" + f
.toLowerCase()
112 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
117 // King can send shell? Queen can be invisible?
119 w: {k: false, q: false},
120 b: {k: false, q: false}
122 for (let c
of ['w', 'b']) {
123 for (let p
of ['k', 'q']) {
124 this.powerFlags
[c
][p
] =
125 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
131 return this.powerFlags
;
134 disaggregateFlags(flags
) {
135 this.powerFlags
= flags
;
139 return ['w', 'b'].map(c
=> {
140 return ['k', 'q'].map(p
=> this.powerFlags
[c
][p
] ? "1" : "0").join("");
144 setOtherVariables(fenParsed
) {
145 this.setFlags(fenParsed
.flags
);
146 this.reserve
= {}; //to be filled later
151 // For Toadette bonus
152 getDropMovesFrom([c
, p
]) {
153 if (typeof c
!= "string" || this.reserve
[c
][p
] == 0)
156 const start
= (c
== 'w' && p
== 'p' ? 1 : 0);
157 const end
= (color
== 'b' && p
== 'p' ? 7 : 8);
158 for (let i
= start
; i
< end
; i
++) {
159 for (let j
= 0; j
< this.size
.y
; j
++) {
160 const pieceIJ
= this.getPiece(i
, j
);
162 this.board
[i
][j
] == "" ||
163 this.getColor(i
, j
) == 'a' ||
164 pieceIJ
== V
.INVISIBLE_QUEEN
169 appear: [new PiPo({x: i
, y: j
, c: c
, p: p
})],
172 // A drop move may remove a bonus (or hidden queen!)
173 if (this.board
[i
][j
] != "")
174 m
.vanish
.push(new PiPo({x: i
, y: j
, c: 'a', p: pieceIJ
}));
182 getPotentialMovesFrom([x
, y
]) {
184 if (this.egg
== "toadette")
185 moves
= this.getDropMovesFrom([x
, y
]);
186 else if (this.egg
== "kingboo") {
187 const initPiece
= this.getPiece(x
, y
);
188 const color
= this.getColor(x
, y
);
189 const oppCol
= C
.GetOppCol(color
);
190 // Only allow to swap pieces
191 for (let i
=0; i
<this.size
.x
; i
++) {
192 for (let j
=0; j
<this.size
.y
; j
++) {
193 const colIJ
= this.getColor(i
, j
);
194 const pieceIJ
= this.getPiece(i
, j
);
196 (i
!= x
|| j
!= y
) &&
197 ['w', 'b'].includes(colIJ
) &&
198 // Next conditions = no pawn on last rank
202 (color
!= 'w' || i
!= 0) &&
203 (color
!= 'b' || i
!= this.size
.x
- 1)
210 (colIJ
!= 'w' || x
!= 0) &&
211 (colIJ
!= 'b' || x
!= this.size
.x
- 1)
215 let m
= this.getBasicMove([x
, y
], [i
, j
]);
217 new PiPo({x: x
, y: y
, p: this.getPiece(i
, j
), c: oppCol
}));
224 // Normal case (including bonus daisy)
225 const piece
= this.getPiece(x
, y
);
228 moves
= this.getPawnMovesFrom([x
, y
]); //apply promotions
231 moves
= this.getQueenMovesFrom([x
, y
]);
234 moves
= this.getKingMovesFrom([x
, y
]);
237 moves
= this.getKnightMovesFrom([x
, y
]);
241 // explicitely listing types to avoid moving immobilized piece
242 moves
= this.getRookOrBishopMovesFrom([x
, y
], piece
);
246 // Set potential random effects, so that play() is deterministic
248 switch (this.getPiece(m
.end
.x
, m
.end
.y
)) {
250 m
.egg
= Random
.sample(V
.EGG_SURPRISE
);
251 m
.next
= this.getEggEffect(m
);
254 m
.next
= this.getRandomSquare(
255 [m
.end
.x
, m
.end
.y
], [[1, 1], [1, -1], [-1, 1], [-1, -1]]);
258 m
.next
= this.getRandomSquare(
259 [m
.end
.x
, m
.end
.y
], [[1, 0], [-1, 0], [0, 1], [0, -1]]);
267 const getRandomPiece
= (c
) => {
268 let bagOfPieces
= [];
269 for (let i
=0; i
<this.size
.x
; i
++) {
270 for (let j
=0; j
<this.size
.y
; j
++) {
271 if (this.getColor(i
, j
) == c
&& this.getPiece(i
, j
) != 'k')
272 bagOfPieces
.push([i
, j
]);
275 if (bagOfPieces
.length
>= 1)
276 return Random
.sample(bagOfPieces
);
279 const color
= this.turn
;
284 // Change color of friendly or enemy piece, king excepted
285 const oldColor
= (move.egg
== "waluigi" ? color : C
.GetOppCol(color
));
286 const newColor
= C
.GetOppCol(oldColor
);
287 const coords
= getRandomPiece(oldColor
);
289 const piece
= this.getPiece(coords
[0], coords
[1]);
292 new PiPo({x: coords
[0], y: coords
[1], c: newColor
, p: piece
})
295 new PiPo({x: coords
[0], y: coords
[1], c: oldColor
, p: piece
})
307 p: V
.IMMOBILIZED_CODE
[move.appear
[0].p
]
325 x: move.start
.x
, y: move.start
.y
, c: color
, p: move.appear
[0].p
330 x: move.end
.x
, y: move.end
.y
, c: color
, p: move.appear
[0].p
341 x: move.end
.x
, y: move.end
.y
, c: color
, p: move.appear
[0].p
344 end: {x: move.end
.x
, y: move.end
.y
}
351 // Helper to set and apply banana/bomb effect
352 getRandomSquare([x
, y
], steps
, freeSquare
) {
353 let validSteps
= steps
.filter(s
=> this.onBoard(x
+ s
[0], y
+ s
[1]));
355 // Square to put banana/bomb cannot be occupied by a piece
356 validSteps
= validSteps
.filter(s
=> {
357 return ["", 'a'].includes(this.getColor(x
+ s
[0], y
+ s
[1]))
360 if (validSteps
.length
== 0)
362 const step
= validSteps
[Random
.randInt(validSteps
.length
)];
363 return [x
+ step
[0], y
+ step
[1]];
366 getPotentialMovesOf(piece
, [x
, y
]) {
367 const color
= this.getColor(x
, y
);
368 const stepSpec
= this.pieces(color
, x
, y
)[piece
];
370 const findAddMoves
= (type
, stepArray
) => {
371 for (let s
of stepArray
) {
372 outerLoop: for (let step
of s
.steps
) {
373 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
376 this.onBoard(i
, j
) &&
378 this.board
[i
][j
] == "" ||
379 [V
.MUSHROOM
, V
.EGG
].includes(this.getPiece(i
, j
))
382 if (type
!= "attack")
383 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
384 if (s
.range
<= stepCounter
++)
386 [i
, j
] = [i
+ step
[0], j
+ step
[1]];
388 if (!this.onBoard(i
, j
))
390 const pieceIJ
= this.getPiece(i
, j
);
391 if (type
!= "moveonly" && this.getColor(i
, j
) != color
)
392 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
396 const specialAttack
= !!stepSpec
.attack
;
398 findAddMoves("attack", stepSpec
.attack
);
399 findAddMoves(specialAttack
? "moveonly" : "all", stepSpec
.moves
);
403 getPawnMovesFrom([x
, y
]) {
404 const color
= this.turn
;
405 const oppCol
= C
.GetOppCol(color
);
406 const shiftX
= (color
== 'w' ? -1 : 1);
407 const firstRank
= (color
== "w" ? this.size
.x
- 1 : 0);
410 this.board
[x
+ shiftX
][y
] == "" ||
411 this.getColor(x
+ shiftX
, y
) == 'a' ||
412 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
414 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
416 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
418 this.board
[x
+ 2 * shiftX
][y
] == "" ||
419 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
420 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
423 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
426 for (let shiftY
of [-1, 1]) {
429 y
+ shiftY
< this.size
.y
&&
430 this.board
[x
+ shiftX
][y
+ shiftY
] != "" &&
431 // Pawns cannot capture invisible queen this way!
432 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
433 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
435 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
438 this.pawnPostProcess(moves
, color
, oppCol
);
439 // Add mushroom on initial square
441 m
.appear
.push(new PiPo({x: m
.start
.x
, y: m
.start
.y
, c: 'a', p: 'm'}));
446 getRookOrBishopMovesFrom([x
, y
], type
) {
447 // Add banana if possible, diagonaly
448 return this.getPotentialMovesOf(type
, [x
, y
]).map(m
=> {
450 this.getRandomSquare([m
.end
.x
, m
.end
.y
],
452 ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
453 : [[1, 0], [-1, 0], [0, 1], [0, -1]],
461 p: type
== 'r' ? 'd' : 'w'
464 if (this.board
[bs
[0]][bs
[1]] != "") {
469 c: this.getColor(bs
[0], bs
[1]),
470 p: this.getPiece(bs
[0], bs
[1])
479 getKnightMovesFrom([x
, y
]) {
480 // Add egg on initial square:
481 return this.getPotentialMovesOf('n', [x
, y
]).map(m
=> {
482 m
.appear
.push(new PiPo({p: "e", c: "a", x: x
, y: y
}));
487 getQueenMovesFrom(sq
) {
488 const normalMoves
= this.getPotentialMovesOf('q', sq
);
489 // If flag allows it, add 'invisible movements'
490 let invisibleMoves
= [];
491 if (this.powerFlags
[this.turn
]['q']) {
492 normalMoves
.forEach(m
=> {
494 m
.appear
.length
== 1 &&
495 m
.vanish
.length
== 1 &&
496 // Only simple non-capturing moves:
499 let im
= JSON
.parse(JSON
.stringify(m
));
500 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
502 invisibleMoves
.push(im
);
506 return normalMoves
.concat(invisibleMoves
);
509 getKingMovesFrom([x
, y
]) {
510 let moves
= this.getPotentialMovesOf('k', [x
, y
]);
511 // If flag allows it, add 'remote shell captures'
512 if (this.powerFlags
[this.turn
]['k']) {
513 super.pieces()['k'].moves
[0].steps
.forEach(step
=> {
514 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
516 this.onBoard(i
, j
) &&
518 this.board
[i
][j
] == "" ||
519 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
521 this.getColor(i
, j
) == 'a' &&
522 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
529 if (this.onBoard(i
, j
)) {
530 const colIJ
= this.getColor(i
, j
);
531 if (colIJ
!= this.turn
) {
532 // May just destroy a bomb or banana:
533 let shellCapture
= new Move({
538 new PiPo({x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)})
541 shellCapture
.shell
= true; //easier play()
542 moves
.push(shellCapture
);
552 const color
= this.turn
;
553 if (move.egg
== "toadette") {
554 this.reserve
= { w: {}, b: {} };
555 // Randomly select a piece in pawnPromotions
556 this.reserve
[color
][Random
.sample(this.pawnPromotions
)] = 1;
557 this.re_drawReserve([color
]);
559 else if (Object
.keys(this.reserve
).length
> 0) {
561 this.re_drawReserve([color
]);
563 if (this.getPiece(move.end
.x
, move.end
.y
) == V
.MUSHROOM
)
564 move.next
= this.getMushroomEffect(move);
566 this.powerFlags
[color
]['k'] = false;
567 else if (move.appear
.length
> 0 && move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
568 this.powerFlags
[move.appear
[0].c
]['q'] = false;
569 this.playOnBoard(move);
570 // Look for an immobilized piece of my color: it can now move
571 for (let i
=0; i
<8; i
++) {
572 for (let j
=0; j
<8; j
++) {
573 if ((i
!= move.end
.x
|| j
!= move.end
.y
) && this.board
[i
][j
] != "") {
574 const piece
= this.getPiece(i
, j
);
576 this.getColor(i
, j
) == color
&&
577 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
579 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
584 // Also make opponent invisible queen visible again, if any
585 const oppCol
= C
.GetOppCol(color
);
586 for (let i
=0; i
<8; i
++) {
587 for (let j
=0; j
<8; j
++) {
589 this.board
[i
][j
] != "" &&
590 this.getColor(i
, j
) == oppCol
&&
591 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
593 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
597 if (!move.next
&& !["daisy", "toadette", "kingboo"].includes(move.egg
)) {
602 this.displayBonus(move.egg
);
603 this.nextMove
= move.next
;
607 alert(egg
); //TODO: nicer display
614 getMushroomEffect(move) {
615 let step
= [move.end
.x
- move.start
.x
, move.end
.y
- move.start
.y
];
616 if ([0, 1].some(i
=> step
[i
] >= 2 && step
[1-i
] != 1)) {
617 // Slider, multi-squares: normalize step
618 for (let j
of [0, 1])
619 step
[j
] = step
[j
] / Math
.abs(step
[j
]) || 0;
621 const nextSquare
= [move.end
.x
+ step
[0], move.end
.y
+ step
[1]];
623 [nextSquare
[0] + step
[0], nextSquare
[1] + step
[1]];
626 this.onBoard(nextSquare
[0], nextSquare
[1]) &&
627 this.board
[nextSquare
[0]][nextSquare
[1]] == "" &&
628 ['k', 'p', 'n'].includes(move.vanish
[0].p
)
630 // Speed up non-sliders
631 nextMove
= this.getBasicMove([move.end
.x
, move.end
.y
], nextSquare
);
634 this.onBoard(afterSquare
[0], afterSquare
[1]) &&
635 this.board
[nextSquare
[0]][nextSquare
[1]] != "" &&
636 this.getColor(nextSquare
[0], nextSquare
[1]) != 'a' &&
637 this.getColor(afterSquare
[0], afterSquare
[1]) != this.turn
639 nextMove
= this.getBasicMove([move.end
.x
, move.end
.y
], afterSquare
);
644 playPlusVisual(move, r
) {
645 this.moveStack
.push(move);
647 this.playVisual(move, r
);
649 this.playPlusVisual(this.nextMove
, r
);
651 this.afterPlay(this.moveStack
);
654 // TODO: set some special moves (effects) as noAnimate
655 // TODO: put bomb/banana only at final location of a move ? Seems more logical
656 // + fix bishop takes mushroom and jump
657 // Also improve showChoices for invisible queen + king shell
658 // + fix turn issues after multimove (like bishop put bomb)