Draft Hiddenqueen, Grasshopper and Knightmate chess (rules unwritten)
[vchess.git] / client / src / variants / Wormhole.js
1 import { ChessRules } from "@/base_rules";
2
3 export const VariantRules = class WormholeRules extends ChessRules {
4 static get HasFlags() {
5 return false;
6 }
7
8 static get HasEnpassant() {
9 return false;
10 }
11
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);
24 }
25
26 getPpath(b) {
27 if (b[0] == 'x') return "Wormhole/hole";
28 return b;
29 }
30
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
157 const finalPieces = x + shiftX == lastRank
158 ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
159 : [V.PAWN];
160 for (let shiftY of [-1, 1]) {
161 const sq = this.getSquareAfter([x,y], [shiftX,shiftY]);
162 if (
163 sq &&
164 this.board[sq[0]][sq[1]] != V.EMPTY &&
165 this.canTake([x, y], [sq[0], sq[1]])
166 ) {
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
204 isAttackedByJump([x, y], colors, piece, steps) {
205 for (let step of steps) {
206 const sq = this.getSquareAfter([x,y], step);
207 if (
208 sq &&
209 this.getPiece(sq[0], sq[1]) === piece &&
210 colors.includes(this.getColor(sq[0], sq[1]))
211 ) {
212 return true;
213 }
214 }
215 return false;
216 }
217
218 isAttackedByPawn([x, y], colors) {
219 for (let c of colors) {
220 const pawnShift = c == "w" ? 1 : -1;
221 for (let i of [-1, 1]) {
222 const sq = this.getSquareAfter([x,y], [pawnShift,i]);
223 if (
224 sq &&
225 this.getPiece(sq[0], sq[1]) == V.PAWN &&
226 this.getColor(sq[0], sq[1]) == c
227 ) {
228 return true;
229 }
230 }
231 }
232 return false;
233 }
234
235 isAttackedByRook(sq, colors) {
236 return this.isAttackedByJump(sq, colors, V.ROOK, V.steps[V.ROOK]);
237 }
238
239 isAttackedByKnight(sq, colors) {
240 // NOTE: knight attack is not symmetric in this variant:
241 // steps order need to be reversed.
242 return this.isAttackedByJump(
243 sq,
244 colors,
245 V.KNIGHT,
246 V.steps[V.KNIGHT].map(s => s.reverse())
247 );
248 }
249
250 isAttackedByBishop(sq, colors) {
251 return this.isAttackedByJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]);
252 }
253
254 isAttackedByQueen(sq, colors) {
255 return this.isAttackedByJump(
256 sq,
257 colors,
258 V.QUEEN,
259 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
260 );
261 }
262
263 isAttackedByKing(sq, colors) {
264 return this.isAttackedByJump(sq, colors, V.KING, V.steps[V.KING]);
265 }
266
267 // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical.
268 // This is a side-effect on board generated by the move.
269 static PlayOnBoard(board, move) {
270 board[move.vanish[0].x][move.vanish[0].y] = V.HOLE;
271 for (let psq of move.appear) board[psq.x][psq.y] = psq.c + psq.p;
272 }
273
274 getCurrentScore() {
275 if (this.atLeastOneMove())
276 return "*";
277 // No valid move: I lose
278 return this.turn == "w" ? "0-1" : "1-0";
279 }
280
281 evalPosition() {
282 let evaluation = 0;
283 for (let i = 0; i < V.size.x; i++) {
284 for (let j = 0; j < V.size.y; j++) {
285 if (![V.EMPTY,V.HOLE].includes(this.board[i][j])) {
286 const sign = this.getColor(i, j) == "w" ? 1 : -1;
287 evaluation += sign * V.VALUES[this.getPiece(i, j)];
288 }
289 }
290 }
291 return evaluation;
292 }
293
294 getNotation(move) {
295 const piece = this.getPiece(move.start.x, move.start.y);
296 // Indicate start square + dest square, because holes distort the board
297 let notation =
298 piece.toUpperCase() +
299 V.CoordsToSquare(move.start) +
300 (move.vanish.length > move.appear.length ? "x" : "") +
301 V.CoordsToSquare(move.end);
302 if (piece == V.PAWN && move.appear[0].p != V.PAWN)
303 // Promotion
304 notation += "=" + move.appear[0].p.toUpperCase();
305 return notation;
306 }
307 };