Simplify Game logic a bit + some advances on Chakart
[vchess.git] / client / src / variants / Chakart.js
1 import { ChessRules } from "@/base_rules";
2
3 export class ChakartRules extends ChessRules {
4 static get CorrConfirm() {
5 // Because of bonus effects
6 return false;
7 }
8
9 static get CanAnalyze() {
10 return false;
11 }
12
13 hoverHighlight(x, y) {
14 if (
15 this.firstMove.appear.length == 0 ||
16 this.firstMove.vanish.length == 0 ||
17 this.board[x][y] != V.EMPTY
18 ) {
19 return false;
20 }
21 const deltaX = Math.abs(this.firstMove.end.x - x);
22 const deltaY = Math.abs(this.firstMove.end.y - y);
23 return (
24 this.subTurn == 2 &&
25 // Condition: rook or bishop move, may capture, but no bonus move
26 [V.ROOK, V.BISHOP].includes(this.firstMove.vanish[0].p) &&
27 (
28 this.firstMove.vanish.length == 1 ||
29 ['w', 'b'].includes(this.firstMove.vanish[1].c)
30 ) &&
31 (
32 this.firstMove.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1 ||
33 this.firstMove.vanish[0].p == V.BISHOP && deltaX + deltaY == 1
34 )
35 );
36 }
37
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 // Doesn't collide with bishop because color 'a'
67 return 'b';
68 }
69 static get BANANA() {
70 return 'n';
71 }
72 static get EGG() {
73 return 'e';
74 }
75 static get MUSHROOM() {
76 return 'm';
77 }
78
79 static get PIECES() {
80 return (
81 ChessRules.PIECES.concat(
82 Object.keys(V.IMMOBILIZE_DECODE)).concat(
83 [V.BANANA, V.BOMB, V.EGG, V.MUSHROOM, V.INVISIBLE_QUEEN])
84 );
85 }
86
87 getPpath(b) {
88 let prefix = "";
89 if (
90 b[0] == 'a' ||
91 b[1] == V.INVISIBLE_QUEEN ||
92 Object.keys(V.IMMOBILIZE_DECODE).includes(b[1])
93 ) {
94 prefix = "Chakart/";
95 }
96 return prefix + b;
97 }
98
99 static ParseFen(fen) {
100 const fenParts = fen.split(" ");
101 return Object.assign(
102 ChessRules.ParseFen(fen),
103 { captured: fenParts[5] }
104 );
105 }
106
107 // King can be l or L (immobilized) --> similar to Alice variant
108 static IsGoodPosition(position) {
109 if (position.length == 0) return false;
110 const rows = position.split("/");
111 if (rows.length != V.size.x) return false;
112 let kings = { "k": 0, "K": 0, 'l': 0, 'L': 0 };
113 for (let row of rows) {
114 let sumElts = 0;
115 for (let i = 0; i < row.length; i++) {
116 if (['K','k','L','l'].includes(row[i])) kings[row[i]]++;
117 if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
118 else {
119 const num = parseInt(row[i]);
120 if (isNaN(num)) return false;
121 sumElts += num;
122 }
123 }
124 if (sumElts != V.size.y) return false;
125 }
126 if (kings['k'] + kings['l'] != 1 || kings['K'] + kings['L'] != 1)
127 return false;
128 return true;
129 }
130
131 static IsGoodFlags(flags) {
132 // 4 for castle + 4 for Peach + Mario w, b
133 return !!flags.match(/^[a-z]{4,4}[01]{4,4}$/);
134 }
135
136 setFlags(fenflags) {
137 super.setFlags(fenflags); //castleFlags
138 this.powerFlags = {
139 w: [...Array(2)], //king can send shell? Queen can be invisible?
140 b: [...Array(2)]
141 };
142 const flags = fenflags.substr(4); //skip first 4 letters, for castle
143 for (let c of ["w", "b"]) {
144 for (let i = 0; i < 2; i++)
145 this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 2) + i) == "1";
146 }
147 }
148
149 aggregateFlags() {
150 return [this.castleFlags, this.powerFlags];
151 }
152
153 disaggregateFlags(flags) {
154 this.castleFlags = flags[0];
155 this.powerFlags = flags[1];
156 }
157
158 getFen() {
159 return super.getFen() + " " + this.getCapturedFen();
160 }
161
162 getFenForRepeat() {
163 return super.getFenForRepeat() + "_" + this.getCapturedFen();
164 }
165
166 getCapturedFen() {
167 let counts = [...Array(10).fill(0)];
168 let i = 0;
169 for (let p of [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.PAWN]) {
170 counts[i] = this.captured["w"][p];
171 counts[5 + i] = this.captured["b"][p];
172 i++;
173 }
174 return counts.join("");
175 }
176
177 setOtherVariables(fen) {
178 super.setOtherVariables(fen);
179 const fenParsed = V.ParseFen(fen);
180 // Initialize captured pieces' counts from FEN
181 this.captured = {
182 w: {
183 [V.ROOK]: parseInt(fenParsed.captured[0]),
184 [V.KNIGHT]: parseInt(fenParsed.captured[1]),
185 [V.BISHOP]: parseInt(fenParsed.captured[2]),
186 [V.QUEEN]: parseInt(fenParsed.captured[3]),
187 [V.PAWN]: parseInt(fenParsed.captured[4]),
188 },
189 b: {
190 [V.ROOK]: parseInt(fenParsed.captured[5]),
191 [V.KNIGHT]: parseInt(fenParsed.captured[6]),
192 [V.BISHOP]: parseInt(fenParsed.captured[7]),
193 [V.QUEEN]: parseInt(fenParsed.captured[8]),
194 [V.PAWN]: parseInt(fenParsed.captured[9]),
195 }
196 };
197 this.subTurn = 1;
198 }
199
200 getFlagsFen() {
201 let fen = super.getFlagsFen();
202 // Add power flags
203 for (let c of ["w", "b"])
204 for (let i = 0; i < 2; i++) fen += (this.powerFlags[c][i] ? "1" : "0");
205 return fen;
206 }
207
208 addBonusYoshi() {
209 // TODO
210 // --> pour bonus toadette, passer "capture" temporairement en "reserve" pour permettre de jouer le coup.
211 }
212
213 getPotentialMovesFrom([x, y]) {
214 // TODO: si banane ou bombe ou... alors return [] ?
215 // TODO: bananes et bombes limitent les déplacements (agissent comme un mur "capturable")
216 // bananes jaunes et rouges ?! (agissant sur une seule couleur ?) --> mauvaise idée.
217 if (this.subTurn == 2) {
218 // TODO: coup compatible avec firstMove
219 }
220 //Détails :
221 //Si une pièce pose quelque chose sur une case ça remplace ce qui y était déjà.
222 // TODO: un-immobilize my immobilized piece at the end of this turn, if any
223 }
224
225 getBasicMove([x1, y1], [x2, y2]) {
226 // NOTE: getBasicMove, ajouter les bonus à vanish array
227 // + déterminer leur effet (si cavalier) ou case (si banane ou bombe)
228 // (L'effet doit être caché au joueur : devrait être OK)
229 }
230
231 getSlideNJumpMpves(sq, steps, oneStep) {
232 // Saut possible par dessus bonus ou champis mais pas bananes ou bombes
233 //==> redefinir isAttackedBySlide et getPotentialSlide...
234 }
235
236 getPotentialPawnMoves(sq) {
237 //Toad: pion
238 // laisse sur sa case de départ un champi turbo permettant à Peach et cavalier et autres pions d'aller
239 // un dep plus loin (evt 2 cases si pion saut initial), et aux pièces arrivant sur cette case de sauter par
240 // dessus une pièce immédiatement adjacente dans leur trajectoire (en atterissant juste derrière).
241 }
242
243 // Coups en 2 temps (si pose possible)
244 getPotentialRookMoves(sq) {
245 //Donkey : tour
246 // pose une banane (optionnel) sur une case adjacente (diagonale) à celle d'arrivée
247 // Si une pièce arrive sur la peau de banane, alors elle effectue un déplacement
248 // aléatoire d'une (2?) case (vertical ou horizontal) depuis sa position finale.
249 }
250
251 // Coups en 2 temps (si pose)
252 getPotentialBishopMoves([x, y]) {
253 //Wario: fou
254 // pose une bombe (optionnel) sur une case orthogonalement adjacente à la case d'arrivée
255 // Si une pièce arrive sur une bombe, alors elle effectue un déplacement diagonal
256 // aléatoire d'une (2?) case depuis sa position finale (juste une case si impossible).
257 }
258
259 getPotentialKnightMoves([x, y]) {
260 //Yoshi: cavalier
261 // laisse sur sa case de départ un bonus aléatoire
262 // (NOTE: certains bonus pourraient ne pas être applicables ==> pion bloqué par exemple)
263 // - i) roi boo(*E*) : échange avec n'importe quelle pièce (choix du joueur, type et/ou couleur différents)
264 // - i*) koopa(*B*) : ramène sur la case initiale
265 // - ii) toadette(*R*) : permet de poser une pièce capturée sur le plateau
266 // (n'importe où sauf 8eme rangée pour les pions)
267 // - ii*) chomp(*W*) : mange la pièce ; si c'est Peach, c'est perdu
268 // - iii) daisy(*T*) : permet de rejouer un coup avec la même pièce --> cumulable si ensuite coup sur bonus Daisy.
269 // - iii*) bowser(*M*) : immobilise la pièce (marquée jaune/rouge), qui ne pourra pas jouer au tour suivant
270 // - iv) luigi(*L*) : fait changer de camp une pièce adverse (aléatoire) (sauf le roi)
271 // - iv*) waluigi(*D*) : fait changer de camp une de nos pièces (aléatoire, sauf le roi)
272 // --> i, ii, iii en deux temps (subTurn 1 & 2)
273 }
274
275 getPotentialQueenMoves(sq) {
276 //Mario: dame
277 // pouvoir "fantôme" : peut effectuer une fois dans la partie un coup non-capturant invisible (=> choix à chaque coup, getPPpath(m) teste m.nvisible...)
278 //wg bg ghost once in the game the queen can make an invisible move --> printed as "?"
279 }
280
281 getPotentialKingMoves(sq) {
282 //Peach: roi
283 // Carapace rouge (disons ^^) jouable une seule fois dans la partie,
284 // au lieu de se déplacer. Capture un ennemi au choix parmi les plus proches,
285 // à condition qu'ils soient visibles (suivant les directions de déplacement d'une dame).
286 // Profite des accélérateurs posés par les pions (+ 1 case : obligatoire).
287 }
288
289 isAttackedBySlideNJump() {
290 // TODO:
291 }
292
293 atLeastOneMove() {
294 // TODO: check that
295 return true;
296 }
297
298 getAllPotentialMoves() {
299 // (Attention: objets pas jouables cf. getPotentialMoves...)
300 }
301
302 play(move) {
303 // TODO: subTurn passe à 2 si arrivée sur bonus cavalier
304 // potentiellement pose (tour, fou) ou si choix (reconnaître i (ok), ii (ok) et iii (si coup normal + pas immobilisé) ?)
305 // voire +2 si plusieurs daisy...
306 // si pièce immobilisée de ma couleur : elle redevient utilisable (changer status fin de play)
307 }
308
309 undo(move) {
310 // TODO: reconnaissance inverse si subTurn == 1 --> juste impossible ==> marquer pendant play (comme DoubleMove1 : move.turn = ...)
311 }
312
313 doClick(square) {
314 if (isNaN(square[0])) return null;
315 // TODO: If subTurn == 2:
316 // if square is empty && firstMove is compatible,
317 // complete the move (banana or bomb or piece exchange).
318 // if square not empty, just complete with empty move
319 const Lf = this.firstMove.length;
320 if (this.subTurn == 2) {
321 if (
322 this.board[square[0]][square[1]] == V.EMPTY &&
323 !this.underCheck(this.turn) &&
324 (La == 0 || !this.oppositeMoves(this.amoves[La-1], this.firstMove[Lf-1]))
325 ) {
326 return {
327 start: { x: -1, y: -1 },
328 end: { x: -1, y: -1 },
329 appear: [],
330 vanish: []
331 };
332 }
333 }
334 return null;
335 }
336
337 postPlay(move) {
338 // TODO: king may also be "chomped"
339 super.updateCastleFlags(move, piece);
340 }
341 postPlay(move) {
342 super.postPlay(move);
343 if (move.vanish.length == 2 && move.appear.length == 1)
344 // Capture: update this.captured
345 this.captured[move.vanish[1].c][move.vanish[1].p]++;
346 }
347
348 postUndo(move) {
349 super.postUndo(move);
350 if (move.vanish.length == 2 && move.appear.length == 1)
351 this.captured[move.vanish[1].c][move.vanish[1].p]--;
352 }
353
354 getCurrentScore() {
355 if (this.kingPos[this.turn][0] < 0)
356 // King captured (or "chomped")
357 return this.turn == "w" ? "0-1" : "1-0";
358 return '*';
359 }
360
361 static GenRandInitFen(randomness) {
362 return (
363 ChessRules.GenRandInitFen(randomness).slice(0, -2) +
364 // Add Peach + Mario flags, re-add en-passant + capture counts
365 "0000 - 0000000000"
366 );
367 }
368
369 getComputerMove() {
370 // TODO: random mover
371 }
372
373 getNotation(move) {
374 // invisibility used? --> move notation Q??
375 }
376 };