Check variants. All OK except Dark (bug), Checkered (missing internal moves stack...
[vchess.git] / client / src / variants / Grand.js
CommitLineData
0c3fe8a6
BA
1import { ChessRules } from "@/base_rules";
2import { ArrayFun } from "@/utils/array";
3import { randInt } from "@/utils/alea";
4
92342261
BA
5// NOTE: initial setup differs from the original; see
6// https://www.chessvariants.com/large.dir/freeling.html
0c3fe8a6 7export const VariantRules = class GrandRules extends ChessRules
a37076f1
BA
8{
9 static getPpath(b)
10 {
c6052161 11 return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
a37076f1
BA
12 }
13
2d7194bd 14 static IsGoodFen(fen)
c6052161 15 {
2d7194bd
BA
16 if (!ChessRules.IsGoodFen(fen))
17 return false;
18 const fenParsed = V.ParseFen(fen);
19 // 5) Check captures
69f3d801 20 if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
2d7194bd
BA
21 return false;
22 return true;
23 }
24
6e62b1c7
BA
25 static IsGoodEnpassant(enpassant)
26 {
27 if (enpassant != "-")
28 {
29 const squares = enpassant.split(",");
30 if (squares.length > 2)
31 return false;
32 for (let sq of squares)
33 {
34 const ep = V.SquareToCoords(sq);
35 if (isNaN(ep.x) || !V.OnBoard(ep))
36 return false;
37 }
38 }
39 return true;
40 }
41
fb6ceeff
BA
42 static ParseFen(fen)
43 {
44 const fenParts = fen.split(" ");
45 return Object.assign(
46 ChessRules.ParseFen(fen),
b6487fb9 47 { captured: fenParts[5] }
fb6ceeff
BA
48 );
49 }
50
2d7194bd
BA
51 getFen()
52 {
53 return super.getFen() + " " + this.getCapturedFen();
54 }
55
56 getCapturedFen()
57 {
8d61fc4a 58 let counts = [...Array(14).fill(0)];
69f3d801
BA
59 let i = 0;
60 for (let j=0; j<V.PIECES.length; j++)
2d7194bd 61 {
69f3d801
BA
62 if (V.PIECES[j] == V.KING) //no king captured
63 continue;
2d7194bd 64 counts[i] = this.captured["w"][V.PIECES[i]];
69f3d801
BA
65 counts[7+i] = this.captured["b"][V.PIECES[i]];
66 i++;
2d7194bd
BA
67 }
68 return counts.join("");
69 }
70
71 setOtherVariables(fen)
72 {
73 super.setOtherVariables(fen);
74 const fenParsed = V.ParseFen(fen);
75 // Initialize captured pieces' counts from FEN
76 this.captured =
77 {
78 "w":
79 {
80 [V.PAWN]: parseInt(fenParsed.captured[0]),
81 [V.ROOK]: parseInt(fenParsed.captured[1]),
82 [V.KNIGHT]: parseInt(fenParsed.captured[2]),
83 [V.BISHOP]: parseInt(fenParsed.captured[3]),
84 [V.QUEEN]: parseInt(fenParsed.captured[4]),
69f3d801
BA
85 [V.MARSHALL]: parseInt(fenParsed.captured[5]),
86 [V.CARDINAL]: parseInt(fenParsed.captured[6]),
2d7194bd
BA
87 },
88 "b":
89 {
69f3d801
BA
90 [V.PAWN]: parseInt(fenParsed.captured[7]),
91 [V.ROOK]: parseInt(fenParsed.captured[8]),
92 [V.KNIGHT]: parseInt(fenParsed.captured[9]),
93 [V.BISHOP]: parseInt(fenParsed.captured[10]),
94 [V.QUEEN]: parseInt(fenParsed.captured[11]),
95 [V.MARSHALL]: parseInt(fenParsed.captured[12]),
96 [V.CARDINAL]: parseInt(fenParsed.captured[13]),
2d7194bd
BA
97 }
98 };
c6052161
BA
99 }
100
0b7d99ec 101 static get size() { return {x:10,y:10}; }
c6052161 102
a37076f1
BA
103 static get MARSHALL() { return 'm'; } //rook+knight
104 static get CARDINAL() { return 'c'; } //bishop+knight
105
2d7194bd
BA
106 static get PIECES()
107 {
7931e479
BA
108 return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
109 }
110
2d7194bd
BA
111 // There may be 2 enPassant squares (if pawn jump 3 squares)
112 getEnpassantFen()
113 {
114 const L = this.epSquares.length;
115 if (!this.epSquares[L-1])
116 return "-"; //no en-passant
117 let res = "";
118 this.epSquares[L-1].forEach(sq => {
119 res += V.CoordsToSquare(sq) + ",";
120 });
121 return res.slice(0,-1); //remove last comma
122 }
123
c6052161 124 // En-passant after 2-sq or 3-sq jumps
2d7194bd 125 getEpSquare(moveOrSquare)
a37076f1 126 {
2d7194bd
BA
127 if (!moveOrSquare)
128 return undefined;
129 if (typeof moveOrSquare === "string")
130 {
131 const square = moveOrSquare;
132 if (square == "-")
133 return undefined;
134 let res = [];
135 square.split(",").forEach(sq => {
136 res.push(V.SquareToCoords(sq));
137 });
138 return res;
139 }
140 // Argument is a move:
141 const move = moveOrSquare;
a37076f1 142 const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
0b7d99ec 143 if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
a37076f1 144 {
c6052161
BA
145 const step = (ex-sx) / Math.abs(ex-sx);
146 let res = [{
147 x: sx + step,
a37076f1 148 y: sy
c6052161
BA
149 }];
150 if (sx + 2*step != ex) //3-squares move
151 {
152 res.push({
153 x: sx + 2*step,
154 y: sy
155 });
156 }
157 return res;
a37076f1
BA
158 }
159 return undefined; //default
160 }
161
162 getPotentialMovesFrom([x,y])
163 {
164 switch (this.getPiece(x,y))
165 {
0b7d99ec 166 case V.MARSHALL:
a37076f1 167 return this.getPotentialMarshallMoves([x,y]);
0b7d99ec 168 case V.CARDINAL:
a37076f1
BA
169 return this.getPotentialCardinalMoves([x,y]);
170 default:
171 return super.getPotentialMovesFrom([x,y])
172 }
173 }
174
c6052161
BA
175 // Special pawn rules: promotions to captured friendly pieces,
176 // optional on ranks 8-9 and mandatory on rank 10.
a37076f1
BA
177 getPotentialPawnMoves([x,y])
178 {
c6052161
BA
179 const color = this.turn;
180 let moves = [];
0b7d99ec 181 const [sizeX,sizeY] = [V.size.x,V.size.y];
69f3d801 182 const shiftX = (color == "w" ? -1 : 1);
cf130369
BA
183 const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
184 const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
69f3d801
BA
185 const promotionPieces =
186 [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
c6052161 187
69f3d801
BA
188 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
189 let finalPieces = undefined;
190 if (lastRanks.includes(x + shiftX))
c6052161 191 {
69f3d801
BA
192 finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
193 if (x + shiftX != lastRanks[0])
194 finalPieces.push(V.PAWN);
195 }
196 else
197 finalPieces = [V.PAWN];
198 if (this.board[x+shiftX][y] == V.EMPTY)
199 {
200 // One square forward
201 for (let piece of finalPieces)
202 moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
203 if (startRanks.includes(x))
c6052161 204 {
69f3d801 205 if (this.board[x+2*shiftX][y] == V.EMPTY)
c6052161
BA
206 {
207 // Two squares jump
69f3d801
BA
208 moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
209 if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
c6052161 210 {
69f3d801
BA
211 // Three squares jump
212 moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
c6052161
BA
213 }
214 }
215 }
c6052161 216 }
69f3d801
BA
217 // Captures
218 for (let shiftY of [-1,1])
c6052161 219 {
69f3d801
BA
220 if (y + shiftY >= 0 && y + shiftY < sizeY
221 && this.board[x+shiftX][y+shiftY] != V.EMPTY
222 && this.canTake([x,y], [x+shiftX,y+shiftY]))
223 {
224 for (let piece of finalPieces)
92342261 225 {
69f3d801
BA
226 moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
227 {c:color,p:piece}));
92342261 228 }
69f3d801 229 }
c6052161
BA
230 }
231
232 // En passant
233 const Lep = this.epSquares.length;
69f3d801 234 const epSquare = this.epSquares[Lep-1];
c6052161
BA
235 if (!!epSquare)
236 {
237 for (let epsq of epSquare)
238 {
239 // TODO: some redundant checks
69f3d801 240 if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
c6052161 241 {
69f3d801
BA
242 var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
243 // WARNING: the captured pawn may be diagonally behind us,
244 // if it's a 3-squares jump and we take on 1st passing square
245 const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
c6052161 246 enpassantMove.vanish.push({
69f3d801 247 x: px,
375ecdd1 248 y: epsq.y,
c6052161 249 p: 'p',
69f3d801 250 c: this.getColor(px,epsq.y)
c6052161
BA
251 });
252 moves.push(enpassantMove);
253 }
254 }
255 }
256
257 return moves;
a37076f1
BA
258 }
259
8a196305
BA
260 // TODO: different castle?
261
a37076f1
BA
262 getPotentialMarshallMoves(sq)
263 {
a37076f1
BA
264 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
265 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
266 }
267
268 getPotentialCardinalMoves(sq)
269 {
a37076f1
BA
270 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
271 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
272 }
273
274 isAttacked(sq, colors)
275 {
c6052161 276 return super.isAttacked(sq, colors)
a37076f1
BA
277 || this.isAttackedByMarshall(sq, colors)
278 || this.isAttackedByCardinal(sq, colors);
279 }
280
281 isAttackedByMarshall(sq, colors)
282 {
a37076f1 283 return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
92342261
BA
284 || this.isAttackedBySlideNJump(
285 sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
a37076f1
BA
286 }
287
288 isAttackedByCardinal(sq, colors)
289 {
a37076f1 290 return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
92342261
BA
291 || this.isAttackedBySlideNJump(
292 sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
a37076f1
BA
293 }
294
a6abf094 295 updateVariables(move)
c6052161 296 {
a6abf094 297 super.updateVariables(move);
69f3d801 298 if (move.vanish.length == 2 && move.appear.length == 1)
c6052161 299 {
2d7194bd
BA
300 // Capture: update this.captured
301 this.captured[move.vanish[1].c][move.vanish[1].p]++;
c6052161 302 }
69f3d801
BA
303 if (move.vanish[0].p != move.appear[0].p)
304 {
305 // Promotion: update this.captured
306 this.captured[move.vanish[0].c][move.appear[0].p]--;
307 }
c6052161
BA
308 }
309
a6abf094 310 unupdateVariables(move)
c6052161 311 {
a6abf094 312 super.unupdateVariables(move);
69f3d801 313 if (move.vanish.length == 2 && move.appear.length == 1)
2d7194bd 314 this.captured[move.vanish[1].c][move.vanish[1].p]--;
69f3d801
BA
315 if (move.vanish[0].p != move.appear[0].p)
316 this.captured[move.vanish[0].c][move.appear[0].p]++;
c6052161
BA
317 }
318
2d7194bd
BA
319 static get VALUES()
320 {
a37076f1
BA
321 return Object.assign(
322 ChessRules.VALUES,
323 {'c': 5, 'm': 7} //experimental
324 );
325 }
326
3c09dc49
BA
327 static get SEARCH_DEPTH() { return 2; }
328
2d7194bd 329 // TODO: this function could be generalized and shared better (how ?!...)
a37076f1
BA
330 static GenRandInitFen()
331 {
92342261 332 let pieces = { "w": new Array(10), "b": new Array(10) };
c6052161 333 // Shuffle pieces on first and last rank
92342261 334 for (let c of ["w","b"])
c6052161 335 {
0c3fe8a6 336 let positions = ArrayFun.range(10);
c6052161
BA
337
338 // Get random squares for bishops
656b1878 339 let randIndex = 2 * randInt(5);
c6052161
BA
340 let bishop1Pos = positions[randIndex];
341 // The second bishop must be on a square of different color
656b1878 342 let randIndex_tmp = 2 * randInt(5) + 1;
c6052161
BA
343 let bishop2Pos = positions[randIndex_tmp];
344 // Remove chosen squares
345 positions.splice(Math.max(randIndex,randIndex_tmp), 1);
346 positions.splice(Math.min(randIndex,randIndex_tmp), 1);
347
348 // Get random squares for knights
656b1878 349 randIndex = randInt(8);
c6052161
BA
350 let knight1Pos = positions[randIndex];
351 positions.splice(randIndex, 1);
656b1878 352 randIndex = randInt(7);
c6052161
BA
353 let knight2Pos = positions[randIndex];
354 positions.splice(randIndex, 1);
355
356 // Get random square for queen
656b1878 357 randIndex = randInt(6);
c6052161
BA
358 let queenPos = positions[randIndex];
359 positions.splice(randIndex, 1);
360
361 // ...random square for marshall
656b1878 362 randIndex = randInt(5);
c6052161
BA
363 let marshallPos = positions[randIndex];
364 positions.splice(randIndex, 1);
365
366 // ...random square for cardinal
656b1878 367 randIndex = randInt(4);
c6052161
BA
368 let cardinalPos = positions[randIndex];
369 positions.splice(randIndex, 1);
370
371 // Rooks and king positions are now fixed, because of the ordering rook-king-rook
372 let rook1Pos = positions[0];
373 let kingPos = positions[1];
374 let rook2Pos = positions[2];
375
376 // Finally put the shuffled pieces in the board array
377 pieces[c][rook1Pos] = 'r';
378 pieces[c][knight1Pos] = 'n';
379 pieces[c][bishop1Pos] = 'b';
380 pieces[c][queenPos] = 'q';
381 pieces[c][marshallPos] = 'm';
382 pieces[c][cardinalPos] = 'c';
383 pieces[c][kingPos] = 'k';
384 pieces[c][bishop2Pos] = 'b';
385 pieces[c][knight2Pos] = 'n';
386 pieces[c][rook2Pos] = 'r';
387 }
c794dbb8 388 return pieces["b"].join("") +
c6052161 389 "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
92342261 390 pieces["w"].join("").toUpperCase() +
0c3fe8a6 391 " w 0 1111 - 00000000000000";
a37076f1
BA
392 }
393}