Test post-update hook
[vchess.git] / client / src / variants / Grand.js
... / ...
CommitLineData
1import { ChessRules } from "@/base_rules";
2import { ArrayFun } from "@/utils/array";
3import { randInt } from "@/utils/alea";
4
5// NOTE: initial setup differs from the original; see
6// https://www.chessvariants.com/large.dir/freeling.html
7export const VariantRules = class GrandRules extends ChessRules
8{
9 static getPpath(b)
10 {
11 return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
12 }
13
14 static IsGoodFen(fen)
15 {
16 if (!ChessRules.IsGoodFen(fen))
17 return false;
18 const fenParsed = V.ParseFen(fen);
19 // 5) Check captures
20 if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
21 return false;
22 return true;
23 }
24
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
42 static ParseFen(fen)
43 {
44 const fenParts = fen.split(" ");
45 return Object.assign(
46 ChessRules.ParseFen(fen),
47 { captured: fenParts[5] }
48 );
49 }
50
51 getFen()
52 {
53 return super.getFen() + " " + this.getCapturedFen();
54 }
55
56 getCapturedFen()
57 {
58 let counts = [...Array(14).fill(0)];
59 let i = 0;
60 for (let j=0; j<V.PIECES.length; j++)
61 {
62 if (V.PIECES[j] == V.KING) //no king captured
63 continue;
64 counts[i] = this.captured["w"][V.PIECES[i]];
65 counts[7+i] = this.captured["b"][V.PIECES[i]];
66 i++;
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]),
85 [V.MARSHALL]: parseInt(fenParsed.captured[5]),
86 [V.CARDINAL]: parseInt(fenParsed.captured[6]),
87 },
88 "b":
89 {
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]),
97 }
98 };
99 }
100
101 static get size() { return {x:10,y:10}; }
102
103 static get MARSHALL() { return 'm'; } //rook+knight
104 static get CARDINAL() { return 'c'; } //bishop+knight
105
106 static get PIECES()
107 {
108 return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
109 }
110
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
124 // En-passant after 2-sq or 3-sq jumps
125 getEpSquare(moveOrSquare)
126 {
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;
142 const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
143 if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
144 {
145 const step = (ex-sx) / Math.abs(ex-sx);
146 let res = [{
147 x: sx + step,
148 y: sy
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;
158 }
159 return undefined; //default
160 }
161
162 getPotentialMovesFrom([x,y])
163 {
164 switch (this.getPiece(x,y))
165 {
166 case V.MARSHALL:
167 return this.getPotentialMarshallMoves([x,y]);
168 case V.CARDINAL:
169 return this.getPotentialCardinalMoves([x,y]);
170 default:
171 return super.getPotentialMovesFrom([x,y])
172 }
173 }
174
175 // Special pawn rules: promotions to captured friendly pieces,
176 // optional on ranks 8-9 and mandatory on rank 10.
177 getPotentialPawnMoves([x,y])
178 {
179 const color = this.turn;
180 let moves = [];
181 const [sizeX,sizeY] = [V.size.x,V.size.y];
182 const shiftX = (color == "w" ? -1 : 1);
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]);
185 const promotionPieces =
186 [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
187
188 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
189 let finalPieces = undefined;
190 if (lastRanks.includes(x + shiftX))
191 {
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))
204 {
205 if (this.board[x+2*shiftX][y] == V.EMPTY)
206 {
207 // Two squares jump
208 moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
209 if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
210 {
211 // Three squares jump
212 moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
213 }
214 }
215 }
216 }
217 // Captures
218 for (let shiftY of [-1,1])
219 {
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)
225 {
226 moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
227 {c:color,p:piece}));
228 }
229 }
230 }
231
232 // En passant
233 const Lep = this.epSquares.length;
234 const epSquare = this.epSquares[Lep-1];
235 if (!!epSquare)
236 {
237 for (let epsq of epSquare)
238 {
239 // TODO: some redundant checks
240 if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
241 {
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);
246 enpassantMove.vanish.push({
247 x: px,
248 y: epsq.y,
249 p: 'p',
250 c: this.getColor(px,epsq.y)
251 });
252 moves.push(enpassantMove);
253 }
254 }
255 }
256
257 return moves;
258 }
259
260 // TODO: different castle?
261
262 getPotentialMarshallMoves(sq)
263 {
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 {
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 {
276 return super.isAttacked(sq, colors)
277 || this.isAttackedByMarshall(sq, colors)
278 || this.isAttackedByCardinal(sq, colors);
279 }
280
281 isAttackedByMarshall(sq, colors)
282 {
283 return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
284 || this.isAttackedBySlideNJump(
285 sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
286 }
287
288 isAttackedByCardinal(sq, colors)
289 {
290 return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
291 || this.isAttackedBySlideNJump(
292 sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
293 }
294
295 updateVariables(move)
296 {
297 super.updateVariables(move);
298 if (move.vanish.length == 2 && move.appear.length == 1)
299 {
300 // Capture: update this.captured
301 this.captured[move.vanish[1].c][move.vanish[1].p]++;
302 }
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 }
308 }
309
310 unupdateVariables(move)
311 {
312 super.unupdateVariables(move);
313 if (move.vanish.length == 2 && move.appear.length == 1)
314 this.captured[move.vanish[1].c][move.vanish[1].p]--;
315 if (move.vanish[0].p != move.appear[0].p)
316 this.captured[move.vanish[0].c][move.appear[0].p]++;
317 }
318
319 static get VALUES()
320 {
321 return Object.assign(
322 ChessRules.VALUES,
323 {'c': 5, 'm': 7} //experimental
324 );
325 }
326
327 static get SEARCH_DEPTH() { return 2; }
328
329 // TODO: this function could be generalized and shared better (how ?!...)
330 static GenRandInitFen()
331 {
332 let pieces = { "w": new Array(10), "b": new Array(10) };
333 // Shuffle pieces on first and last rank
334 for (let c of ["w","b"])
335 {
336 let positions = ArrayFun.range(10);
337
338 // Get random squares for bishops
339 let randIndex = 2 * randInt(5);
340 let bishop1Pos = positions[randIndex];
341 // The second bishop must be on a square of different color
342 let randIndex_tmp = 2 * randInt(5) + 1;
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
349 randIndex = randInt(8);
350 let knight1Pos = positions[randIndex];
351 positions.splice(randIndex, 1);
352 randIndex = randInt(7);
353 let knight2Pos = positions[randIndex];
354 positions.splice(randIndex, 1);
355
356 // Get random square for queen
357 randIndex = randInt(6);
358 let queenPos = positions[randIndex];
359 positions.splice(randIndex, 1);
360
361 // ...random square for marshall
362 randIndex = randInt(5);
363 let marshallPos = positions[randIndex];
364 positions.splice(randIndex, 1);
365
366 // ...random square for cardinal
367 randIndex = randInt(4);
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 }
388 return pieces["b"].join("") +
389 "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
390 pieces["w"].join("").toUpperCase() +
391 " w 0 1111 - 00000000000000";
392 }
393}