Commit | Line | Data |
---|---|---|
cd49e617 BA |
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 ShakoRules extends ChessRules { | |
6 | static get PawnSpecs() { | |
7 | return Object.assign( | |
8 | {}, | |
9 | ChessRules.PawnSpecs, | |
10 | { | |
11 | initShift: { w: 2, b: 2 }, | |
12 | promotions: | |
13 | ChessRules.PawnSpecs.promotions.concat([V.ELEPHANT, V.CANNON]) | |
14 | } | |
15 | ); | |
16 | } | |
17 | ||
18 | static get ELEPHANT() { | |
19 | return "e"; | |
20 | } | |
21 | ||
22 | static get CANNON() { | |
23 | return "c"; | |
24 | } | |
25 | ||
26 | static get PIECES() { | |
27 | return ChessRules.PIECES.concat([V.ELEPHANT, V.CANNON]); | |
28 | } | |
29 | ||
30 | getPpath(b) { | |
31 | const prefix = [V.ELEPHANT, V.CANNON].includes(b[1]) ? "Shako/" : ""; | |
32 | return prefix + b; | |
33 | } | |
34 | ||
35 | static get steps() { | |
36 | return Object.assign( | |
37 | {}, | |
38 | ChessRules.steps, | |
39 | { | |
40 | e: [ | |
41 | [-1, -1], | |
42 | [-1, 1], | |
43 | [1, -1], | |
44 | [1, 1], | |
45 | [-2, -2], | |
46 | [-2, 2], | |
47 | [2, -2], | |
48 | [2, 2] | |
49 | ] | |
50 | } | |
51 | ); | |
52 | } | |
53 | ||
54 | static get size() { | |
55 | return { x: 10, y: 10}; | |
56 | } | |
57 | ||
58 | getPotentialMovesFrom([x, y]) { | |
59 | switch (this.getPiece(x, y)) { | |
60 | case V.ELEPHANT: | |
61 | return this.getPotentialElephantMoves([x, y]); | |
62 | case V.CANNON: | |
63 | return this.getPotentialCannonMoves([x, y]); | |
64 | default: | |
65 | return super.getPotentialMovesFrom([x, y]); | |
66 | } | |
67 | } | |
68 | ||
69 | getPotentialElephantMoves([x, y]) { | |
70 | return this.getSlideNJumpMoves([x, y], V.steps[V.ELEPHANT], "oneStep"); | |
71 | } | |
72 | ||
73 | getPotentialCannonMoves([x, y]) { | |
74 | const oppCol = V.GetOppCol(this.turn); | |
75 | let moves = []; | |
76 | // Look in every direction until an obstacle (to jump) is met | |
77 | for (const step of V.steps[V.ROOK]) { | |
78 | let i = x + step[0]; | |
79 | let j = y + step[1]; | |
80 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
81 | moves.push(this.getBasicMove([x, y], [i, j])); | |
82 | i += step[0]; | |
83 | j += step[1]; | |
84 | } | |
85 | // Then, search for an enemy | |
86 | i += step[0]; | |
87 | j += step[1]; | |
88 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
89 | i += step[0]; | |
90 | j += step[1]; | |
91 | } | |
92 | if (V.OnBoard(i, j) && this.getColor(i, j) == oppCol) | |
93 | moves.push(this.getBasicMove([x, y], [i, j])); | |
94 | } | |
95 | return moves; | |
96 | } | |
97 | ||
98 | getCastleMoves([x, y]) { | |
99 | const c = this.getColor(x, y); | |
100 | if (x != (c == "w" ? V.size.x - 2 : 1) || y != this.INIT_COL_KING[c]) | |
101 | return []; //x isn't second rank, or king has moved (shortcut) | |
102 | ||
103 | // Castling ? | |
104 | const oppCol = V.GetOppCol(c); | |
105 | let moves = []; | |
106 | let i = 0; | |
107 | // King, then rook: | |
108 | const finalSquares = [ | |
109 | [3, 4], | |
110 | [7, 6] | |
111 | ]; | |
112 | castlingCheck: for ( | |
113 | let castleSide = 0; | |
114 | castleSide < 2; | |
115 | castleSide++ //large, then small | |
116 | ) { | |
117 | if (this.castleFlags[c][castleSide] >= V.size.y) continue; | |
118 | // If this code is reached, rook and king are on initial position | |
119 | ||
120 | const rookPos = this.castleFlags[c][castleSide]; | |
121 | ||
122 | // Nothing on the path of the king ? (and no checks) | |
123 | const castlingPiece = this.getPiece(x, rookPos); | |
124 | const finDist = finalSquares[castleSide][0] - y; | |
125 | let step = finDist / Math.max(1, Math.abs(finDist)); | |
126 | i = y; | |
127 | do { | |
128 | if ( | |
129 | this.isAttacked([x, i], oppCol) || | |
130 | (this.board[x][i] != V.EMPTY && | |
131 | // NOTE: next check is enough, because of chessboard constraints | |
132 | (this.getColor(x, i) != c || | |
133 | ![V.KING, castlingPiece].includes(this.getPiece(x, i)))) | |
134 | ) { | |
135 | continue castlingCheck; | |
136 | } | |
137 | i += step; | |
138 | } while (i != finalSquares[castleSide][0]); | |
139 | ||
140 | // Nothing on the path to the rook? | |
141 | step = castleSide == 0 ? -1 : 1; | |
142 | for (i = y + step; i != rookPos; i += step) { | |
143 | if (this.board[x][i] != V.EMPTY) continue castlingCheck; | |
144 | } | |
145 | ||
146 | // Nothing on final squares, except maybe king and castling rook? | |
147 | for (i = 0; i < 2; i++) { | |
148 | if ( | |
149 | finalSquares[castleSide][i] != rookPos && | |
150 | this.board[x][finalSquares[castleSide][i]] != V.EMPTY && | |
151 | ( | |
152 | this.getPiece(x, finalSquares[castleSide][i]) != V.KING || | |
153 | this.getColor(x, finalSquares[castleSide][i]) != c | |
154 | ) | |
155 | ) { | |
156 | continue castlingCheck; | |
157 | } | |
158 | } | |
159 | ||
160 | // If this code is reached, castle is valid | |
161 | moves.push( | |
162 | new Move({ | |
163 | appear: [ | |
164 | new PiPo({ | |
165 | x: x, | |
166 | y: finalSquares[castleSide][0], | |
167 | p: V.KING, | |
168 | c: c | |
169 | }), | |
170 | new PiPo({ | |
171 | x: x, | |
172 | y: finalSquares[castleSide][1], | |
173 | p: castlingPiece, | |
174 | c: c | |
175 | }) | |
176 | ], | |
177 | vanish: [ | |
178 | new PiPo({ x: x, y: y, p: V.KING, c: c }), | |
179 | new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c }) | |
180 | ], | |
181 | end: | |
182 | Math.abs(y - rookPos) <= 2 | |
183 | ? { x: x, y: rookPos } | |
184 | : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } | |
185 | }) | |
186 | ); | |
187 | } | |
188 | ||
189 | return moves; | |
190 | } | |
191 | ||
192 | isAttacked(sq, color) { | |
193 | return ( | |
194 | super.isAttacked(sq, color) || | |
195 | this.isAttackedByElephant(sq, color) || | |
196 | this.isAttackedByCannon(sq, color) | |
197 | ); | |
198 | } | |
199 | ||
200 | isAttackedByElephant(sq, color) { | |
201 | return ( | |
202 | this.isAttackedBySlideNJump( | |
203 | sq, color, V.ELEPHANT, V.steps[V.ELEPHANT], "oneStep" | |
204 | ) | |
205 | ); | |
206 | } | |
207 | ||
208 | isAttackedByCannon([x, y], color) { | |
209 | // Reversed process: is there an obstacle in line, | |
210 | // and a cannon next in the same line? | |
211 | for (const step of V.steps[V.ROOK]) { | |
212 | let [i, j] = [x+step[0], y+step[1]]; | |
213 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
214 | i += step[0]; | |
215 | j += step[1]; | |
216 | } | |
217 | if (V.OnBoard(i, j)) { | |
218 | // Keep looking in this direction | |
219 | i += step[0]; | |
220 | j += step[1]; | |
221 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
222 | i += step[0]; | |
223 | j += step[1]; | |
224 | } | |
225 | if ( | |
226 | V.OnBoard(i, j) && | |
227 | this.getPiece(i, j) == V.CANNON && | |
228 | this.getColor(i, j) == color | |
229 | ) { | |
230 | return true; | |
231 | } | |
232 | } | |
233 | } | |
234 | return false; | |
235 | } | |
236 | ||
237 | updateCastleFlags(move, piece) { | |
238 | const c = V.GetOppCol(this.turn); | |
239 | const firstRank = (c == "w" ? V.size.x - 2 : 1); | |
240 | // Update castling flags if rooks are moved | |
241 | const oppCol = this.turn; | |
242 | const oppFirstRank = V.size.x - 1 - firstRank; | |
243 | if (piece == V.KING) | |
244 | this.castleFlags[c] = [V.size.y, V.size.y]; | |
245 | else if ( | |
246 | move.start.x == firstRank && //our rook moves? | |
247 | this.castleFlags[c].includes(move.start.y) | |
248 | ) { | |
249 | const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); | |
250 | this.castleFlags[c][flagIdx] = V.size.y; | |
251 | } | |
252 | // NOTE: not "else if" because a rook could take an opposing rook | |
253 | if ( | |
254 | move.end.x == oppFirstRank && //we took opponent rook? | |
255 | this.castleFlags[oppCol].includes(move.end.y) | |
256 | ) { | |
257 | const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); | |
258 | this.castleFlags[oppCol][flagIdx] = V.size.y; | |
259 | } | |
260 | } | |
261 | ||
262 | static get VALUES() { | |
263 | return Object.assign( | |
264 | { e: 3, c: 5 }, | |
265 | ChessRules.VALUES | |
266 | ); | |
267 | } | |
268 | ||
269 | static get SEARCH_DEPTH() { | |
270 | return 2; | |
271 | } | |
272 | ||
273 | static GenRandInitFen(randomness) { | |
274 | if (randomness == 0) { | |
275 | return ( | |
276 | "c8c/ernbqkbnre/pppppppppp/91/91/91/91/PPPPPPPPPP/ERNBQKBNRE/C8C " + | |
277 | "w 0 bibi -" | |
278 | ); | |
279 | } | |
280 | ||
281 | let pieces = { w: new Array(10), b: new Array(10) }; | |
282 | let flags = ""; | |
283 | // Shuffle pieces on second (and before-last rank if randomness == 2) | |
284 | for (let c of ["w", "b"]) { | |
285 | if (c == 'b' && randomness == 1) { | |
286 | pieces['b'] = pieces['w']; | |
287 | flags += flags; | |
288 | break; | |
289 | } | |
290 | ||
291 | let positions = ArrayFun.range(10); | |
292 | ||
293 | // Get random squares for bishops + elephants | |
294 | const be1Pos = sample([0, 2, 4, 6, 8], 2); | |
295 | const be2Pos = sample([1, 3, 5, 7, 9], 2); | |
296 | const bishop1Pos = be1Pos[0]; | |
297 | const bishop2Pos = be2Pos[0]; | |
298 | const elephant1Pos = be1Pos[1]; | |
299 | const elephant2Pos = be2Pos[1]; | |
300 | // Remove chosen squares | |
301 | (be1Pos.concat(be2Pos)).sort((x, y) => y - x).forEach(pos => { | |
302 | positions.splice(pos, 1); | |
303 | }); | |
304 | ||
305 | let randIndex = randInt(6); | |
306 | const knight1Pos = positions[randIndex]; | |
307 | positions.splice(randIndex, 1); | |
308 | randIndex = randInt(5); | |
309 | const knight2Pos = positions[randIndex]; | |
310 | positions.splice(randIndex, 1); | |
311 | ||
312 | randIndex = randInt(4); | |
313 | const queenPos = positions[randIndex]; | |
314 | positions.splice(randIndex, 1); | |
315 | ||
316 | const rook1Pos = positions[0]; | |
317 | const kingPos = positions[1]; | |
318 | const rook2Pos = positions[2]; | |
319 | ||
320 | pieces[c][elephant1Pos] = "e"; | |
321 | pieces[c][rook1Pos] = "r"; | |
322 | pieces[c][knight1Pos] = "n"; | |
323 | pieces[c][bishop1Pos] = "b"; | |
324 | pieces[c][queenPos] = "q"; | |
325 | pieces[c][kingPos] = "k"; | |
326 | pieces[c][bishop2Pos] = "b"; | |
327 | pieces[c][knight2Pos] = "n"; | |
328 | pieces[c][rook2Pos] = "r"; | |
329 | pieces[c][elephant2Pos] = "e"; | |
330 | flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos); | |
331 | } | |
332 | // Add turn + flags + enpassant | |
333 | return ( | |
334 | "c8c/" + pieces["b"].join("") + | |
335 | "/pppppppppp/91/91/91/91/PPPPPPPPPP/" + | |
336 | pieces["w"].join("").toUpperCase() + "/C8C" + | |
337 | " w 0 " + flags + " -" | |
338 | ); | |
339 | } | |
340 | }; |