Add temporary patch for preset challenges
[vchess.git] / client / src / variants / Teleport1.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class Teleport1Rules extends ChessRules {
5
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);
10 }
11
12 setOtherVariables(fen) {
13 super.setOtherVariables(fen);
14 this.subTurn = 1;
15 this.firstMove = [];
16 }
17
18 canTake([x1, y1], [x2, y2]) {
19 return this.subTurn == 1;
20 }
21
22 canIplay(side, [x, y]) {
23 if (this.subTurn == 2) return (this.board[x][y] == V.EMPTY);
24 return super.canIplay(side, [x, y]);
25 }
26
27 getPPpath(m) {
28 if (
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
33 ) {
34 // Rook teleportation with the king
35 return this.getPpath(m.vanish[1].c + m.vanish[1].p);
36 }
37 return this.getPpath(m.appear[0].c + m.appear[0].p);
38 }
39
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
43 return [];
44 }
45
46 filterValid(moves) {
47 if (this.subTurn == 2) return super.filterValid(moves);
48 const color = this.turn;
49 return moves.filter(m => {
50 this.play(m);
51 let res = false;
52 if (
53 m.vanish.length == 1 ||
54 m.appear.length == 2 ||
55 m.vanish[0].c != m.vanish[1].c
56 ) {
57 // Standard check:
58 res = !this.underCheck(color);
59 }
60 else {
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++) {
64 if (
65 this.board[i][j] == V.EMPTY &&
66 (
67 m.vanish[1].p != V.PAWN ||
68 i != (color == 'w' ? 0 : 7)
69 )
70 ) {
71 const tMove = new Move({
72 appear: [
73 new PiPo({
74 x: i,
75 y: j,
76 c: color,
77 // The dropped piece nature has no importance:
78 p: V.KNIGHT
79 })
80 ],
81 vanish: [],
82 start: { x: -1, y: -1 }
83 });
84 this.play(tMove);
85 const moveOk = !this.underCheck(color);
86 this.undo(tMove);
87 if (moveOk) {
88 res = true;
89 break outerLoop;
90 }
91 }
92 }
93 }
94 }
95 this.undo(m);
96 return res;
97 });
98 }
99
100 getAllValidMoves() {
101 if (this.subTurn == 1) return super.getAllValidMoves();
102 // Subturn == 2: only teleportations
103 let moves = [];
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++) {
108 if (
109 this.board[i][j] == V.EMPTY &&
110 (
111 this.firstMove[L-1].vanish[1].p != V.PAWN ||
112 i != (color == 'w' ? 0 : 7)
113 )
114 ) {
115 const tMove = new Move({
116 appear: [
117 new PiPo({
118 x: i,
119 y: j,
120 c: color,
121 p: this.firstMove[L-1].vanish[1].p
122 })
123 ],
124 vanish: [],
125 start: { x: -1, y: -1 }
126 });
127 this.play(tMove);
128 const moveOk = !this.underCheck(color);
129 this.undo(tMove);
130 if (moveOk) moves.push(tMove);
131 }
132 }
133 }
134 return moves;
135 }
136
137 underCheck(color) {
138 if (this.kingPos[color][0] < 0)
139 // King is being moved:
140 return false;
141 return super.underCheck(color);
142 }
143
144 doClick(square) {
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;
150 if (
151 this.firstMove[L-1].vanish[1].p == V.PAWN &&
152 square[0] == (color == 'w' ? 0 : 7)
153 ) {
154 // Pawns cannot be teleported on last rank
155 return null;
156 }
157 const tMove = new Move({
158 appear: [
159 new PiPo({
160 x: square[0],
161 y: square[1],
162 c: color,
163 p: this.firstMove[L-1].vanish[1].p
164 })
165 ],
166 vanish: [],
167 start: { x: -1, y: -1 }
168 });
169 this.play(tMove);
170 const moveOk = !this.underCheck(color);
171 this.undo(tMove);
172 if (moveOk) return tMove;
173 }
174 return null;
175 }
176
177 play(move) {
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);
182 }
183 V.PlayOnBoard(this.board, move);
184 if (
185 this.subTurn == 2 ||
186 move.vanish.length == 1 ||
187 move.appear.length == 2 ||
188 move.vanish[0].c != move.vanish[1].c
189 ) {
190 this.turn = V.GetOppCol(this.turn);
191 this.subTurn = 1;
192 this.movesCount++;
193 }
194 else this.subTurn = 2;
195 this.postPlay(move);
196 }
197
198 postPlay(move) {
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);
205 }
206
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;
211 if (
212 move.vanish.length == 2 &&
213 move.appear.length == 1 &&
214 move.vanish[0].c == move.vanish[1].c
215 ) {
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);
221 if (
222 move.end.x == firstRank &&
223 this.castleFlags[c].includes(move.end.y)
224 ) {
225 const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
226 this.castleFlags[c][flagIdx] = V.size.y;
227 }
228 }
229 }
230 // Normal check:
231 super.updateCastleFlags(move, move.vanish[0].p, c);
232 }
233
234 undo(move) {
235 this.disaggregateFlags(JSON.parse(move.flags));
236 if (move.vanish.length > 0) {
237 this.epSquares.pop();
238 this.firstMove.pop();
239 }
240 V.UndoOnBoard(this.board, move);
241 if (this.subTurn == 2) this.subTurn = 1;
242 else {
243 this.turn = V.GetOppCol(this.turn);
244 this.movesCount--;
245 this.subTurn = (move.vanish.length > 0 ? 1 : 2);
246 }
247 this.postUndo(move);
248 }
249
250 postUndo(move) {
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];
255 }
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);
260 }
261
262 getComputerMove() {
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();
269 moves.forEach(m => {
270 this.play(m);
271 m.eval = (color == "w" ? -1 : 1) * maxeval;
272 if (
273 m.vanish.length == 2 &&
274 m.appear.length == 1 &&
275 m.vanish[0].c == m.vanish[1].c
276 ) {
277 const moves2 = this.getAllValidMoves();
278 m.next = moves2[0];
279 moves2.forEach(m2 => {
280 this.play(m2);
281 const score = this.getCurrentScore();
282 let mvEval = 0;
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;
289 if (
290 (color == 'w' && mvEval > m.eval) ||
291 (color == 'b' && mvEval < m.eval)
292 ) {
293 m.eval = mvEval;
294 m.next = m2;
295 }
296 this.undo(m2);
297 });
298 }
299 else {
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();
304 }
305 }
306 this.undo(m);
307 });
308 moves.sort((a, b) => {
309 return (color == "w" ? 1 : -1) * (b.eval - a.eval);
310 });
311 let candidates = [0];
312 for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
313 candidates.push(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];
319 }
320
321 getNotation(move) {
322 if (move.vanish.length > 0) return super.getNotation(move);
323 // Teleportation:
324 const piece =
325 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
326 return piece + "@" + V.CoordsToSquare(move.end);
327 }
328
329 };