Commit | Line | Data |
---|---|---|
e023d747 BA |
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 | ); | |
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 | }; |