Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Fusion.js
CommitLineData
e023d747
BA
1import { ChessRules, Move, PiPo } from "@/base_rules";
2
3export 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 );
4313762d 188 if (oneStep) continue outerLoop;
e023d747
BA
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]) ||
4313762d 329 this.isAttackedBySlideNJump(sq, color, V.BN, V.steps[V.KNIGHT], 1)
e023d747
BA
330 );
331 }
332
333 isAttackedByRN(sq, color) {
334 return (
335 this.isAttackedBySlideNJump(sq, color, V.RN, V.steps[V.ROOK]) ||
4313762d 336 this.isAttackedBySlideNJump(sq, color, V.RN, V.steps[V.KNIGHT], 1)
e023d747
BA
337 );
338 }
339
340 isAttackedByKN(sq, color) {
341 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
342 return (
4313762d
BA
343 this.isAttackedBySlideNJump(sq, color, V.KN, steps, 1) ||
344 this.isAttackedBySlideNJump(sq, color, V.KN, V.steps[V.KNIGHT], 1)
e023d747
BA
345 );
346 }
347
348 isAttackedByKB(sq, color) {
349 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
350 return (
4313762d 351 this.isAttackedBySlideNJump(sq, color, V.KB, steps, 1) ||
e023d747
BA
352 this.isAttackedBySlideNJump(sq, color, V.KB, V.steps[V.BISHOP])
353 );
354 }
355
356 isAttackedByKR(sq, color) {
357 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
358 return (
4313762d 359 this.isAttackedBySlideNJump(sq, color, V.KR, steps, 1) ||
e023d747
BA
360 this.isAttackedBySlideNJump(sq, color, V.KR, V.steps[V.ROOK])
361 );
362 }
363
364 updateCastleFlags(move, piece) {
365 const c = V.GetOppCol(this.turn);
366 const firstRank = (c == "w" ? V.size.x - 1 : 0);
367 const oppCol = this.turn;
368 const oppFirstRank = V.size.x - 1 - firstRank;
369 if (piece == V.KING)
370 this.castleFlags[c] = [V.size.y, V.size.y];
371 else if (
372 move.start.x == firstRank && //our rook moves?
373 this.castleFlags[c].includes(move.start.y)
374 ) {
375 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
376 this.castleFlags[c][flagIdx] = V.size.y;
377 }
378 // Check move endpoint: if my king or any rook position, flags off
379 if (move.end.x == this.kingPos[c][0] && move.end.y == this.kingPos[c][1])
380 this.castleFlags[c] = [V.size.y, V.size.y];
381 else if (
382 move.end.x == firstRank &&
383 this.castleFlags[c].includes(move.end.y)
384 ) {
385 const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
386 this.castleFlags[c][flagIdx] = V.size.y;
387 }
388 else if (
389 move.end.x == oppFirstRank &&
390 this.castleFlags[oppCol].includes(move.end.y)
391 ) {
392 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
393 this.castleFlags[oppCol][flagIdx] = V.size.y;
394 }
395 }
396
397 postPlay(move) {
398 const c = V.GetOppCol(this.turn);
7097b736
BA
399 move.kingMove = (
400 [V.KING, V.KN, V.KB, V.KR].includes(move.appear[0].p) &&
401 [V.KING, V.KN, V.KB, V.KR].includes(move.vanish[0].p)
402 );
403 if (move.kingMove) this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
404 this.updateCastleFlags(move, move.appear[0].p);
e023d747
BA
405 }
406
407 postUndo(move) {
408 const c = this.getColor(move.start.x, move.start.y);
7097b736 409 if (!!move.kingMove) this.kingPos[c] = [move.start.x, move.start.y];
e023d747
BA
410 }
411
412 static get VALUES() {
413 // Values such that sum of values = value of sum
414 return Object.assign(
415 { m: 8, d: 6, f: 1003, g: 1005, c: 1003 },
416 ChessRules.VALUES
417 );
418 }
419
22c591c5
BA
420 static get SEARCH_DEPTH() {
421 return 2;
422 }
423
e023d747
BA
424 getNotation(move) {
425 if (move.appear.length == 2 && move.vanish.length == 1) {
426 // Fission (because no capture in this case)
427 return (
428 move.appear[0].p.toUpperCase() + V.CoordsToSquare(move.end) +
429 "/f:" + move.appear[1].p.toUpperCase() + V.CoordsToSquare(move.start)
430 );
431 }
432 let notation = super.getNotation(move);
433 if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p)
434 // Fusion (not from a pawn: handled in ChessRules)
435 notation += "=" + move.appear[0].p.toUpperCase();
436 return notation;
437 }
438
439};