04c0864a14a69d7503aa2cc4608649445427ee8d
1 import ChessRules
from "/base_rules";
2 import GiveawayRules
from "/variants/Giveaway";
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 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({mode: "suicide"}, true);
81 gr
.genRandInitFen(seed
).slice(0, -1) +
82 // Add Peach + Mario flags + capture counts
83 '{"flags":"1111", "ccount":"000000000000"}'
90 ? "w" + f
.toLowerCase()
91 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
96 // King can send shell? Queen can be invisible?
98 w: {k: false, q: false},
99 b: {k: false, q: false}
101 for (let c
of ['w', 'b']) {
102 for (let p
of ['k', 'q']) {
103 this.powerFlags
[c
][p
] =
104 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
110 return this.powerFlags
;
113 disaggregateFlags(flags
) {
114 this.powerFlags
= flags
;
118 return super.getFen() + " " + this.getCapturedFen();
122 return ['w', 'b'].map(c
=> {
123 return ['k', 'q'].map(p
=> this.powerFlags
[c
][p
] ? "1" : "0").join("");
128 const res
= ['w', 'b'].map(c
=> {
129 Object
.values(this.captured
[c
])
131 return res
[0].concat(res
[1]).join("");
134 setOtherVariables(fenParsed
) {
135 super.setOtherVariables(fenParsed
);
136 // Initialize captured pieces' counts from FEN
137 const allCapts
= fenParsed
.captured
.split("").map(x
=> parseInt(x
, 10));
138 const pieces
= ['p', 'r', 'n', 'b', 'q', 'k'];
140 w: Array
.toObject(pieces
, allCapts
.slice(0, 6)),
141 b: Array
.toObject(pieces
, allCapts
.slice(6, 12))
143 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 // TODO: rethink from here:
181 // queen invisible move, king shell: special functions
183 // prevent pawns from capturing invisible queen (post)
186 //events : playPlusVisual after mouse up, playReceived (include animation) on opp move
187 // ==> if move.cont (banana...) self re-call playPlusVisual (rec ?)
189 // Moving something. Potential effects resolved after playing
190 getPotentialMovesFrom([x
, y
], bonus
) {
192 if (bonus
== "toadette")
193 return this.getDropMovesFrom([x
, y
]);
194 else if (bonus
== "kingboo") {
195 const initPiece
= this.getPiece(x
, y
);
196 const color
= this.getColor(x
, y
);
197 const oppCol
= C
.GetOppCol(color
);
198 // Only allow to swap pieces (TODO: restrict for pawns)
199 for (let i
=0; i
<this.size
.x
; i
++) {
200 for (let j
=0; j
<this.size
.y
; j
++) {
201 if ((i
!= x
|| j
!= y
) && this.board
[i
][j
] != "") {
202 const pstart
= new PiPo({x: x
, y: y
, p: initPiece
, c: color
});
204 let m
= this.getBasicMove([x
, y
], [i
, j
]);
206 new PiPo({x: x
, y: y
, p: this.getPiece(i
, j
), c: oppCol
}));
213 // Normal case (including bonus daisy)
214 switch (this.getPiece(x
, y
)) {
216 moves
= this.getPawnMovesFrom([x
, y
]); //apply promotions
217 // TODO: add mushroom on init square
220 moves
= this.getQueenMovesFrom([x
, y
]);
223 moves
= this.getKingMovesFrom([x
, y
]);
226 moves
= super.getPotentialMovesFrom([x
, y
]);
227 // TODO: add egg on init square
230 moves
= super.getPotentialMovesFrom([x
, y
]);
235 getPawnMovesFrom([x
, y
]) {
236 const color
= this.turn
;
237 const oppCol
= C
.GetOppCol(color
);
238 const shiftX
= (color
== 'w' ? -1 : 1);
239 const firstRank
= (color
== "w" ? this.size
.x
- 1 : 0);
242 this.board
[x
+ shiftX
][y
] == "" ||
243 this.getColor(x
+ shiftX
, y
) == 'a' ||
244 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
248 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
250 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
252 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
253 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
254 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
257 moves
.push(this.getBasicMove({ x: x
, y: y
}, [x
+ 2 * shiftX
, y
]));
260 for (let shiftY
of [-1, 1]) {
263 y
+ shiftY
< sizeY
&&
264 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
265 // Pawns cannot capture invisible queen this way!
266 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
267 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
269 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
275 getQueenMovesFrom(sq
) {
276 const normalMoves
= super.getPotentialQueenMoves(sq
);
277 // If flag allows it, add 'invisible movements'
278 let invisibleMoves
= [];
279 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
280 normalMoves
.forEach(m
=> {
282 m
.appear
.length
== 1 &&
283 m
.vanish
.length
== 1 &&
284 // Only simple non-capturing moves:
287 let im
= JSON
.parse(JSON
.stringify(m
));
288 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
289 im
.end
.noHighlight
= true;
290 invisibleMoves
.push(im
);
294 return normalMoves
.concat(invisibleMoves
);
297 getKingMovesFrom([x
, y
]) {
298 let moves
= super.getPotentialKingMoves([x
, y
]);
299 const color
= this.turn
;
300 // If flag allows it, add 'remote shell captures'
301 if (this.powerFlags
[this.turn
][V
.KING
]) {
302 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
303 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
307 this.board
[i
][j
] == V
.EMPTY
||
308 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
310 this.getColor(i
, j
) == 'a' &&
311 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
318 if (V
.OnBoard(i
, j
)) {
319 const colIJ
= this.getColor(i
, j
);
320 if (colIJ
!= color
) {
321 // May just destroy a bomb or banana:
324 start: { x: x
, y: y
},
329 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
341 // TODO: can merge prePlay into play() ==> no need to distinguish
342 /// if any of my pieces was immobilized, it's not anymore.
343 //if play set a piece immobilized, then mark it
345 if (move.effect
== "toadette")
346 this.reserve
= this.captured
;
348 this.reserve
= { w: {}, b: {} };;
349 const color
= this.turn
;
351 move.vanish
.length
== 2 &&
352 move.vanish
[1].c
!= 'a' &&
353 move.appear
.length
== 1 //avoid king Boo!
355 // Capture: update this.captured
356 let capturedPiece
= move.vanish
[1].p
;
357 if (capturedPiece
== V
.INVISIBLE_QUEEN
)
358 capturedPiece
= V
.QUEEN
;
359 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
360 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
361 this.captured
[move.vanish
[1].c
][capturedPiece
]++;
363 else if (move.vanish
.length
== 0) {
364 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
365 // A piece is back on board
366 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
368 if (move.appear
.length
== 0) {
369 // Three cases: king "shell capture", Chomp or Koopa
370 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
371 // King remote capture:
372 this.powerFlags
[color
][V
.KING
] = false;
373 else if (move.end
.effect
== "chomp")
374 this.captured
[color
][move.vanish
[0].p
]++;
376 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
377 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
378 if (this.subTurn
== 2) return;
381 move.appear
.length
== 0 ||
382 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
384 // Look for an immobilized piece of my color: it can now move
385 for (let i
=0; i
<8; i
++) {
386 for (let j
=0; j
<8; j
++) {
387 if (this.board
[i
][j
] != V
.EMPTY
) {
388 const piece
= this.getPiece(i
, j
);
390 this.getColor(i
, j
) == color
&&
391 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
393 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
394 move.wasImmobilized
= [i
, j
];
400 // Also make opponent invisible queen visible again, if any
401 const oppCol
= V
.GetOppCol(color
);
402 for (let i
=0; i
<8; i
++) {
403 for (let j
=0; j
<8; j
++) {
405 this.board
[i
][j
] != V
.EMPTY
&&
406 this.getColor(i
, j
) == oppCol
&&
407 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
409 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
410 move.wasInvisible
= [i
, j
];
418 this.playOnBoard(move);
419 if (["kingboo", "toadette", "daisy"].includes(move.effect
)) {
420 this.effect
= move.effect
;
424 this.turn
= C
.GetOppCol(this.turn
);
434 // idée : on joue le coup, puis son effet est déterminé, puis la suite (si suite)
435 // est jouée automatiquement ou demande action utilisateur, etc jusqu'à coup terminal.
436 tryMoveFollowup(move, cb
) {
437 if (this.getColor(move.end
.x
, move.end
.y
) == 'a') {
438 // effect, or bonus/malus
439 const endType
= this.getPiece(m
.end
.x
, m
.end
.y
);
442 this.applyRandomBonus(move, cb
);
447 this.getRandomSquare([m
.end
.x
, m
.end
.y
],
449 ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
450 : [[1, 0], [-1, 0], [0, 1], [0, -1]]);
451 const nextMove
= this.getBasicMove([move.end
.x
, move.end
.y
], dest
);
456 // aller dans direction, saut par dessus pièce adverse
457 // ou amie (tjours), new step si roi caval pion
463 applyRandomBonnus(move, cb
) {
464 // TODO: determine bonus/malus, and then
467 // Helper to apply banana/bomb effect
468 getRandomSquare([x
, y
], steps
) {
469 const validSteps
= steps
.filter(s
=> this.onBoard(x
+ s
[0], y
+ s
[1]));
470 const step
= validSteps
[Random
.randInt(validSteps
.length
)];
471 return [x
+ step
[0], y
+ step
[1]];
473 // TODO: turn change indicator ?!
474 playPlusVisual(move, r
) {
475 this.moveStack
.push(move);
477 this.playVisual(move, r
);
479 alert(move.bonus
); //TODO: nicer display
480 this.tryMoveFollowup(move, (nextMove
) => {
482 this.playPlusVisual(nextMove
, r
);
484 this.afterPlay(this.moveStack
);