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