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