Commit | Line | Data |
---|---|---|
3f22c2c3 | 1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
bb688df5 BA |
2 | import { ArrayFun } from "@/utils/array"; |
3 | import { randInt, sample } from "@/utils/alea"; | |
4 | ||
5 | export 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 | }; |