Attempt to fix Eightpieces
[vchess.git] / client / src / variants / Wormhole2.js
1 import { ChessRules } from "@/base_rules";
2
3 export class Wormhole2Rules extends ChessRules {
4
5 static get HasFlags() {
6 return false;
7 }
8
9 static get HasEnpassant() {
10 return false;
11 }
12
13 static get HOLE() {
14 return "xx";
15 }
16
17 static board2fen(b) {
18 if (b[0] == 'x') return 'x';
19 return ChessRules.board2fen(b);
20 }
21
22 static fen2board(f) {
23 if (f == 'x') return V.HOLE;
24 return ChessRules.fen2board(f);
25 }
26
27 getPpath(b) {
28 if (b[0] == 'x') return "Wormhole/hole";
29 return b;
30 }
31
32 static IsGoodPosition(position) {
33 if (position.length == 0) return false;
34 const rows = position.split("/");
35 if (rows.length != V.size.x) return false;
36 let kings = { "k": 0, "K": 0 };
37 for (let row of rows) {
38 let sumElts = 0;
39 for (let i = 0; i < row.length; i++) {
40 if (['K','k'].includes(row[i])) kings[row[i]]++;
41 if (['x'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++;
42 else {
43 const num = parseInt(row[i], 10);
44 if (isNaN(num)) return false;
45 sumElts += num;
46 }
47 }
48 if (sumElts != V.size.y) return false;
49 }
50 if (Object.values(kings).some(v => v != 1)) return false;
51 return true;
52 }
53
54 getSquareAfter(square, movement) {
55 let shift1, shift2;
56 if (Array.isArray(movement[0])) {
57 // A knight
58 shift1 = movement[0];
59 shift2 = movement[1];
60 }
61 else {
62 shift1 = movement;
63 shift2 = null;
64 }
65 const tryMove = (init, shift) => {
66 let step = [
67 shift[0] / Math.abs(shift[0]) || 0,
68 shift[1] / Math.abs(shift[1]) || 0,
69 ];
70 const nbSteps = Math.max(Math.abs(shift[0]), Math.abs(shift[1]));
71 let stepsAchieved = 0;
72 let sq = [init[0] + step[0], init[1] + step[1]];
73 while (V.OnBoard(sq[0],sq[1])) {
74 if (this.board[sq[0]][sq[1]] != V.HOLE)
75 stepsAchieved++;
76 if (stepsAchieved < nbSteps) {
77 sq[0] += step[0];
78 sq[1] += step[1];
79 }
80 else break;
81 }
82 if (stepsAchieved < nbSteps)
83 // The move is impossible
84 return null;
85 return sq;
86 };
87 // First, apply shift1
88 let dest = tryMove(square, shift1);
89 if (dest && shift2)
90 // A knight: apply second shift
91 dest = tryMove(dest, shift2);
92 return dest;
93 }
94
95 // NOTE (TODO?): some extra work done in some function because informations
96 // on one step should ease the computation for a step in the same direction.
97 static get steps() {
98 return {
99 r: [
100 [-1, 0],
101 [1, 0],
102 [0, -1],
103 [0, 1],
104 [-2, 0],
105 [2, 0],
106 [0, -2],
107 [0, 2]
108 ],
109 // Decompose knight movements into one step orthogonal + one diagonal
110 n: [
111 [[0, -1], [-1, -1]],
112 [[0, -1], [1, -1]],
113 [[-1, 0], [-1,-1]],
114 [[-1, 0], [-1, 1]],
115 [[0, 1], [-1, 1]],
116 [[0, 1], [1, 1]],
117 [[1, 0], [1, -1]],
118 [[1, 0], [1, 1]]
119 ],
120 b: [
121 [-1, -1],
122 [-1, 1],
123 [1, -1],
124 [1, 1],
125 [-2, -2],
126 [-2, 2],
127 [2, -2],
128 [2, 2]
129 ],
130 k: [
131 [-1, 0],
132 [1, 0],
133 [0, -1],
134 [0, 1],
135 [-1, -1],
136 [-1, 1],
137 [1, -1],
138 [1, 1]
139 ]
140 };
141 }
142
143 getJumpMoves([x, y], steps) {
144 let moves = [];
145 for (let step of steps) {
146 const sq = this.getSquareAfter([x,y], step);
147 if (sq &&
148 (
149 this.board[sq[0]][sq[1]] == V.EMPTY ||
150 this.canTake([x, y], sq)
151 )
152 ) {
153 moves.push(this.getBasicMove([x, y], sq));
154 }
155 }
156 return moves;
157 }
158
159 // What are the pawn moves from square x,y ?
160 getPotentialPawnMoves([x, y]) {
161 const color = this.turn;
162 let moves = [];
163 const [sizeX, sizeY] = [V.size.x, V.size.y];
164 const shiftX = color == "w" ? -1 : 1;
165 const startRank = color == "w" ? sizeX - 2 : 1;
166 const lastRank = color == "w" ? 0 : sizeX - 1;
167
168 const sq1 = this.getSquareAfter([x,y], [shiftX,0]);
169 if (sq1 && this.board[sq1[0]][y] == V.EMPTY) {
170 // One square forward (cannot be a promotion)
171 moves.push(this.getBasicMove([x, y], [sq1[0], y]));
172 if (x == startRank) {
173 // If two squares after is available, then move is possible
174 const sq2 = this.getSquareAfter([x,y], [2*shiftX,0]);
175 if (sq2 && this.board[sq2[0]][y] == V.EMPTY)
176 // Two squares jump
177 moves.push(this.getBasicMove([x, y], [sq2[0], y]));
178 }
179 }
180 // Captures
181 for (let shiftY of [-1, 1]) {
182 const sq = this.getSquareAfter([x,y], [shiftX,shiftY]);
183 if (
184 !!sq &&
185 this.board[sq[0]][sq[1]] != V.EMPTY &&
186 this.canTake([x, y], [sq[0], sq[1]])
187 ) {
188 const finalPieces = sq[0] == lastRank
189 ? V.PawnSpecs.promotions
190 : [V.PAWN];
191 for (let piece of finalPieces) {
192 moves.push(
193 this.getBasicMove([x, y], [sq[0], sq[1]], {
194 c: color,
195 p: piece
196 })
197 );
198 }
199 }
200 }
201
202 return moves;
203 }
204
205 getPotentialRookMoves(sq) {
206 return this.getJumpMoves(sq, V.steps[V.ROOK]);
207 }
208
209 getPotentialKnightMoves(sq) {
210 return this.getJumpMoves(sq, V.steps[V.KNIGHT]);
211 }
212
213 getPotentialBishopMoves(sq) {
214 return this.getJumpMoves(sq, V.steps[V.BISHOP]);
215 }
216
217 getPotentialQueenMoves(sq) {
218 return this.getJumpMoves(
219 sq,
220 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
221 );
222 }
223
224 getPotentialKingMoves(sq) {
225 return this.getJumpMoves(sq, V.steps[V.KING]);
226 }
227
228 isAttackedByJump([x, y], color, piece, steps) {
229 for (let step of steps) {
230 const sq = this.getSquareAfter([x,y], step);
231 if (
232 sq &&
233 this.getPiece(sq[0], sq[1]) == piece &&
234 this.getColor(sq[0], sq[1]) == color
235 ) {
236 return true;
237 }
238 }
239 return false;
240 }
241
242 isAttackedByPawn([x, y], color) {
243 const pawnShift = (color == "w" ? 1 : -1);
244 for (let i of [-1, 1]) {
245 const sq = this.getSquareAfter([x,y], [pawnShift,i]);
246 if (
247 sq &&
248 this.getPiece(sq[0], sq[1]) == V.PAWN &&
249 this.getColor(sq[0], sq[1]) == color
250 ) {
251 return true;
252 }
253 }
254 return false;
255 }
256
257 isAttackedByRook(sq, color) {
258 return this.isAttackedByJump(sq, color, V.ROOK, V.steps[V.ROOK]);
259 }
260
261 isAttackedByKnight(sq, color) {
262 // NOTE: knight attack is not symmetric in this variant:
263 // steps order need to be reversed.
264 return this.isAttackedByJump(
265 sq,
266 color,
267 V.KNIGHT,
268 V.steps[V.KNIGHT].map(s => s.reverse())
269 );
270 }
271
272 isAttackedByBishop(sq, color) {
273 return this.isAttackedByJump(sq, color, V.BISHOP, V.steps[V.BISHOP]);
274 }
275
276 isAttackedByQueen(sq, color) {
277 return this.isAttackedByJump(
278 sq,
279 color,
280 V.QUEEN,
281 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
282 );
283 }
284
285 isAttackedByKing(sq, color) {
286 return this.isAttackedByJump(sq, color, V.KING, V.steps[V.KING]);
287 }
288
289 // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical.
290 // This is a side-effect on board generated by the move.
291 static PlayOnBoard(board, move) {
292 board[move.vanish[0].x][move.vanish[0].y] = V.HOLE;
293 for (let psq of move.appear) board[psq.x][psq.y] = psq.c + psq.p;
294 }
295
296 getCurrentScore() {
297 if (this.atLeastOneMove()) return "*";
298 // No valid move: I lose
299 return this.turn == "w" ? "0-1" : "1-0";
300 }
301
302 static get SEARCH_DEPTH() {
303 return 2;
304 }
305
306 evalPosition() {
307 let evaluation = 0;
308 for (let i = 0; i < V.size.x; i++) {
309 for (let j = 0; j < V.size.y; j++) {
310 if (![V.EMPTY,V.HOLE].includes(this.board[i][j])) {
311 const sign = this.getColor(i, j) == "w" ? 1 : -1;
312 evaluation += sign * V.VALUES[this.getPiece(i, j)];
313 }
314 }
315 }
316 return evaluation;
317 }
318
319 getNotation(move) {
320 const piece = this.getPiece(move.start.x, move.start.y);
321 // Indicate start square + dest square, because holes distort the board
322 let notation =
323 (piece != V.PAWN ? piece.toUpperCase() : "") +
324 V.CoordsToSquare(move.start) +
325 (move.vanish.length > move.appear.length ? "x" : "") +
326 V.CoordsToSquare(move.end);
327 if (piece == V.PAWN && move.appear[0].p != V.PAWN)
328 // Promotion
329 notation += "=" + move.appear[0].p.toUpperCase();
330 return notation;
331 }
332
333 };