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