Some progress on Chakart
[xogo.git] / variants / Chakart / class.js
CommitLineData
2b9b90da 1import ChessRules from "/base_rules";
f382c57b 2import GiveawayRules from "/variants/Giveaway";
8f57fbf2
BA
3import { ArrayFun } from "/utils/array.js";
4import { Random } from "/utils/alea.js";
5import PiPo from "/utils/PiPo.js";
6import Move from "/utils/Move.js";
f382c57b 7
f8b43ef7
BA
8export 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};