1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class FusionRules
extends ChessRules
{
5 static get PawnSpecs() {
8 { promotions: [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
] },
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
) {
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
++;
26 const num
= parseInt(row
[i
], 10);
27 if (isNaN(num
) || num
<= 0) return false;
31 if (sumElts
!= V
.size
.y
) return false;
33 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
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
++) {
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
];
49 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
50 if (!isNaN(num
)) k
+= num
- 1;
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
);
63 [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
].includes(p1
) &&
64 [V
.KING
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
].includes(p2
)
69 if ([V
.BN
, V
.RN
, V
.KB
, V
.KR
, V
.KN
].includes(b
[1])) return "Fusion/" + b
;
73 // Three new pieces: rook+knight, bishop+knight and queen+knight
96 return ChessRules
.PIECES
.concat([V
.RN
, V
.BN
, V
.KB
, V
.KR
, V
.KN
]);
99 static Fusion(p1
, p2
) {
102 case V
.ROOK: return V
.KR
;
103 case V
.BISHOP: return V
.KB
;
104 case V
.KNIGHT: return V
.KN
;
107 if ([p1
, p2
].includes(V
.KNIGHT
)) {
108 if ([p1
, p2
].includes(V
.ROOK
)) return V
.RN
;
111 // Only remaining combination is rook + bishop = queen
115 getPotentialMovesFrom(sq
) {
117 const piece
= this.getPiece(sq
[0], sq
[1]);
121 super.getPotentialRookMoves(sq
).concat(
122 super.getPotentialKnightMoves(sq
)).concat(
123 this.getFissionMoves(sq
));
127 super.getPotentialBishopMoves(sq
).concat(
128 super.getPotentialKnightMoves(sq
)).concat(
129 this.getFissionMoves(sq
));
133 super.getPotentialKingMoves(sq
).concat(
134 this.getPotentialKingAsKnightMoves(sq
)).concat(
135 this.getFissionMoves(sq
));
139 super.getPotentialKingMoves(sq
).concat(
140 this.getPotentialKingAsBishopMoves(sq
)).concat(
141 this.getFissionMoves(sq
));
145 super.getPotentialKingMoves(sq
).concat(
146 this.getPotentialKingAsRookMoves(sq
)).concat(
147 this.getFissionMoves(sq
));
151 super.getPotentialQueenMoves(sq
).concat(this.getFissionMoves(sq
));
154 moves
= super.getPotentialMovesFrom(sq
);
159 m
.vanish
.length
== 2 &&
160 m
.appear
.length
== 1 &&
161 m
.vanish
[0].c
== m
.vanish
[1].c
163 // Augment pieces abilities in case of self-captures
164 m
.appear
[0].p
= V
.Fusion(piece
, m
.vanish
[1].p
);
170 getSlideNJumpMoves_fission([x
, y
], moving
, staying
, steps
, oneStep
) {
172 const c
= this.getColor(x
, y
);
173 outerLoop: for (let step
of steps
) {
176 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
180 new PiPo({ x: i
, y: j
, c: c
, p: moving
}),
181 new PiPo({ x: x
, y: y
, c: c
, p: staying
}),
184 new PiPo({ x: x
, y: y
, c: c
, p: this.getPiece(x
, y
) })
188 if (oneStep
) continue outerLoop
;
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])) {
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"))
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"))
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"))
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
]))
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
]))
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
]))
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] ];
251 return [ [0, -1], [step
[1], -1] ];
254 getPotentialKingAsKnightMoves([x
, y
]) {
255 const oppCol
= V
.GetOppCol(this.turn
);
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
);
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;
269 moveOk
= !this.isAttacked([x
+ sq
[0], y
+ sq
[1]], oppCol
);
270 intermediateOk
[key
] = moveOk
;
274 if (moveOk
) moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
279 getPotentialKingMovesAsSlider([x
, y
], slider
) {
280 const oppCol
= V
.GetOppCol(this.turn
);
282 for (let s
of V
.steps
[slider
]) {
283 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
286 this.board
[i
][j
] != V
.EMPTY
||
287 this.isAttacked([i
, j
], oppCol
)
295 this.board
[i
][j
] == V
.EMPTY
&&
296 // TODO: this test will be done twice (also in filterValid())
297 !this.isAttacked([i
, j
], oppCol
)
299 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
307 getPotentialKingAsBishopMoves(sq
) {
308 return this.getPotentialKingMovesAsSlider(sq
, V
.BISHOP
);
311 getPotentialKingAsRookMoves(sq
) {
312 return this.getPotentialKingMovesAsSlider(sq
, V
.ROOK
);
315 isAttacked(sq
, color
) {
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
)
326 isAttackedByBN(sq
, color
) {
328 this.isAttackedBySlideNJump(sq
, color
, V
.BN
, V
.steps
[V
.BISHOP
]) ||
329 this.isAttackedBySlideNJump(sq
, color
, V
.BN
, V
.steps
[V
.KNIGHT
], 1)
333 isAttackedByRN(sq
, color
) {
335 this.isAttackedBySlideNJump(sq
, color
, V
.RN
, V
.steps
[V
.ROOK
]) ||
336 this.isAttackedBySlideNJump(sq
, color
, V
.RN
, V
.steps
[V
.KNIGHT
], 1)
340 isAttackedByKN(sq
, color
) {
341 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
343 this.isAttackedBySlideNJump(sq
, color
, V
.KN
, steps
, 1) ||
344 this.isAttackedBySlideNJump(sq
, color
, V
.KN
, V
.steps
[V
.KNIGHT
], 1)
348 isAttackedByKB(sq
, color
) {
349 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
351 this.isAttackedBySlideNJump(sq
, color
, V
.KB
, steps
, 1) ||
352 this.isAttackedBySlideNJump(sq
, color
, V
.KB
, V
.steps
[V
.BISHOP
])
356 isAttackedByKR(sq
, color
) {
357 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
359 this.isAttackedBySlideNJump(sq
, color
, V
.KR
, steps
, 1) ||
360 this.isAttackedBySlideNJump(sq
, color
, V
.KR
, V
.steps
[V
.ROOK
])
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
;
370 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
372 move.start
.x
== firstRank
&& //our rook moves?
373 this.castleFlags
[c
].includes(move.start
.y
)
375 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
376 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
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
];
382 move.end
.x
== firstRank
&&
383 this.castleFlags
[c
].includes(move.end
.y
)
385 const flagIdx
= (move.end
.y
== this.castleFlags
[c
][0] ? 0 : 1);
386 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
389 move.end
.x
== oppFirstRank
&&
390 this.castleFlags
[oppCol
].includes(move.end
.y
)
392 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
393 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
398 const c
= V
.GetOppCol(this.turn
);
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
)
403 if (move.kingMove
) this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
404 this.updateCastleFlags(move, move.appear
[0].p
);
408 const c
= this.getColor(move.start
.x
, move.start
.y
);
409 if (!!move.kingMove
) this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
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 },
420 static get SEARCH_DEPTH() {
425 if (move.appear
.length
== 2 && move.vanish
.length
== 1) {
426 // Fission (because no capture in this case)
428 move.appear
[0].p
.toUpperCase() + V
.CoordsToSquare(move.end
) +
429 "/f:" + move.appear
[1].p
.toUpperCase() + V
.CoordsToSquare(move.start
)
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();