1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class Teleport1Rules
extends ChessRules
{
6 hoverHighlight([x
, y
]) {
7 // Testing move validity results in an infinite update loop.
8 // TODO: find a way to test validity anyway.
9 return (this.subTurn
== 2 && this.board
[x
][y
] == V
.EMPTY
);
12 setOtherVariables(fen
) {
13 super.setOtherVariables(fen
);
18 canTake([x1
, y1
], [x2
, y2
]) {
19 return this.subTurn
== 1;
22 canIplay(side
, [x
, y
]) {
23 if (this.subTurn
== 2) return (this.board
[x
][y
] == V
.EMPTY
);
24 return super.canIplay(side
, [x
, y
]);
29 m
.vanish
.length
== 2 &&
30 m
.appear
.length
== 1 &&
31 m
.vanish
[0].c
== m
.vanish
[1].c
&&
32 m
.appear
[0].p
== V
.KING
34 // Rook teleportation with the king
35 return this.getPpath(m
.vanish
[1].c
+ m
.vanish
[1].p
);
37 return this.getPpath(m
.appear
[0].c
+ m
.appear
[0].p
);
40 getPotentialMovesFrom([x
, y
]) {
41 if (this.subTurn
== 1) return super.getPotentialMovesFrom([x
, y
]);
42 // subTurn == 2: a move is a click, not handled here
47 if (this.subTurn
== 2) return super.filterValid(moves
);
48 const color
= this.turn
;
49 return moves
.filter(m
=> {
53 m
.vanish
.length
== 1 ||
54 m
.appear
.length
== 2 ||
55 m
.vanish
[0].c
!= m
.vanish
[1].c
58 res
= !this.underCheck(color
);
61 // Self-capture: find landing square not resulting in check
62 outerLoop: for (let i
=0; i
<8; i
++) {
63 for (let j
=0; j
<8; j
++) {
65 this.board
[i
][j
] == V
.EMPTY
&&
67 m
.vanish
[1].p
!= V
.PAWN
||
68 i
!= (color
== 'w' ? 0 : 7)
71 const tMove
= new Move({
77 // The dropped piece nature has no importance:
82 start: { x: -1, y: -1 }
85 const moveOk
= !this.underCheck(color
);
101 if (this.subTurn
== 1) return super.getAllValidMoves();
102 // Subturn == 2: only teleportations
104 const L
= this.firstMove
.length
;
105 const color
= this.turn
;
106 for (let i
=0; i
<8; i
++) {
107 for (let j
=0; j
<8; j
++) {
109 this.board
[i
][j
] == V
.EMPTY
&&
111 this.firstMove
[L
-1].vanish
[1].p
!= V
.PAWN
||
112 i
!= (color
== 'w' ? 0 : 7)
115 const tMove
= new Move({
121 p: this.firstMove
[L
-1].vanish
[1].p
125 start: { x: -1, y: -1 }
128 const moveOk
= !this.underCheck(color
);
130 if (moveOk
) moves
.push(tMove
);
138 if (this.kingPos
[color
][0] < 0)
139 // King is being moved:
141 return super.underCheck(color
);
145 if (isNaN(square
[0])) return null;
146 // If subTurn == 2 && square is empty && !underCheck, then teleport
147 if (this.subTurn
== 2 && this.board
[square
[0]][square
[1]] == V
.EMPTY
) {
148 const L
= this.firstMove
.length
;
149 const color
= this.turn
;
151 this.firstMove
[L
-1].vanish
[1].p
== V
.PAWN
&&
152 square
[0] == (color
== 'w' ? 0 : 7)
154 // Pawns cannot be teleported on last rank
157 const tMove
= new Move({
163 p: this.firstMove
[L
-1].vanish
[1].p
167 start: { x: -1, y: -1 }
170 const moveOk
= !this.underCheck(color
);
172 if (moveOk
) return tMove
;
178 move.flags
= JSON
.stringify(this.aggregateFlags());
179 if (move.vanish
.length
> 0) {
180 this.epSquares
.push(this.getEpSquare(move));
181 this.firstMove
.push(move);
183 V
.PlayOnBoard(this.board
, move);
186 move.vanish
.length
== 1 ||
187 move.appear
.length
== 2 ||
188 move.vanish
[0].c
!= move.vanish
[1].c
190 this.turn
= V
.GetOppCol(this.turn
);
194 else this.subTurn
= 2;
199 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
200 // A king is moved: temporarily off board
201 this.kingPos
[move.vanish
[1].c
] = [-1, -1];
202 else if (move.appear
[0].p
== V
.KING
)
203 this.kingPos
[move.appear
[0].c
] = [move.appear
[0].x
, move.appear
[0].y
];
204 if (move.vanish
.length
> 0) this.updateCastleFlags(move);
207 // NOTE: no need to update if castleFlags already off
208 updateCastleFlags(move) {
209 if (move.vanish
.length
== 0) return;
210 const c
= move.vanish
[0].c
;
212 move.vanish
.length
== 2 &&
213 move.appear
.length
== 1 &&
214 move.vanish
[0].c
== move.vanish
[1].c
216 // Self-capture: of the king or a rook?
217 if (move.vanish
[1].p
== V
.KING
)
218 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
219 else if (move.vanish
[1].p
== V
.ROOK
) {
220 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
222 move.end
.x
== firstRank
&&
223 this.castleFlags
[c
].includes(move.end
.y
)
225 const flagIdx
= (move.end
.y
== this.castleFlags
[c
][0] ? 0 : 1);
226 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
231 super.updateCastleFlags(move, move.vanish
[0].p
, c
);
235 this.disaggregateFlags(JSON
.parse(move.flags
));
236 if (move.vanish
.length
> 0) {
237 this.epSquares
.pop();
238 this.firstMove
.pop();
240 V
.UndoOnBoard(this.board
, move);
241 if (this.subTurn
== 2) this.subTurn
= 1;
243 this.turn
= V
.GetOppCol(this.turn
);
245 this.subTurn
= (move.vanish
.length
> 0 ? 1 : 2);
251 if (move.vanish
.length
== 0) {
252 if (move.appear
[0].p
== V
.KING
)
253 // A king was teleported
254 this.kingPos
[move.appear
[0].c
] = [-1, -1];
256 else if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
257 // A king was (self-)taken
258 this.kingPos
[move.vanish
[1].c
] = [move.end
.x
, move.end
.y
];
259 else super.postUndo(move);
263 let moves
= this.getAllValidMoves();
264 if (moves
.length
== 0) return null;
265 // Custom "search" at depth 1 (for now. TODO?)
266 const maxeval
= V
.INFINITY
;
267 const color
= this.turn
;
268 const initEval
= this.evalPosition();
271 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
273 m
.vanish
.length
== 2 &&
274 m
.appear
.length
== 1 &&
275 m
.vanish
[0].c
== m
.vanish
[1].c
277 const moves2
= this.getAllValidMoves();
279 moves2
.forEach(m2
=> {
281 const score
= this.getCurrentScore();
283 if (["1-0", "0-1"].includes(score
))
284 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
285 else if (score
== "*")
286 // Add small fluctuations to avoid dropping pieces always on the
287 // first square available.
288 mvEval
= initEval
+ 0.05 - Math
.random() / 10;
290 (color
== 'w' && mvEval
> m
.eval
) ||
291 (color
== 'b' && mvEval
< m
.eval
)
300 const score
= this.getCurrentScore();
301 if (score
!= "1/2") {
302 if (score
!= "*") m
.eval
= (score
== "1-0" ? 1 : -1) * maxeval
;
303 else m
.eval
= this.evalPosition();
308 moves
.sort((a
, b
) => {
309 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
311 let candidates
= [0];
312 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
314 const mIdx
= candidates
[randInt(candidates
.length
)];
315 if (!moves
[mIdx
].next
) return moves
[mIdx
];
316 const move2
= moves
[mIdx
].next
;
317 delete moves
[mIdx
]["next"];
318 return [moves
[mIdx
], move2
];
322 if (move.vanish
.length
> 0) return super.getNotation(move);
325 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
326 return piece
+ "@" + V
.CoordsToSquare(move.end
);