Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Coregal.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt, sample } from "@/utils/alea";
4
5 export class CoregalRules extends ChessRules {
6
7 static IsGoodPosition(position) {
8 if (!ChessRules.IsGoodPosition(position)) return false;
9 const rows = position.split("/");
10 // Check that at least one queen of each color is there:
11 let queens = {};
12 for (let row of rows) {
13 for (let i = 0; i < row.length; i++)
14 if (['Q','q'].includes(row[i])) queens[row[i]] = true;
15 }
16 if (Object.keys(queens).length != 2) return false;
17 return true;
18 }
19
20 static IsGoodFlags(flags) {
21 return !!flags.match(/^[a-z]{8,8}$/);
22 }
23
24 // Scanning king position for faster updates is still interesting.
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: {
40 const num = parseInt(fenRows[i].charAt(j), 10);
41 if (!isNaN(num)) k += num - 1;
42 }
43 }
44 k++;
45 }
46 }
47 }
48
49 getPPpath(m) {
50 if (
51 m.vanish.length == 2 &&
52 m.appear.length == 2 &&
53 m.vanish[0].p == V.QUEEN
54 ) {
55 // Large castle: show castle symbol
56 return "Coregal/castle";
57 }
58 return super.getPPpath(m);
59 }
60
61 getCheckSquares() {
62 const color = this.turn;
63 let squares = [];
64 const oppCol = V.GetOppCol(color);
65 if (this.isAttacked(this.kingPos[color], oppCol))
66 squares.push(JSON.parse(JSON.stringify(this.kingPos[color])));
67 for (let i=0; i<V.size.x; i++) {
68 for (let j=0; j<V.size.y; j++) {
69 if (
70 this.getColor(i, j) == color &&
71 this.getPiece(i, j) == V.QUEEN &&
72 this.isAttacked([i, j], oppCol)
73 ) {
74 squares.push([i, j]);
75 }
76 }
77 }
78 return squares;
79 }
80
81 static GenRandInitFen(options) {
82 if (options.randomness == 0)
83 // Castle flags here indicate pieces positions (if can castle)
84 return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 adehadeh -";
85
86 let pieces = { w: new Array(8), b: new Array(8) };
87 let flags = "";
88 for (let c of ["w", "b"]) {
89 if (c == 'b' && options.randomness == 1) {
90 pieces['b'] = pieces['w'];
91 flags += flags;
92 break;
93 }
94
95 // Get random squares for king and queen between b and g files
96 let randIndex = randInt(6) + 1;
97 let kingPos = randIndex;
98 randIndex = randInt(5) + 1;
99 if (randIndex >= kingPos) randIndex++;
100 let queenPos = randIndex;
101
102 // Get random squares for rooks to the left and right of the queen
103 // and king: not all squares of the same colors (for bishops).
104 const minQR = Math.min(kingPos, queenPos);
105 const maxQR = Math.max(kingPos, queenPos);
106 let rook1Pos = randInt(minQR);
107 let rook2Pos = 7 - randInt(7 - maxQR);
108
109 // Now, if we are unlucky all these 4 pieces may be on the same color.
110 const rem2 = [kingPos, queenPos, rook1Pos, rook2Pos].map(pos => pos % 2);
111 if (rem2.every(r => r == 0) || rem2.every(r => r == 1)) {
112 // Shift a random of these pieces to the left or right
113 switch (randInt(4)) {
114 case 0:
115 if (rook1Pos == 0) rook1Pos++;
116 else rook1Pos--;
117 break;
118 case 1:
119 if (Math.random() < 0.5) kingPos++;
120 else kingPos--;
121 break;
122 case 2:
123 if (Math.random() < 0.5) queenPos++;
124 else queenPos--;
125 break;
126 case 3:
127 if (rook2Pos == 7) rook2Pos--;
128 else rook2Pos++;
129 break;
130 }
131 }
132 let bishop1Options = { 0: true, 2: true, 4: true, 6: true };
133 let bishop2Options = { 1: true, 3: true, 5: true, 7: true };
134 [kingPos, queenPos, rook1Pos, rook2Pos].forEach(pos => {
135 if (!!bishop1Options[pos]) delete bishop1Options[pos];
136 else if (!!bishop2Options[pos]) delete bishop2Options[pos];
137 });
138 const bishop1Pos =
139 parseInt(sample(Object.keys(bishop1Options), 1)[0], 10);
140 const bishop2Pos =
141 parseInt(sample(Object.keys(bishop2Options), 1)[0], 10);
142
143 // Knights' positions are now determined
144 const forbidden = [
145 kingPos, queenPos, rook1Pos, rook2Pos, bishop1Pos, bishop2Pos
146 ];
147 const [knight1Pos, knight2Pos] =
148 ArrayFun.range(8).filter(pos => !forbidden.includes(pos));
149
150 pieces[c][rook1Pos] = "r";
151 pieces[c][knight1Pos] = "n";
152 pieces[c][bishop1Pos] = "b";
153 pieces[c][queenPos] = "q";
154 pieces[c][kingPos] = "k";
155 pieces[c][bishop2Pos] = "b";
156 pieces[c][knight2Pos] = "n";
157 pieces[c][rook2Pos] = "r";
158 flags += [rook1Pos, queenPos, kingPos, rook2Pos]
159 .sort().map(V.CoordToColumn).join("");
160 }
161 // Add turn + flags + enpassant
162 return (
163 pieces["b"].join("") +
164 "/pppppppp/8/8/8/8/PPPPPPPP/" +
165 pieces["w"].join("").toUpperCase() +
166 " w 0 " + flags + " -"
167 );
168 }
169
170 setFlags(fenflags) {
171 // white pieces positions, then black pieces positions
172 this.castleFlags = { w: [...Array(4)], b: [...Array(4)] };
173 for (let i = 0; i < 8; i++) {
174 this.castleFlags[i < 4 ? "w" : "b"][i % 4] =
175 V.ColumnToCoord(fenflags.charAt(i))
176 }
177 }
178
179 getPotentialQueenMoves([x, y]) {
180 let moves = super.getPotentialQueenMoves([x, y]);
181 const c = this.getColor(x, y);
182 if (this.castleFlags[c].slice(1, 3).includes(y))
183 moves = moves.concat(this.getCastleMoves([x, y]));
184 return moves;
185 }
186
187 getPotentialKingMoves([x, y]) {
188 let moves = this.getSlideNJumpMoves(
189 [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
190 const c = this.getColor(x, y);
191 if (this.castleFlags[c].slice(1, 3).includes(y))
192 moves = moves.concat(this.getCastleMoves([x, y]));
193 return moves;
194 }
195
196 getCastleMoves([x, y]) {
197 // Relative position of the selected piece: left or right ?
198 // If left: small castle left, large castle right.
199 // If right: usual situation.
200 const c = this.getColor(x, y);
201 const relPos = (this.castleFlags[c][1] == y ? "left" : "right");
202
203 const finalSquares = [
204 relPos == "left" ? [1, 2] : [2, 3],
205 relPos == "right" ? [6, 5] : [5, 4]
206 ];
207 const saveFlags = JSON.stringify(this.castleFlags[c]);
208 // Alter flags to follow base_rules semantic
209 this.castleFlags[c] = [0, 3].map(i => this.castleFlags[c][i]);
210 const moves = super.getCastleMoves([x, y], finalSquares);
211 this.castleFlags[c] = JSON.parse(saveFlags);
212 return moves;
213 }
214
215 underCheck(color) {
216 const oppCol = V.GetOppCol(color);
217 if (this.isAttacked(this.kingPos[color], oppCol)) return true;
218 for (let i=0; i<V.size.x; i++) {
219 for (let j=0; j<V.size.y; j++) {
220 if (
221 this.getColor(i, j) == color &&
222 this.getPiece(i, j) == V.QUEEN &&
223 this.isAttacked([i, j], oppCol)
224 ) {
225 return true;
226 }
227 }
228 }
229 return false;
230 }
231
232 // "twoKings" arg for the similar Twokings variant.
233 updateCastleFlags(move, piece, twoKings) {
234 const c = V.GetOppCol(this.turn);
235 const firstRank = (c == "w" ? V.size.x - 1 : 0);
236 // Update castling flags if castling pieces moved or were captured
237 const oppCol = V.GetOppCol(c);
238 const oppFirstRank = V.size.x - 1 - firstRank;
239 if (move.start.x == firstRank) {
240 if (piece == V.KING || (!twoKings && piece == V.QUEEN)) {
241 if (this.castleFlags[c][1] == move.start.y)
242 this.castleFlags[c][1] = 8;
243 else if (this.castleFlags[c][2] == move.start.y)
244 this.castleFlags[c][2] = 8;
245 // Else: the flag is already turned off
246 }
247 }
248 else if (
249 move.start.x == firstRank && //our rook moves?
250 [this.castleFlags[c][0], this.castleFlags[c][3]].includes(move.start.y)
251 ) {
252 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 3);
253 this.castleFlags[c][flagIdx] = 8;
254 } else if (
255 move.end.x == oppFirstRank && //we took opponent rook?
256 [this.castleFlags[oppCol][0], this.castleFlags[oppCol][3]]
257 .includes(move.end.y)
258 ) {
259 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 3);
260 this.castleFlags[oppCol][flagIdx] = 8;
261 }
262 }
263
264 // NOTE: do not set queen value to 1000 or so, because there may be several.
265
266 static get SEARCH_DEPTH() {
267 return 2;
268 }
269
270 getNotation(move) {
271 if (move.appear.length == 2) {
272 // Castle: determine the right notation
273 const color = move.appear[0].c;
274 let symbol = (move.appear[0].p == V.QUEEN ? "Q" : "") + "0-0";
275 if (
276 (
277 this.castleFlags[color][1] == move.vanish[0].y &&
278 move.end.y > move.start.y
279 )
280 ||
281 (
282 this.castleFlags[color][2] == move.vanish[0].y &&
283 move.end.y < move.start.y
284 )
285 ) {
286 symbol += "-0";
287 }
288 return symbol;
289 }
290 return super.getNotation(move);
291 }
292
293 };