Fix Koopa promotions with captures, and Balakhlava: pawns move forward
[vchess.git] / client / src / variants / Coregal.js
CommitLineData
3f22c2c3 1import { ChessRules, Move, PiPo } from "@/base_rules";
bb688df5
BA
2import { ArrayFun } from "@/utils/array";
3import { randInt, sample } from "@/utils/alea";
4
5export class CoregalRules extends ChessRules {
6 static IsGoodPosition(position) {
6f2f9437 7 if (!ChessRules.IsGoodPosition(position)) return false;
3f22c2c3 8 const rows = position.split("/");
bb688df5
BA
9 // Check that at least one queen of each color is there:
10 let queens = {};
11 for (let row of rows) {
12 for (let i = 0; i < row.length; i++)
13 if (['Q','q'].includes(row[i])) queens[row[i]] = true;
14 }
15 if (Object.keys(queens).length != 2) return false;
16 return true;
17 }
18
19 static IsGoodFlags(flags) {
20 return !!flags.match(/^[a-z]{8,8}$/);
21 }
22
3f22c2c3
BA
23 // Scanning king position for faster updates is still interesting,
24 // but no need for INIT_COL_KING because it's given in castle flags.
25 scanKings(fen) {
26 this.kingPos = { w: [-1, -1], b: [-1, -1] };
27 const fenRows = V.ParseFen(fen).position.split("/");
28 const startRow = { 'w': V.size.x - 1, 'b': 0 };
29 for (let i = 0; i < fenRows.length; i++) {
30 let k = 0;
31 for (let j = 0; j < fenRows[i].length; j++) {
32 switch (fenRows[i].charAt(j)) {
33 case "k":
34 this.kingPos["b"] = [i, k];
35 break;
36 case "K":
37 this.kingPos["w"] = [i, k];
38 break;
39 default: {
e50a8025 40 const num = parseInt(fenRows[i].charAt(j), 10);
3f22c2c3
BA
41 if (!isNaN(num)) k += num - 1;
42 }
43 }
44 k++;
45 }
46 }
47 }
48
af34341d
BA
49 getCheckSquares() {
50 const color = this.turn;
bb688df5
BA
51 let squares = [];
52 const oppCol = V.GetOppCol(color);
53 if (this.isAttacked(this.kingPos[color], oppCol))
3f22c2c3 54 squares.push(JSON.parse(JSON.stringify(this.kingPos[color])));
bb688df5
BA
55 for (let i=0; i<V.size.x; i++) {
56 for (let j=0; j<V.size.y; j++) {
57 if (
58 this.getColor(i, j) == color &&
59 this.getPiece(i, j) == V.QUEEN &&
60 this.isAttacked([i, j], oppCol)
61 ) {
62 squares.push([i, j]);
63 }
64 }
65 }
66 return squares;
67 }
68
69 static GenRandInitFen(randomness) {
70 if (randomness == 0)
71 // Castle flags here indicate pieces positions (if can castle)
72 return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 adehadeh -";
73
74 let pieces = { w: new Array(8), b: new Array(8) };
75 let flags = "";
76 for (let c of ["w", "b"]) {
77 if (c == 'b' && randomness == 1) {
78 pieces['b'] = pieces['w'];
79 flags += flags;
80 break;
81 }
82
83 // Get random squares for king and queen between b and g files
df3eb77c
BA
84 let randIndex = randInt(6) + 1;
85 let kingPos = randIndex;
86 randIndex = randInt(5) + 1;
bb688df5 87 if (randIndex >= kingPos) randIndex++;
df3eb77c 88 let queenPos = randIndex;
bb688df5
BA
89
90 // Get random squares for rooks to the left and right of the queen
91 // and king: not all squares of the same colors (for bishops).
92 const minQR = Math.min(kingPos, queenPos);
93 const maxQR = Math.max(kingPos, queenPos);
94 let rook1Pos = randInt(minQR);
95 let rook2Pos = 7 - randInt(7 - maxQR);
96
97 // Now, if we are unlucky all these 4 pieces may be on the same color.
98 const rem2 = [kingPos, queenPos, rook1Pos, rook2Pos].map(pos => pos % 2);
99 if (rem2.every(r => r == 0) || rem2.every(r => r == 1)) {
100 // Shift a random of these pieces to the left or right
101 switch (randInt(4)) {
102 case 0:
103 if (rook1Pos == 0) rook1Pos++;
104 else rook1Pos--;
105 break;
106 case 1:
107 if (Math.random() < 0.5) kingPos++;
108 else kingPos--;
109 break;
110 case 2:
111 if (Math.random() < 0.5) queenPos++;
112 else queenPos--;
113 break;
114 case 3:
115 if (rook2Pos == 7) rook2Pos--;
116 else rook2Pos++;
117 break;
118 }
119 }
120 let bishop1Options = { 0: true, 2: true, 4: true, 6: true };
121 let bishop2Options = { 1: true, 3: true, 5: true, 7: true };
122 [kingPos, queenPos, rook1Pos, rook2Pos].forEach(pos => {
123 if (!!bishop1Options[pos]) delete bishop1Options[pos];
124 else if (!!bishop2Options[pos]) delete bishop2Options[pos];
125 });
e50a8025
BA
126 const bishop1Pos =
127 parseInt(sample(Object.keys(bishop1Options), 1)[0], 10);
128 const bishop2Pos =
129 parseInt(sample(Object.keys(bishop2Options), 1)[0], 10);
bb688df5
BA
130
131 // Knights' positions are now determined
132 const forbidden = [
133 kingPos, queenPos, rook1Pos, rook2Pos, bishop1Pos, bishop2Pos
134 ];
135 const [knight1Pos, knight2Pos] =
136 ArrayFun.range(8).filter(pos => !forbidden.includes(pos));
137
138 pieces[c][rook1Pos] = "r";
139 pieces[c][knight1Pos] = "n";
140 pieces[c][bishop1Pos] = "b";
141 pieces[c][queenPos] = "q";
142 pieces[c][kingPos] = "k";
143 pieces[c][bishop2Pos] = "b";
144 pieces[c][knight2Pos] = "n";
145 pieces[c][rook2Pos] = "r";
2c5d7b20
BA
146 flags += [rook1Pos, queenPos, kingPos, rook2Pos]
147 .sort().map(V.CoordToColumn).join("");
bb688df5
BA
148 }
149 // Add turn + flags + enpassant
150 return (
151 pieces["b"].join("") +
152 "/pppppppp/8/8/8/8/PPPPPPPP/" +
153 pieces["w"].join("").toUpperCase() +
154 " w 0 " + flags + " -"
155 );
156 }
157
158 setFlags(fenflags) {
3f22c2c3 159 // white pieces positions, then black pieces positions
bb688df5
BA
160 this.castleFlags = { w: [...Array(4)], b: [...Array(4)] };
161 for (let i = 0; i < 8; i++) {
162 this.castleFlags[i < 4 ? "w" : "b"][i % 4] =
3f22c2c3 163 V.ColumnToCoord(fenflags.charAt(i))
bb688df5
BA
164 }
165 }
166
167 getPotentialQueenMoves(sq) {
168 return super.getPotentialQueenMoves(sq).concat(this.getCastleMoves(sq));
169 }
170
3f22c2c3
BA
171 getCastleMoves([x, y]) {
172 const c = this.getColor(x, y);
173 if (
174 x != (c == "w" ? V.size.x - 1 : 0) ||
175 !this.castleFlags[c].slice(1, 3).includes(y)
176 ) {
177 // x isn't first rank, or piece moved
178 return [];
179 }
180 const castlingPiece = this.getPiece(x, y);
181
182 // Relative position of the selected piece: left or right ?
183 // If left: small castle left, large castle right.
184 // If right: usual situation.
185 const relPos = (this.castleFlags[c][1] == y ? "left" : "right");
186
187 // Castling ?
188 const oppCol = V.GetOppCol(c);
189 let moves = [];
190 let i = 0;
191 // Castling piece, then rook:
192 const finalSquares = {
193 0: (relPos == "left" ? [1, 2] : [2, 3]),
194 3: (relPos == "right" ? [6, 5] : [5, 4])
195 };
196
197 // Left, then right castle:
198 castlingCheck: for (let castleSide of [0, 3]) {
199 if (this.castleFlags[c][castleSide] >= 8) continue;
200
201 // Rook and castling piece are on initial position
202 const rookPos = this.castleFlags[c][castleSide];
203
204 // Nothing on the path of the king ? (and no checks)
205 const finDist = finalSquares[castleSide][0] - y;
206 let step = finDist / Math.max(1, Math.abs(finDist));
207 i = y;
208 do {
209 if (
210 this.isAttacked([x, i], oppCol) ||
211 (this.board[x][i] != V.EMPTY &&
212 // NOTE: next check is enough, because of chessboard constraints
213 (this.getColor(x, i) != c ||
214 ![castlingPiece, V.ROOK].includes(this.getPiece(x, i))))
215 ) {
216 continue castlingCheck;
217 }
218 i += step;
219 } while (i != finalSquares[castleSide][0]);
220
221 // Nothing on the path to the rook?
222 step = castleSide == 0 ? -1 : 1;
223 for (i = y + step; i != rookPos; i += step) {
224 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
225 }
226
227 // Nothing on final squares, except maybe castling piece and rook?
228 for (i = 0; i < 2; i++) {
229 if (
230 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
231 ![y, rookPos].includes(finalSquares[castleSide][i])
232 ) {
233 continue castlingCheck;
234 }
235 }
236
237 // If this code is reached, castle is valid
238 moves.push(
239 new Move({
240 appear: [
2c5d7b20
BA
241 new PiPo({
242 x: x,
243 y: finalSquares[castleSide][0],
244 p: castlingPiece,
245 c: c
246 }),
247 new PiPo({
248 x: x,
249 y: finalSquares[castleSide][1],
250 p: V.ROOK,
251 c: c
252 })
3f22c2c3
BA
253 ],
254 vanish: [
255 new PiPo({ x: x, y: y, p: castlingPiece, c: c }),
256 new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
257 ],
258 // In this variant, always castle by playing onto the rook
259 end: { x: x, y: rookPos }
260 })
261 );
262 }
263
264 return moves;
bb688df5
BA
265 }
266
267 underCheck(color) {
268 const oppCol = V.GetOppCol(color);
269 if (this.isAttacked(this.kingPos[color], oppCol)) return true;
270 for (let i=0; i<V.size.x; i++) {
271 for (let j=0; j<V.size.y; j++) {
272 if (
273 this.getColor(i, j) == color &&
274 this.getPiece(i, j) == V.QUEEN &&
275 this.isAttacked([i, j], oppCol)
276 ) {
277 return true;
278 }
279 }
280 }
281 return false;
282 }
283
14c35dc6
BA
284 // "twoKings" arg for the similar Twokings variant.
285 updateCastleFlags(move, piece, twoKings) {
3f22c2c3
BA
286 const c = V.GetOppCol(this.turn);
287 const firstRank = (c == "w" ? V.size.x - 1 : 0);
288 // Update castling flags if castling pieces moved or were captured
289 const oppCol = V.GetOppCol(c);
290 const oppFirstRank = V.size.x - 1 - firstRank;
14c35dc6
BA
291 if (move.start.x == firstRank) {
292 if (piece == V.KING || (!twoKings && piece == V.QUEEN)) {
293 if (this.castleFlags[c][1] == move.start.y)
294 this.castleFlags[c][1] = 8;
295 else if (this.castleFlags[c][2] == move.start.y)
296 this.castleFlags[c][2] = 8;
297 // Else: the flag is already turned off
298 }
3f22c2c3
BA
299 }
300 else if (
301 move.start.x == firstRank && //our rook moves?
302 [this.castleFlags[c][0], this.castleFlags[c][3]].includes(move.start.y)
303 ) {
304 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 3);
305 this.castleFlags[c][flagIdx] = 8;
306 } else if (
307 move.end.x == oppFirstRank && //we took opponent rook?
2c5d7b20
BA
308 [this.castleFlags[oppCol][0], this.castleFlags[oppCol][3]]
309 .includes(move.end.y)
3f22c2c3
BA
310 ) {
311 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 3);
312 this.castleFlags[oppCol][flagIdx] = 8;
313 }
bb688df5
BA
314 }
315
316 // NOTE: do not set queen value to 1000 or so, because there may be several.
3f22c2c3 317
f82609cd
BA
318 static get SEARCH_DEPTH() {
319 return 2;
320 }
321
3f22c2c3
BA
322 getNotation(move) {
323 if (move.appear.length == 2) {
324 // Castle: determine the right notation
325 const color = move.appear[0].c;
326 let symbol = (move.appear[0].p == V.QUEEN ? "Q" : "") + "0-0";
327 if (
328 (
329 this.castleFlags[color][1] == move.vanish[0].y &&
330 move.end.y > move.start.y
331 )
332 ||
333 (
334 this.castleFlags[color][2] == move.vanish[0].y &&
335 move.end.y < move.start.y
336 )
337 ) {
338 symbol += "-0";
339 }
340 return symbol;
341 }
342 return super.getNotation(move);
343 }
bb688df5 344};