Add Fugue variant
[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
32f6285e 5export class GrandRules extends ChessRules {
7e8a7ea1 6
0fb43db7
BA
7 static get HasCastle() {
8 return false;
9 }
10
6808d7a1
BA
11 static IsGoodFen(fen) {
12 if (!ChessRules.IsGoodFen(fen)) return false;
dac39588
BA
13 const fenParsed = V.ParseFen(fen);
14 // 5) Check captures
472c0c4f 15 if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{12,12}$/))
dac39588
BA
16 return false;
17 return true;
18 }
19
6808d7a1 20 static IsGoodEnpassant(enpassant) {
472c0c4f 21 if (enpassant != "-") return !!enpassant.match(/^([a-j][0-9]{1,2},?)+$/);
dac39588
BA
22 return true;
23 }
24
6808d7a1 25 static ParseFen(fen) {
dac39588 26 const fenParts = fen.split(" ");
6f2f9437
BA
27 return Object.assign(
28 ChessRules.ParseFen(fen),
0fb43db7 29 { captured: fenParts[4] }
6f2f9437 30 );
dac39588
BA
31 }
32
241bf8f2
BA
33 getPpath(b) {
34 return ([V.MARSHALL, V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
35 }
36
6808d7a1 37 getFen() {
dac39588
BA
38 return super.getFen() + " " + this.getCapturedFen();
39 }
40
f9c36b2d 41 getFenForRepeat() {
90e814b6 42 return super.getFenForRepeat() + "_" + this.getCapturedFen();
f9c36b2d
BA
43 }
44
6808d7a1 45 getCapturedFen() {
6e47d367 46 let counts = [...Array(12).fill(0)];
dac39588 47 let i = 0;
6808d7a1 48 for (let j = 0; j < V.PIECES.length; j++) {
6e47d367
BA
49 if ([V.KING, V.PAWN].includes(V.PIECES[j]))
50 // No king captured, and pawns don't promote in pawns
dac39588 51 continue;
6e47d367
BA
52 counts[i] = this.captured["w"][V.PIECES[j]];
53 counts[6 + i] = this.captured["b"][V.PIECES[j]];
dac39588
BA
54 i++;
55 }
56 return counts.join("");
57 }
58
6808d7a1 59 setOtherVariables(fen) {
dac39588 60 super.setOtherVariables(fen);
e50a8025
BA
61 const captured =
62 V.ParseFen(fen).captured.split("").map(x => parseInt(x, 10));
dac39588 63 // Initialize captured pieces' counts from FEN
6808d7a1
BA
64 this.captured = {
65 w: {
6e0f2842
BA
66 [V.ROOK]: captured[0],
67 [V.KNIGHT]: captured[1],
68 [V.BISHOP]: captured[2],
69 [V.QUEEN]: captured[3],
70 [V.MARSHALL]: captured[4],
71 [V.CARDINAL]: captured[5]
dac39588 72 },
6808d7a1 73 b: {
6e0f2842
BA
74 [V.ROOK]: captured[6],
75 [V.KNIGHT]: captured[7],
76 [V.BISHOP]: captured[8],
77 [V.QUEEN]: captured[9],
78 [V.MARSHALL]: captured[10],
79 [V.CARDINAL]: captured[11]
dac39588
BA
80 }
81 };
82 }
83
6808d7a1
BA
84 static get size() {
85 return { x: 10, y: 10 };
86 }
dac39588 87
a6836242 88 // Rook + knight:
6808d7a1
BA
89 static get MARSHALL() {
90 return "m";
a6836242
BA
91 }
92
93 // Bishop + knight
6808d7a1
BA
94 static get CARDINAL() {
95 return "c";
a6836242 96 }
dac39588 97
6808d7a1
BA
98 static get PIECES() {
99 return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]);
dac39588
BA
100 }
101
6808d7a1
BA
102 getPotentialMovesFrom([x, y]) {
103 switch (this.getPiece(x, y)) {
dac39588 104 case V.MARSHALL:
6808d7a1 105 return this.getPotentialMarshallMoves([x, y]);
dac39588 106 case V.CARDINAL:
6808d7a1 107 return this.getPotentialCardinalMoves([x, y]);
dac39588 108 default:
6808d7a1 109 return super.getPotentialMovesFrom([x, y]);
dac39588
BA
110 }
111 }
112
113 // Special pawn rules: promotions to captured friendly pieces,
114 // optional on ranks 8-9 and mandatory on rank 10.
6808d7a1 115 getPotentialPawnMoves([x, y]) {
dac39588
BA
116 const color = this.turn;
117 let moves = [];
6808d7a1
BA
118 const [sizeX, sizeY] = [V.size.x, V.size.y];
119 const shiftX = color == "w" ? -1 : 1;
0fb43db7 120 const startRank = (color == "w" ? sizeX - 3 : 2);
6808d7a1
BA
121 const lastRanks =
122 color == "w" ? [0, 1, 2] : [sizeX - 1, sizeX - 2, sizeX - 3];
123 const promotionPieces = [
124 V.ROOK,
125 V.KNIGHT,
126 V.BISHOP,
127 V.QUEEN,
128 V.MARSHALL,
129 V.CARDINAL
130 ];
dac39588
BA
131
132 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
133 let finalPieces = undefined;
6808d7a1 134 if (lastRanks.includes(x + shiftX)) {
dac39588 135 finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
6808d7a1 136 if (x + shiftX != lastRanks[0]) finalPieces.push(V.PAWN);
0fb43db7
BA
137 }
138 else finalPieces = [V.PAWN];
6808d7a1 139 if (this.board[x + shiftX][y] == V.EMPTY) {
dac39588
BA
140 // One square forward
141 for (let piece of finalPieces)
6808d7a1
BA
142 moves.push(
143 this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece })
144 );
0fb43db7
BA
145 if (x == startRank && this.board[x + 2 * shiftX][y] == V.EMPTY)
146 // Two squares jump
147 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
dac39588
BA
148 }
149 // Captures
6808d7a1
BA
150 for (let shiftY of [-1, 1]) {
151 if (
152 y + shiftY >= 0 &&
153 y + shiftY < sizeY &&
154 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
155 this.canTake([x, y], [x + shiftX, y + shiftY])
156 ) {
157 for (let piece of finalPieces) {
158 moves.push(
159 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
160 c: color,
161 p: piece
162 })
163 );
dac39588
BA
164 }
165 }
166 }
167
168 // En passant
0fb43db7
BA
169 Array.prototype.push.apply(
170 moves,
171 this.getEnpassantCaptures([x, y], shiftX)
172 );
dac39588
BA
173
174 return moves;
175 }
176
6808d7a1 177 getPotentialMarshallMoves(sq) {
dac39588 178 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
6808d7a1
BA
179 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
180 );
dac39588
BA
181 }
182
6808d7a1 183 getPotentialCardinalMoves(sq) {
dac39588 184 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
6808d7a1
BA
185 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
186 );
dac39588
BA
187 }
188
68e19a44 189 isAttacked(sq, color) {
6808d7a1 190 return (
68e19a44
BA
191 super.isAttacked(sq, color) ||
192 this.isAttackedByMarshall(sq, color) ||
193 this.isAttackedByCardinal(sq, color)
6808d7a1 194 );
dac39588
BA
195 }
196
68e19a44 197 isAttackedByMarshall(sq, color) {
6808d7a1 198 return (
68e19a44 199 this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) ||
6808d7a1
BA
200 this.isAttackedBySlideNJump(
201 sq,
68e19a44 202 color,
6808d7a1
BA
203 V.MARSHALL,
204 V.steps[V.KNIGHT],
205 "oneStep"
206 )
207 );
dac39588
BA
208 }
209
68e19a44 210 isAttackedByCardinal(sq, color) {
6808d7a1 211 return (
68e19a44 212 this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) ||
6808d7a1
BA
213 this.isAttackedBySlideNJump(
214 sq,
68e19a44 215 color,
6808d7a1
BA
216 V.CARDINAL,
217 V.steps[V.KNIGHT],
218 "oneStep"
219 )
220 );
dac39588
BA
221 }
222
3a2a7b5f
BA
223 postPlay(move) {
224 super.postPlay(move);
6e47d367 225 if (move.vanish.length == 2 && move.appear.length == 1)
dac39588
BA
226 // Capture: update this.captured
227 this.captured[move.vanish[1].c][move.vanish[1].p]++;
dac39588
BA
228 }
229
3a2a7b5f
BA
230 postUndo(move) {
231 super.postUndo(move);
dac39588
BA
232 if (move.vanish.length == 2 && move.appear.length == 1)
233 this.captured[move.vanish[1].c][move.vanish[1].p]--;
dac39588
BA
234 }
235
6808d7a1 236 static get VALUES() {
dac39588 237 return Object.assign(
a97bdbda
BA
238 { c: 5, m: 7 }, //experimental
239 ChessRules.VALUES
dac39588
BA
240 );
241 }
242
6808d7a1
BA
243 static get SEARCH_DEPTH() {
244 return 2;
245 }
dac39588 246
7ba4a5bc 247 static GenRandInitFen(randomness) {
7ba4a5bc 248 if (randomness == 0) {
2c5d7b20
BA
249 return (
250 "r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " +
0fb43db7 251 "w 0 - 00000000000000"
2c5d7b20 252 );
7ba4a5bc
BA
253 }
254
0fb43db7
BA
255 let pieces = { w: new Array(8), b: new Array(8) };
256 // Shuffle pieces on second and before-last rank
6808d7a1 257 for (let c of ["w", "b"]) {
7ba4a5bc
BA
258 if (c == 'b' && randomness == 1) {
259 pieces['b'] = pieces['w'];
260 break;
261 }
262
0fb43db7 263 let positions = ArrayFun.range(8);
dac39588
BA
264
265 // Get random squares for bishops
0fb43db7 266 let randIndex = 2 * randInt(4);
3208c667 267 const bishop1Pos = positions[randIndex];
dac39588 268 // The second bishop must be on a square of different color
0fb43db7 269 let randIndex_tmp = 2 * randInt(4) + 1;
3208c667 270 const bishop2Pos = positions[randIndex_tmp];
dac39588 271 // Remove chosen squares
6808d7a1
BA
272 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
273 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
dac39588
BA
274
275 // Get random squares for knights
0fb43db7 276 randIndex = randInt(6);
3208c667 277 const knight1Pos = positions[randIndex];
dac39588 278 positions.splice(randIndex, 1);
0fb43db7 279 randIndex = randInt(5);
3208c667 280 const knight2Pos = positions[randIndex];
dac39588
BA
281 positions.splice(randIndex, 1);
282
283 // Get random square for queen
0fb43db7 284 randIndex = randInt(4);
3208c667 285 const queenPos = positions[randIndex];
dac39588
BA
286 positions.splice(randIndex, 1);
287
288 // ...random square for marshall
0fb43db7 289 randIndex = randInt(3);
3208c667 290 const marshallPos = positions[randIndex];
dac39588
BA
291 positions.splice(randIndex, 1);
292
293 // ...random square for cardinal
0fb43db7 294 randIndex = randInt(2);
3208c667 295 const cardinalPos = positions[randIndex];
dac39588
BA
296 positions.splice(randIndex, 1);
297
0fb43db7 298 // King position is now fixed,
3208c667 299 const kingPos = positions[0];
dac39588
BA
300
301 // Finally put the shuffled pieces in the board array
6808d7a1
BA
302 pieces[c][knight1Pos] = "n";
303 pieces[c][bishop1Pos] = "b";
304 pieces[c][queenPos] = "q";
305 pieces[c][marshallPos] = "m";
306 pieces[c][cardinalPos] = "c";
307 pieces[c][kingPos] = "k";
308 pieces[c][bishop2Pos] = "b";
309 pieces[c][knight2Pos] = "n";
dac39588 310 }
6808d7a1 311 return (
0fb43db7
BA
312 "r8r/1" + pieces["b"].join("") + "1/" +
313 "pppppppppp/91/91/91/91/PPPPPPPPPP/" +
314 "1" + pieces["w"].join("").toUpperCase() + "1/R8R" +
315 " w 0 " + " - 00000000000000"
6808d7a1 316 );
dac39588 317 }
7e8a7ea1 318
6808d7a1 319};