Fix some broken variant links in rules pages
[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 {
7e8a7ea1 6
bb688df5 7 static IsGoodPosition(position) {
6f2f9437 8 if (!ChessRules.IsGoodPosition(position)) return false;
3f22c2c3 9 const rows = position.split("/");
bb688df5
BA
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
7e8a7ea1 24 // Scanning king position for faster updates is still interesting.
3f22c2c3
BA
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
7e8a7ea1
BA
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
af34341d
BA
61 getCheckSquares() {
62 const color = this.turn;
bb688df5
BA
63 let squares = [];
64 const oppCol = V.GetOppCol(color);
65 if (this.isAttacked(this.kingPos[color], oppCol))
3f22c2c3 66 squares.push(JSON.parse(JSON.stringify(this.kingPos[color])));
bb688df5
BA
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(randomness) {
82 if (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' && 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
df3eb77c
BA
96 let randIndex = randInt(6) + 1;
97 let kingPos = randIndex;
98 randIndex = randInt(5) + 1;
bb688df5 99 if (randIndex >= kingPos) randIndex++;
df3eb77c 100 let queenPos = randIndex;
bb688df5
BA
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 });
e50a8025
BA
138 const bishop1Pos =
139 parseInt(sample(Object.keys(bishop1Options), 1)[0], 10);
140 const bishop2Pos =
141 parseInt(sample(Object.keys(bishop2Options), 1)[0], 10);
bb688df5
BA
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";
2c5d7b20
BA
158 flags += [rook1Pos, queenPos, kingPos, rook2Pos]
159 .sort().map(V.CoordToColumn).join("");
bb688df5
BA
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) {
3f22c2c3 171 // white pieces positions, then black pieces positions
bb688df5
BA
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] =
3f22c2c3 175 V.ColumnToCoord(fenflags.charAt(i))
bb688df5
BA
176 }
177 }
178
7e8a7ea1
BA
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;
bb688df5
BA
185 }
186
7e8a7ea1
BA
187 getPotentialKingMoves([x, y]) {
188 let moves = this.getSlideNJumpMoves(
189 [x, y],
190 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
191 "oneStep"
192 );
3f22c2c3 193 const c = this.getColor(x, y);
7e8a7ea1
BA
194 if (this.castleFlags[c].slice(1, 3).includes(y))
195 moves = moves.concat(this.getCastleMoves([x, y]));
196 return moves;
197 }
3f22c2c3 198
7e8a7ea1 199 getCastleMoves([x, y]) {
3f22c2c3
BA
200 // Relative position of the selected piece: left or right ?
201 // If left: small castle left, large castle right.
202 // If right: usual situation.
7e8a7ea1 203 const c = this.getColor(x, y);
3f22c2c3
BA
204 const relPos = (this.castleFlags[c][1] == y ? "left" : "right");
205
7e8a7ea1
BA
206 const finalSquares = [
207 relPos == "left" ? [1, 2] : [2, 3],
208 relPos == "right" ? [6, 5] : [5, 4]
209 ];
210 const saveFlags = JSON.stringify(this.castleFlags[c]);
211 // Alter flags to follow base_rules semantic
212 this.castleFlags[c] = [0, 3].map(i => this.castleFlags[c][i]);
213 const moves = super.getCastleMoves([x, y], finalSquares);
214 this.castleFlags[c] = JSON.parse(saveFlags);
3f22c2c3 215 return moves;
bb688df5
BA
216 }
217
218 underCheck(color) {
219 const oppCol = V.GetOppCol(color);
220 if (this.isAttacked(this.kingPos[color], oppCol)) return true;
221 for (let i=0; i<V.size.x; i++) {
222 for (let j=0; j<V.size.y; j++) {
223 if (
224 this.getColor(i, j) == color &&
225 this.getPiece(i, j) == V.QUEEN &&
226 this.isAttacked([i, j], oppCol)
227 ) {
228 return true;
229 }
230 }
231 }
232 return false;
233 }
234
14c35dc6
BA
235 // "twoKings" arg for the similar Twokings variant.
236 updateCastleFlags(move, piece, twoKings) {
3f22c2c3
BA
237 const c = V.GetOppCol(this.turn);
238 const firstRank = (c == "w" ? V.size.x - 1 : 0);
239 // Update castling flags if castling pieces moved or were captured
240 const oppCol = V.GetOppCol(c);
241 const oppFirstRank = V.size.x - 1 - firstRank;
14c35dc6
BA
242 if (move.start.x == firstRank) {
243 if (piece == V.KING || (!twoKings && piece == V.QUEEN)) {
244 if (this.castleFlags[c][1] == move.start.y)
245 this.castleFlags[c][1] = 8;
246 else if (this.castleFlags[c][2] == move.start.y)
247 this.castleFlags[c][2] = 8;
248 // Else: the flag is already turned off
249 }
3f22c2c3
BA
250 }
251 else if (
252 move.start.x == firstRank && //our rook moves?
253 [this.castleFlags[c][0], this.castleFlags[c][3]].includes(move.start.y)
254 ) {
255 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 3);
256 this.castleFlags[c][flagIdx] = 8;
257 } else if (
258 move.end.x == oppFirstRank && //we took opponent rook?
2c5d7b20
BA
259 [this.castleFlags[oppCol][0], this.castleFlags[oppCol][3]]
260 .includes(move.end.y)
3f22c2c3
BA
261 ) {
262 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 3);
263 this.castleFlags[oppCol][flagIdx] = 8;
264 }
bb688df5
BA
265 }
266
267 // NOTE: do not set queen value to 1000 or so, because there may be several.
3f22c2c3 268
f82609cd
BA
269 static get SEARCH_DEPTH() {
270 return 2;
271 }
272
3f22c2c3
BA
273 getNotation(move) {
274 if (move.appear.length == 2) {
275 // Castle: determine the right notation
276 const color = move.appear[0].c;
277 let symbol = (move.appear[0].p == V.QUEEN ? "Q" : "") + "0-0";
278 if (
279 (
280 this.castleFlags[color][1] == move.vanish[0].y &&
281 move.end.y > move.start.y
282 )
283 ||
284 (
285 this.castleFlags[color][2] == move.vanish[0].y &&
286 move.end.y < move.start.y
287 )
288 ) {
289 symbol += "-0";
290 }
291 return symbol;
292 }
293 return super.getNotation(move);
294 }
7e8a7ea1 295
bb688df5 296};