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'];
38 static get IMMOBILIZE_CODE() {
49 static get IMMOBILIZE_DECODE() {
60 static get INVISIBLE_QUEEN() {
64 // Fictive color 'a', bomb banana mushroom egg
69 return 'd'; //"Donkey"
74 static get MUSHROOM() {
78 genRandInitFen(seed
) {
79 const gr
= new GiveawayRules(
80 {mode: "suicide", options: {}, genFenOnly: true});
82 gr
.genRandInitFen(seed
).slice(0, -17) +
83 // Add Peach + Mario flags + capture counts
84 '{"flags":"1111","ccount":"000000000000"}'
91 ? "w" + f
.toLowerCase()
92 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
97 // King can send shell? Queen can be invisible?
99 w: {k: false, q: false},
100 b: {k: false, q: false}
102 for (let c
of ['w', 'b']) {
103 for (let p
of ['k', 'q']) {
104 this.powerFlags
[c
][p
] =
105 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
111 return this.powerFlags
;
114 disaggregateFlags(flags
) {
115 this.powerFlags
= flags
;
119 return super.getFen() + " " + this.getCapturedFen();
123 return ['w', 'b'].map(c
=> {
124 return ['k', 'q'].map(p
=> this.powerFlags
[c
][p
] ? "1" : "0").join("");
129 const res
= ['w', 'b'].map(c
=> Object
.values(this.captured
[c
]));
130 return res
[0].concat(res
[1]).join("");
133 setOtherVariables(fenParsed
) {
134 super.setOtherVariables(fenParsed
);
135 // Initialize captured pieces' counts from FEN
136 const allCapts
= fenParsed
.ccount
.split("").map(x
=> parseInt(x
, 10));
137 const pieces
= ['p', 'r', 'n', 'b', 'q', 'k'];
139 w: ArrayFun
.toObject(pieces
, allCapts
.slice(0, 6)),
140 b: ArrayFun
.toObject(pieces
, allCapts
.slice(6, 12))
142 this.reserve
= { w: {}, b: {} }; //to be replaced by this.captured
147 // For Toadette bonus
148 getDropMovesFrom([c
, p
]) {
149 if (typeof c
!= "string" || this.reserve
[c
][p
] == 0)
152 const start
= (c
== 'w' && p
== 'p' ? 1 : 0);
153 const end
= (color
== 'b' && p
== 'p' ? 7 : 8);
154 for (let i
= start
; i
< end
; i
++) {
155 for (let j
= 0; j
< this.size
.y
; j
++) {
156 const pieceIJ
= this.getPiece(i
, j
);
158 this.board
[i
][j
] == "" ||
159 this.getColor(i
, j
) == 'a' ||
160 pieceIJ
== V
.INVISIBLE_QUEEN
165 appear: [new PiPo({x: i
, y: j
, c: c
, p: p
})],
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
}));
178 // Moving something. Potential effects resolved after playing
179 getPotentialMovesFrom([x
, y
]) {
181 if (this.egg
== "toadette")
182 return this.getDropMovesFrom([x
, y
]);
183 if (this.egg
== "kingboo") {
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
188 for (let i
=0; i
<this.size
.x
; i
++) {
189 for (let j
=0; j
<this.size
.y
; j
++) {
190 const colIJ
= this.getColor(i
, j
);
191 const pieceIJ
= this.getPiece(i
, j
);
193 (i
!= x
|| j
!= y
) &&
194 ['w', 'b'].includes(colIJ
) &&
195 // Next conditions = no pawn on last rank
199 (color
!= 'w' || i
!= 0) &&
200 (color
!= 'b' || i
!= this.size
.x
- 1)
207 (colIJ
!= 'w' || x
!= 0) &&
208 (colIJ
!= 'b' || x
!= this.size
.x
- 1)
212 let m
= this.getBasicMove([x
, y
], [i
, j
]);
214 new PiPo({x: x
, y: y
, p: this.getPiece(i
, j
), c: oppCol
}));
221 // Normal case (including bonus daisy)
222 switch (this.getPiece(x
, y
)) {
224 moves
= this.getPawnMovesFrom([x
, y
]); //apply promotions
227 moves
= this.getQueenMovesFrom([x
, y
]);
230 moves
= this.getKingMovesFrom([x
, y
]);
233 moves
= this.getKnightMovesFrom([x
, y
]);
237 // explicitely listing types to avoid moving immobilized piece
238 moves
= super.getPotentialMovesFrom([x
, y
]);
243 getPawnMovesFrom([x
, y
]) {
244 const color
= this.turn
;
245 const oppCol
= C
.GetOppCol(color
);
246 const shiftX
= (color
== 'w' ? -1 : 1);
247 const firstRank
= (color
== "w" ? this.size
.x
- 1 : 0);
250 this.board
[x
+ shiftX
][y
] == "" ||
251 this.getColor(x
+ shiftX
, y
) == 'a' ||
252 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
254 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
256 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
258 this.board
[x
+ 2 * shiftX
][y
] == "" ||
259 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
260 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
263 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
266 for (let shiftY
of [-1, 1]) {
269 y
+ shiftY
< this.size
.y
&&
270 this.board
[x
+ shiftX
][y
+ shiftY
] != "" &&
271 // Pawns cannot capture invisible queen this way!
272 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
273 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
275 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
278 super.pawnPostProcess(moves
, color
, oppCol
);
282 getQueenMovesFrom(sq
) {
283 const normalMoves
= super.getPotentialMovesOf('q', sq
);
284 // If flag allows it, add 'invisible movements'
285 let invisibleMoves
= [];
286 if (this.powerFlags
[this.turn
]['q']) {
287 normalMoves
.forEach(m
=> {
289 m
.appear
.length
== 1 &&
290 m
.vanish
.length
== 1 &&
291 // Only simple non-capturing moves:
294 let im
= JSON
.parse(JSON
.stringify(m
));
295 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
296 im
.end
.noHighlight
= true;
297 invisibleMoves
.push(im
);
301 return normalMoves
.concat(invisibleMoves
);
304 getKingMovesFrom([x
, y
]) {
305 let moves
= super.getPotentialMovesOf('k', [x
, y
]);
306 // If flag allows it, add 'remote shell captures'
307 if (this.powerFlags
[this.turn
]['k']) {
308 super.pieces()['k'].moves
[0].steps
.forEach(step
=> {
309 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
311 this.onBoard(i
, j
) &&
313 this.board
[i
][j
] == "" ||
314 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
316 this.getColor(i
, j
) == 'a' &&
317 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
324 if (this.onBoard(i
, j
)) {
325 const colIJ
= this.getColor(i
, j
);
326 if (colIJ
!= this.turn
) {
327 // May just destroy a bomb or banana:
334 new PiPo({x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)})
345 getKnightMovesFrom([x
, y
]) {
346 // Add egg on initial square:
347 return super.getPotentialMovesOf('n', [x
, y
]).map(m
=> {
348 m
.appear
.push(new PiPo({p: "e", c: "a", x: x
, y: y
}));
353 /// if any of my pieces was immobilized, it's not anymore.
354 //if play set a piece immobilized, then mark it
356 if (move.effect
== "toadette") {
357 this.reserve
= this.captured
;
358 this.re_drawReserve([this.turn
]);
360 else if (this.reserve
) {
361 this.reserve
= { w: {}, b: {} };
362 this.re_drawReserve([this.turn
]);
364 const color
= this.turn
;
366 move.vanish
.length
== 2 &&
367 move.vanish
[1].c
!= 'a' &&
368 move.appear
.length
== 1 //avoid king Boo!
370 // Capture: update this.captured
371 let capturedPiece
= move.vanish
[1].p
;
372 if (capturedPiece
== V
.INVISIBLE_QUEEN
)
373 capturedPiece
= V
.QUEEN
;
374 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
375 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
376 this.captured
[move.vanish
[1].c
][capturedPiece
]++;
378 else if (move.vanish
.length
== 0) {
379 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a')
381 // A piece is back on board
382 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
384 if (move.appear
.length
== 0) {
385 // Three cases: king "shell capture", Chomp or Koopa
386 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
387 // King remote capture:
388 this.powerFlags
[color
][V
.KING
] = false;
389 else if (move.end
.effect
== "chomp")
390 this.captured
[color
][move.vanish
[0].p
]++;
392 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
393 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
394 if (this.subTurn
== 2) return;
397 move.appear
.length
== 0 ||
398 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
400 // Look for an immobilized piece of my color: it can now move
401 for (let i
=0; i
<8; i
++) {
402 for (let j
=0; j
<8; j
++) {
403 if (this.board
[i
][j
] != V
.EMPTY
) {
404 const piece
= this.getPiece(i
, j
);
406 this.getColor(i
, j
) == color
&&
407 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
409 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
410 move.wasImmobilized
= [i
, j
];
416 // Also make opponent invisible queen visible again, if any
417 const oppCol
= V
.GetOppCol(color
);
418 for (let i
=0; i
<8; i
++) {
419 for (let j
=0; j
<8; j
++) {
421 this.board
[i
][j
] != V
.EMPTY
&&
422 this.getColor(i
, j
) == oppCol
&&
423 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
425 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
426 move.wasInvisible
= [i
, j
];
430 this.playOnBoard(move);
431 if (["kingboo", "toadette", "daisy"].includes(move.effect
)) {
432 this.effect
= move.effect
;
436 this.turn
= C
.GetOppCol(this.turn
);
443 this.displayBonus(move.egg
);
445 this.egg
= null; //the egg is consumed
449 alert(egg
); //TODO: nicer display
456 tryMoveFollowup(move, cb
) {
457 // Warning: at this stage, the move is played
458 if (move.vanish
.length
== 2 && move.vanish
[1].c
== 'a') {
459 // effect, or bonus/malus
460 const endType
= move.vanish
[1].p
;
463 this.applyRandomBonus(move, cb
);
468 this.getRandomSquare([m
.end
.x
, m
.end
.y
],
470 ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
471 : [[1, 0], [-1, 0], [0, 1], [0, -1]]);
472 cb(this.getBasicMove([move.end
.x
, move.end
.y
], dest
));
476 let step
= [move.end
.x
- move.start
.x
, move.end
.y
- move.start
.y
];
477 if ([0, 1].some(i
=> step
[i
] >= 2 && step
[1-i
] != 1)) {
478 // Slider, multi-squares: normalize step
479 for (let j
of [0, 1])
480 step
[j
] = step
[j
] / Math
.abs(step
[j
]) || 0;
482 const nextSquare
= [move.end
.x
+ step
[0], move.end
.y
+ step
[1]];
483 if (this.onBoard(nextSquare
[0], nextSquare
[1])) {
485 this.board
[nextSquare
[0]][nextSquare
[1]] != "" &&
486 this.getColor(nextSquare
[0], nextSquare
[1]) != 'a'
490 [nextSquare
[0] + step
[0], nextSquare
[1] + step
[1]];
492 this.onBoard(afterSquare
[0], afterSquare
[1]) &&
493 this.getColor(afterSquare
[0], afterSquare
[1]) != this.turn
495 cb(this.getBasicMove([move.end
.x
, move.end
.y
], afterSquare
));
498 else if (!['b', 'r', 'q'].includes(move.vanish
[0].p
))
499 // Take another step forward if not slider move
500 cb(this.getBasicMove([move.end
.x
, move.end
.y
], nextSquare
));
508 applyRandomBonus(move, cb
) {
509 // TODO: determine bonus/malus, and then ...
510 // if toadette, daisy or kingboo : do not call cb
511 this.egg
= "daisy"; //not calling cb in this case
512 this.displayBonus(this.egg
);
513 move.egg
= this.egg
; //for play() by opponent
516 // Helper to apply banana/bomb effect
517 getRandomSquare([x
, y
], steps
) {
518 const validSteps
= steps
.filter(s
=> this.onBoard(x
+ s
[0], y
+ s
[1]));
519 const step
= validSteps
[Random
.randInt(validSteps
.length
)];
520 return [x
+ step
[0], y
+ step
[1]];
523 // Warning: if play() is called, then move.end changed.
524 playPlusVisual(move, r
) {
525 this.moveStack
.push(move);
527 this.playVisual(move, r
);
528 this.tryMoveFollowup(move, (nextMove
) => {
530 this.playPlusVisual(nextMove
, r
);
532 this.afterPlay(this.moveStack
);