1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class TeleportRules
extends ChessRules
{
5 setOtherVariables(fen
) {
6 super.setOtherVariables(fen
);
11 canTake([x1
, y1
], [x2
, y2
]) {
12 return this.subTurn
== 1;
17 m
.vanish
.length
== 2 &&
18 m
.appear
.length
== 1 &&
19 m
.vanish
[0].c
== m
.vanish
[1].c
&&
20 m
.appear
[0].p
== V
.KING
22 // Rook teleportation with the king
23 return this.getPpath(m
.vanish
[1].c
+ m
.vanish
[1].p
);
25 return this.getPpath(m
.appear
[0].c
+ m
.appear
[0].p
);
28 getPotentialMovesFrom([x
, y
]) {
29 if (this.subTurn
== 1) return super.getPotentialMovesFrom([x
, y
]);
30 // subTurn == 2: a move is a click, not handled here
35 if (this.subTurn
== 2) return super.filterValid(moves
);
36 const color
= this.turn
;
37 return moves
.filter(m
=> {
41 m
.vanish
.length
== 1 ||
42 m
.appear
.length
== 2 ||
43 m
.vanish
[0].c
!= m
.vanish
[1].c
46 res
= !this.underCheck(color
);
49 // Self-capture: find landing square not resulting in check
50 outerLoop: for (let i
=0; i
<8; i
++) {
51 for (let j
=0; j
<8; j
++) {
53 this.board
[i
][j
] == V
.EMPTY
&&
55 m
.vanish
[1].p
!= V
.PAWN
||
56 i
!= (color
== 'w' ? 0 : 7)
59 const tMove
= new Move({
65 // The dropped piece nature has no importance:
70 start: { x: -1, y: -1 }
73 const moveOk
= !this.underCheck(color
);
89 if (this.subTurn
== 1) return super.getAllValidMoves();
90 // Subturn == 2: only teleportations
92 const L
= this.firstMove
.length
;
93 const color
= this.turn
;
94 for (let i
=0; i
<8; i
++) {
95 for (let j
=0; j
<8; j
++) {
97 this.board
[i
][j
] == V
.EMPTY
&&
99 this.firstMove
[L
-1].vanish
[1].p
!= V
.PAWN
||
100 i
!= (color
== 'w' ? 0 : 7)
103 const tMove
= new Move({
109 p: this.firstMove
[L
-1].vanish
[1].p
113 start: { x: -1, y: -1 }
116 const moveOk
= !this.underCheck(color
);
118 if (moveOk
) moves
.push(tMove
);
126 if (this.kingPos
[color
][0] < 0)
127 // King is being moved:
129 return super.underCheck(color
);
133 if (this.subTurn
== 2)
136 return super.getCurrentScore();
140 if (isNaN(square
[0])) return null;
141 // If subTurn == 2 && square is empty && !underCheck, then teleport
142 if (this.subTurn
== 2 && this.board
[square
[0]][square
[1]] == V
.EMPTY
) {
143 const L
= this.firstMove
.length
;
144 const color
= this.turn
;
146 this.firstMove
[L
-1].vanish
[1].p
== V
.PAWN
&&
147 square
[0] == (color
== 'w' ? 0 : 7)
149 // Pawns cannot be teleported on last rank
152 const tMove
= new Move({
158 p: this.firstMove
[L
-1].vanish
[1].p
162 start: { x: -1, y: -1 }
165 const moveOk
= !this.underCheck(color
);
167 if (moveOk
) return tMove
;
173 move.flags
= JSON
.stringify(this.aggregateFlags());
174 if (move.vanish
.length
> 0) {
175 this.epSquares
.push(this.getEpSquare(move));
176 this.firstMove
.push(move);
178 V
.PlayOnBoard(this.board
, move);
181 move.vanish
.length
== 1 ||
182 move.appear
.length
== 2 ||
183 move.vanish
[0].c
!= move.vanish
[1].c
185 this.turn
= V
.GetOppCol(this.turn
);
189 else this.subTurn
= 2;
194 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
195 // A king is moved: temporarily off board
196 this.kingPos
[move.vanish
[1].c
] = [-1, -1];
197 else if (move.appear
[0].p
== V
.KING
)
198 this.kingPos
[move.appear
[0].c
] = [move.appear
[0].x
, move.appear
[0].y
];
199 this.updateCastleFlags(move);
202 // NOTE: no need to update if castleFlags already off
203 updateCastleFlags(move) {
204 if (move.vanish
.length
== 0) return;
205 const c
= move.vanish
[0].c
;
207 move.vanish
.length
== 2 &&
208 move.appear
.length
== 1 &&
209 move.vanish
[0].c
== move.vanish
[1].c
211 // Self-capture: of the king or a rook?
212 if (move.vanish
[1].p
== V
.KING
)
213 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
214 else if (move.vanish
[1].p
== V
.ROOK
) {
215 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
217 move.end
.x
== firstRank
&&
218 this.castleFlags
[c
].includes(move.end
.y
)
220 const flagIdx
= (move.end
.y
== this.castleFlags
[c
][0] ? 0 : 1);
221 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
227 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
228 const oppCol
= V
.GetOppCol(c
);
229 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
230 if (move.vanish
[0].p
== V
.KING
&& move.appear
.length
> 0)
231 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
233 move.start
.x
== firstRank
&&
234 this.castleFlags
[c
].includes(move.start
.y
)
236 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
237 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
240 move.end
.x
== oppFirstRank
&&
241 this.castleFlags
[oppCol
].includes(move.end
.y
)
243 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
244 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
250 this.disaggregateFlags(JSON
.parse(move.flags
));
251 if (move.vanish
.length
> 0) {
252 this.epSquares
.pop();
253 this.firstMove
.pop();
255 V
.UndoOnBoard(this.board
, move);
256 if (this.subTurn
== 2) this.subTurn
= 1;
258 this.turn
= V
.GetOppCol(this.turn
);
260 this.subTurn
= (move.vanish
.length
> 0 ? 1 : 2);
266 if (move.vanish
.length
== 0) {
267 if (move.appear
[0].p
== V
.KING
)
268 // A king was teleported
269 this.kingPos
[move.appear
[0].c
] = [-1, -1];
271 else if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
272 // A king was (self-)taken
273 this.kingPos
[move.vanish
[1].c
] = [move.end
.x
, move.end
.y
];
274 else super.postUndo(move);
278 let moves
= this.getAllValidMoves();
279 if (moves
.length
== 0) return null;
280 // Custom "search" at depth 1 (for now. TODO?)
281 const maxeval
= V
.INFINITY
;
282 const color
= this.turn
;
283 const initEval
= this.evalPosition();
286 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
288 m
.vanish
.length
== 2 &&
289 m
.appear
.length
== 1 &&
290 m
.vanish
[0].c
== m
.vanish
[1].c
292 const moves2
= this.getAllValidMoves();
294 moves2
.forEach(m2
=> {
296 const score
= this.getCurrentScore();
298 ["1-0", "0-1"].includes(score
)
299 ? (score
== "1-0" ? 1 : -1) * maxeval
300 : (score
== "1/2" ? 0 : initEval
);
302 (color
== 'w' && mvEval
> m
.eval
) ||
303 (color
== 'b' && mvEval
< m
.eval
)
305 // TODO: if many second moves have the same eval, only the
306 // first is kept. Could be randomized.
314 const score
= this.getCurrentScore();
315 if (score
!= "1/2") {
316 if (score
!= "*") m
.eval
= (score
== "1-0" ? 1 : -1) * maxeval
;
317 else m
.eval
= this.evalPosition();
322 moves
.sort((a
, b
) => {
323 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
325 let candidates
= [0];
326 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
328 const mIdx
= candidates
[randInt(candidates
.length
)];
329 if (!moves
[mIdx
].next
) return moves
[mIdx
];
330 const move2
= moves
[mIdx
].next
;
331 delete moves
[mIdx
]["next"];
332 return [moves
[mIdx
], move2
];
336 if (move.vanish
.length
> 0) return super.getNotation(move);
339 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
340 return piece
+ "@" + V
.CoordsToSquare(move.end
);