Improve Bario rules description about castling
[vchess.git] / client / src / variants / Fusion.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2
3 export class FusionRules extends ChessRules {
4
5 static get PawnSpecs() {
6 return (
7 Object.assign(
8 { promotions: [V.ROOK, V.KNIGHT, V.BISHOP] },
9 ChessRules.PawnSpecs
10 )
11 );
12 }
13
14 static IsGoodPosition(position) {
15 if (position.length == 0) return false;
16 const rows = position.split("/");
17 if (rows.length != V.size.x) return false;
18 let kings = { "k": 0, "K": 0 };
19 for (let row of rows) {
20 let sumElts = 0;
21 for (let i = 0; i < row.length; i++) {
22 if (['K', 'F', 'G', 'C'].includes(row[i])) kings['K']++;
23 else if (['k', 'f', 'g', 'c'].includes(row[i])) kings['k']++;
24 if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
25 else {
26 const num = parseInt(row[i], 10);
27 if (isNaN(num) || num <= 0) return false;
28 sumElts += num;
29 }
30 }
31 if (sumElts != V.size.y) return false;
32 }
33 if (Object.values(kings).some(v => v != 1)) return false;
34 return true;
35 }
36
37 scanKings(fen) {
38 this.kingPos = { w: [-1, -1], b: [-1, -1] };
39 const fenRows = V.ParseFen(fen).position.split("/");
40 for (let i = 0; i < fenRows.length; i++) {
41 let k = 0;
42 for (let j = 0; j < fenRows[i].length; j++) {
43 const ch_ij = fenRows[i].charAt(j);
44 if (['k', 'f', 'g', 'c'].includes(ch_ij))
45 this.kingPos["b"] = [i, k];
46 else if (['K', 'F', 'G', 'C'].includes(ch_ij))
47 this.kingPos["w"] = [i, k];
48 else {
49 const num = parseInt(fenRows[i].charAt(j), 10);
50 if (!isNaN(num)) k += num - 1;
51 }
52 k++;
53 }
54 }
55 }
56
57 canTake([x1, y1], [x2, y2]) {
58 if (this.getColor(x1, y1) !== this.getColor(x2, y2)) return true;
59 const p1 = this.getPiece(x1, y1);
60 const p2 = this.getPiece(x2, y2);
61 return (
62 p1 != p2 &&
63 [V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) &&
64 [V.KING, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2)
65 );
66 }
67
68 getPpath(b) {
69 if ([V.BN, V.RN, V.KB, V.KR, V.KN].includes(b[1])) return "Fusion/" + b;
70 return b;
71 }
72
73 // Three new pieces: rook+knight, bishop+knight and queen+knight
74 static get RN() {
75 // Marshall
76 return 'm';
77 }
78 static get BN() {
79 // Paladin
80 return 'd';
81 }
82 static get KB() {
83 // Pontiff
84 return 'f';
85 }
86 static get KR() {
87 // Dragon King
88 return 'g';
89 }
90 static get KN() {
91 // Cavalier King
92 return 'c';
93 }
94
95 static get PIECES() {
96 return ChessRules.PIECES.concat([V.RN, V.BN, V.KB, V.KR, V.KN]);
97 }
98
99 static Fusion(p1, p2) {
100 if (p2 == V.KING) {
101 switch (p1) {
102 case V.ROOK: return V.KR;
103 case V.BISHOP: return V.KB;
104 case V.KNIGHT: return V.KN;
105 }
106 }
107 if ([p1, p2].includes(V.KNIGHT)) {
108 if ([p1, p2].includes(V.ROOK)) return V.RN;
109 return V.BN;
110 }
111 // Only remaining combination is rook + bishop = queen
112 return V.QUEEN;
113 }
114
115 getPotentialMovesFrom(sq) {
116 let moves = [];
117 const piece = this.getPiece(sq[0], sq[1]);
118 switch (piece) {
119 case V.RN:
120 moves =
121 super.getPotentialRookMoves(sq).concat(
122 super.getPotentialKnightMoves(sq)).concat(
123 this.getFissionMoves(sq));
124 break;
125 case V.BN:
126 moves =
127 super.getPotentialBishopMoves(sq).concat(
128 super.getPotentialKnightMoves(sq)).concat(
129 this.getFissionMoves(sq));
130 break;
131 case V.KN:
132 moves =
133 super.getPotentialKingMoves(sq).concat(
134 this.getPotentialKingAsKnightMoves(sq)).concat(
135 this.getFissionMoves(sq));
136 break;
137 case V.KB:
138 moves =
139 super.getPotentialKingMoves(sq).concat(
140 this.getPotentialKingAsBishopMoves(sq)).concat(
141 this.getFissionMoves(sq));
142 break;
143 case V.KR:
144 moves =
145 super.getPotentialKingMoves(sq).concat(
146 this.getPotentialKingAsRookMoves(sq)).concat(
147 this.getFissionMoves(sq));
148 break;
149 case V.QUEEN:
150 moves =
151 super.getPotentialQueenMoves(sq).concat(this.getFissionMoves(sq));
152 break;
153 default:
154 moves = super.getPotentialMovesFrom(sq);
155 break;
156 }
157 moves.forEach(m => {
158 if (
159 m.vanish.length == 2 &&
160 m.appear.length == 1 &&
161 m.vanish[0].c == m.vanish[1].c
162 ) {
163 // Augment pieces abilities in case of self-captures
164 m.appear[0].p = V.Fusion(piece, m.vanish[1].p);
165 }
166 });
167 return moves;
168 }
169
170 getSlideNJumpMoves_fission([x, y], moving, staying, steps, oneStep) {
171 let moves = [];
172 const c = this.getColor(x, y);
173 outerLoop: for (let step of steps) {
174 let i = x + step[0];
175 let j = y + step[1];
176 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
177 moves.push(
178 new Move({
179 appear: [
180 new PiPo({ x: i, y: j, c: c, p: moving }),
181 new PiPo({ x: x, y: y, c: c, p: staying }),
182 ],
183 vanish: [
184 new PiPo({ x: x, y: y, c: c, p: this.getPiece(x, y) })
185 ]
186 })
187 );
188 if (!!oneStep) continue outerLoop;
189 i += step[0];
190 j += step[1];
191 }
192 }
193 return moves;
194 }
195
196 getFissionMoves(sq) {
197 // Square attacked by opponent?
198 const color = this.getColor(sq[0], sq[1]);
199 const oppCol = V.GetOppCol(color);
200 if (this.isAttacked(sq, oppCol)) return [];
201 // Ok, fission a priori valid
202 const kSteps = V.steps[V.BISHOP].concat(V.steps[V.BISHOP]);
203 switch (this.getPiece(sq[0], sq[1])) {
204 case V.BN:
205 return (
206 this.getSlideNJumpMoves_fission(
207 sq, V.BISHOP, V.KNIGHT, V.steps[V.BISHOP])
208 .concat(this.getSlideNJumpMoves_fission(
209 sq, V.KNIGHT, V.BISHOP, V.steps[V.KNIGHT], "oneStep"))
210 );
211 case V.RN:
212 return (
213 this.getSlideNJumpMoves_fission(
214 sq, V.ROOK, V.KNIGHT, V.steps[V.ROOK])
215 .concat(this.getSlideNJumpMoves_fission(
216 sq, V.KNIGHT, V.ROOK, V.steps[V.KNIGHT], "oneStep"))
217 );
218 case V.KN:
219 return (
220 this.getSlideNJumpMoves_fission(sq, V.KING, V.KNIGHT, kSteps)
221 .concat(this.getSlideNJumpMoves_fission(
222 sq, V.KNIGHT, V.KING, V.steps[V.KNIGHT], "oneStep"))
223 );
224 case V.KB:
225 return (
226 this.getSlideNJumpMoves_fission(sq, V.KING, V.BISHOP, kSteps)
227 .concat(this.getSlideNJumpMoves_fission(
228 sq, V.BISHOP, V.KING, V.steps[V.BISHOP]))
229 );
230 case V.KR:
231 return (
232 this.getSlideNJumpMoves_fission(sq, V.KING, V.ROOK, kSteps)
233 .concat(this.getSlideNJumpMoves_fission(
234 sq, V.ROOK, V.KING, V.steps[V.ROOK]))
235 );
236 case V.QUEEN:
237 return (
238 this.getSlideNJumpMoves_fission(
239 sq, V.BISHOP, V.ROOK, V.steps[V.BISHOP])
240 .concat(this.getSlideNJumpMoves_fission(
241 sq, V.ROOK, V.BISHOP, V.steps[V.ROOK]))
242 );
243 }
244 }
245
246 intermediateSquaresFromKnightStep(step) {
247 if (step[0] == 2) return [ [1, 0], [1, step[1]] ];
248 if (step[0] == -2) return [ [-1, 0], [-1, step[1]] ];
249 if (step[1] == 2) return [ [0, 1], [step[1], 1] ];
250 // step[1] == -2:
251 return [ [0, -1], [step[1], -1] ];
252 }
253
254 getPotentialKingAsKnightMoves([x, y]) {
255 const oppCol = V.GetOppCol(this.turn);
256 let moves = [];
257 let intermediateOk = {};
258 for (let s of V.steps[V.KNIGHT]) {
259 const [i, j] = [x + s[0], y + s[1]];
260 if (!V.OnBoard(i, j) || this.board[i][j] != V.EMPTY) continue;
261 const iSq = this.intermediateSquaresFromKnightStep(s);
262 let moveOk = false;
263 for (let sq of iSq) {
264 const key = sq[0] + "_" + sq[1];
265 if (Object.keys(intermediateOk).includes(key)) {
266 if (intermediateOk[key]) moveOk = true;
267 }
268 else {
269 moveOk = !this.isAttacked([x + sq[0], y + sq[1]], oppCol);
270 intermediateOk[key] = moveOk;
271 }
272 if (moveOk) break;
273 }
274 if (moveOk) moves.push(this.getBasicMove([x, y], [i, j]));
275 }
276 return moves;
277 }
278
279 getPotentialKingMovesAsSlider([x, y], slider) {
280 const oppCol = V.GetOppCol(this.turn);
281 let moves = [];
282 for (let s of V.steps[slider]) {
283 let [i, j] = [x + s[0], y + s[1]];
284 if (
285 !V.OnBoard(i, j) ||
286 this.board[i][j] != V.EMPTY ||
287 this.isAttacked([i, j], oppCol)
288 ) {
289 continue;
290 }
291 i += s[0];
292 j += s[1];
293 while (
294 V.OnBoard(i, j) &&
295 this.board[i][j] == V.EMPTY &&
296 // TODO: this test will be done twice (also in filterValid())
297 !this.isAttacked([i, j], oppCol)
298 ) {
299 moves.push(this.getBasicMove([x, y], [i, j]));
300 i += s[0];
301 j += s[1];
302 }
303 }
304 return moves;
305 }
306
307 getPotentialKingAsBishopMoves(sq) {
308 return this.getPotentialKingMovesAsSlider(sq, V.BISHOP);
309 }
310
311 getPotentialKingAsRookMoves(sq) {
312 return this.getPotentialKingMovesAsSlider(sq, V.ROOK);
313 }
314
315 isAttacked(sq, color) {
316 return (
317 super.isAttacked(sq, color) ||
318 this.isAttackedByBN(sq, color) ||
319 this.isAttackedByRN(sq, color) ||
320 this.isAttackedByKN(sq, color) ||
321 this.isAttackedByKB(sq, color) ||
322 this.isAttackedByKR(sq, color)
323 );
324 }
325
326 isAttackedByBN(sq, color) {
327 return (
328 this.isAttackedBySlideNJump(sq, color, V.BN, V.steps[V.BISHOP]) ||
329 this.isAttackedBySlideNJump(
330 sq, color, V.BN, V.steps[V.KNIGHT], "oneStep")
331 );
332 }
333
334 isAttackedByRN(sq, color) {
335 return (
336 this.isAttackedBySlideNJump(sq, color, V.RN, V.steps[V.ROOK]) ||
337 this.isAttackedBySlideNJump(
338 sq, color, V.RN, V.steps[V.KNIGHT], "oneStep")
339 );
340 }
341
342 isAttackedByKN(sq, color) {
343 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
344 return (
345 this.isAttackedBySlideNJump(sq, color, V.KN, steps, "oneStep") ||
346 this.isAttackedBySlideNJump(
347 sq, color, V.KN, V.steps[V.KNIGHT], "oneStep")
348 );
349 }
350
351 isAttackedByKB(sq, color) {
352 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
353 return (
354 this.isAttackedBySlideNJump(sq, color, V.KB, steps, "oneStep") ||
355 this.isAttackedBySlideNJump(sq, color, V.KB, V.steps[V.BISHOP])
356 );
357 }
358
359 isAttackedByKR(sq, color) {
360 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
361 return (
362 this.isAttackedBySlideNJump(sq, color, V.KR, steps, "oneStep") ||
363 this.isAttackedBySlideNJump(sq, color, V.KR, V.steps[V.ROOK])
364 );
365 }
366
367 updateCastleFlags(move, piece) {
368 const c = V.GetOppCol(this.turn);
369 const firstRank = (c == "w" ? V.size.x - 1 : 0);
370 const oppCol = this.turn;
371 const oppFirstRank = V.size.x - 1 - firstRank;
372 if (piece == V.KING)
373 this.castleFlags[c] = [V.size.y, V.size.y];
374 else if (
375 move.start.x == firstRank && //our rook moves?
376 this.castleFlags[c].includes(move.start.y)
377 ) {
378 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
379 this.castleFlags[c][flagIdx] = V.size.y;
380 }
381 // Check move endpoint: if my king or any rook position, flags off
382 if (move.end.x == this.kingPos[c][0] && move.end.y == this.kingPos[c][1])
383 this.castleFlags[c] = [V.size.y, V.size.y];
384 else if (
385 move.end.x == firstRank &&
386 this.castleFlags[c].includes(move.end.y)
387 ) {
388 const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
389 this.castleFlags[c][flagIdx] = V.size.y;
390 }
391 else if (
392 move.end.x == oppFirstRank &&
393 this.castleFlags[oppCol].includes(move.end.y)
394 ) {
395 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
396 this.castleFlags[oppCol][flagIdx] = V.size.y;
397 }
398 }
399
400 postPlay(move) {
401 const c = V.GetOppCol(this.turn);
402 const piece = move.appear[0].p;
403 if ([V.KING, V.KN, V.KB, V.KR].includes(piece))
404 this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
405 this.updateCastleFlags(move, piece);
406 }
407
408 postUndo(move) {
409 const c = this.getColor(move.start.x, move.start.y);
410 if ([V.KING, V.KN, V.KB, V.KR].includes(move.appear[0].p))
411 this.kingPos[c] = [move.start.x, move.start.y];
412 }
413
414 static get VALUES() {
415 // Values such that sum of values = value of sum
416 return Object.assign(
417 { m: 8, d: 6, f: 1003, g: 1005, c: 1003 },
418 ChessRules.VALUES
419 );
420 }
421
422 static get SEARCH_DEPTH() {
423 return 2;
424 }
425
426 getNotation(move) {
427 if (move.appear.length == 2 && move.vanish.length == 1) {
428 // Fission (because no capture in this case)
429 return (
430 move.appear[0].p.toUpperCase() + V.CoordsToSquare(move.end) +
431 "/f:" + move.appear[1].p.toUpperCase() + V.CoordsToSquare(move.start)
432 );
433 }
434 let notation = super.getNotation(move);
435 if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p)
436 // Fusion (not from a pawn: handled in ChessRules)
437 notation += "=" + move.appear[0].p.toUpperCase();
438 return notation;
439 }
440
441 };