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