Commit | Line | Data |
---|---|---|
1269441e BA |
1 | import { ChessRules } from "@/base_rules"; |
2 | ||
3 | export class EmpireRules extends ChessRules { | |
4 | ||
5 | static get PawnSpecs() { | |
6 | return Object.assign( | |
7 | {}, | |
8 | ChessRules.PawnSpecs, | |
9 | { promotions: [V.QUEEN] } | |
10 | ); | |
11 | } | |
12 | ||
13 | static get LoseOnRepetition() { | |
14 | return true; | |
15 | } | |
16 | ||
17 | static IsGoodFlags(flags) { | |
18 | // Only black can castle | |
19 | return !!flags.match(/^[a-z]{2,2}$/); | |
20 | } | |
21 | ||
22 | getPpath(b) { | |
23 | return (b[0] == 'w' ? "Empire/" : "") + b; | |
24 | } | |
25 | ||
26 | static GenRandInitFen(randomness) { | |
27 | if (randomness == 0) | |
28 | return "rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w 0 ah -"; | |
29 | ||
30 | // Mapping kingdom --> empire: | |
31 | const piecesMap = { | |
32 | 'R': 'T', | |
33 | 'N': 'E', | |
34 | 'B': 'C', | |
35 | 'Q': 'D', | |
36 | 'K': 'K' | |
37 | }; | |
38 | ||
39 | const baseFen = ChessRules.GenRandInitFen(randomness); | |
40 | return ( | |
41 | baseFen.substr(0, 24) + "PPPSSPPP/8/" + | |
42 | baseFen.substr(35, 8).split('').map(p => piecesMap[p]).join('') + | |
43 | baseFen.substr(43, 5) + baseFen.substr(50) | |
44 | ); | |
45 | } | |
46 | ||
47 | getFlagsFen() { | |
48 | return this.castleFlags['b'].map(V.CoordToColumn).join(""); | |
49 | } | |
50 | ||
51 | setFlags(fenflags) { | |
52 | this.castleFlags = { 'b': [-1, -1] }; | |
53 | for (let i = 0; i < 2; i++) | |
54 | this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i)); | |
55 | } | |
56 | ||
57 | static get TOWER() { | |
58 | return 't'; | |
59 | } | |
60 | static get EAGLE() { | |
61 | return 'e'; | |
62 | } | |
63 | static get CARDINAL() { | |
64 | return 'c'; | |
65 | } | |
66 | static get DUKE() { | |
67 | return 'd'; | |
68 | } | |
69 | static get SOLDIER() { | |
70 | return 's'; | |
71 | } | |
72 | // Kaiser is technically a King, so let's keep things simple. | |
73 | ||
74 | static get PIECES() { | |
75 | return ChessRules.PIECES.concat( | |
76 | [V.TOWER, V.EAGLE, V.CARDINAL, V.DUKE, V.SOLDIER]); | |
77 | } | |
78 | ||
79 | getPotentialMovesFrom(sq) { | |
80 | let moves = []; | |
81 | const piece = this.getPiece(sq[0], sq[1]); | |
82 | switch (piece) { | |
83 | case V.TOWER: | |
84 | moves = this.getPotentialTowerMoves(sq); | |
85 | break; | |
86 | case V.EAGLE: | |
87 | moves = this.getPotentialEagleMoves(sq); | |
88 | break; | |
89 | case V.CARDINAL: | |
90 | moves = this.getPotentialCardinalMoves(sq); | |
91 | break; | |
92 | case V.DUKE: | |
93 | moves = this.getPotentialDukeMoves(sq); | |
94 | break; | |
95 | case V.SOLDIER: | |
96 | moves = this.getPotentialSoldierMoves(sq); | |
97 | break; | |
98 | default: | |
99 | moves = super.getPotentialMovesFrom(sq); | |
100 | } | |
101 | if ( | |
102 | piece != V.KING && | |
103 | this.kingPos['w'][0] != this.kingPos['b'][0] && | |
104 | this.kingPos['w'][1] != this.kingPos['b'][1] | |
105 | ) { | |
106 | return moves; | |
107 | } | |
108 | // TODO: factor two next "if" into one (rank/column...) | |
109 | if (this.kingPos['w'][1] == this.kingPos['b'][1]) { | |
110 | const colKing = this.kingPos['w'][1]; | |
111 | let intercept = 0; //count intercepting pieces | |
112 | let [kingPos1, kingPos2] = [this.kingPos['w'][0], this.kingPos['b'][0]]; | |
113 | if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1]; | |
114 | for (let i = kingPos1 + 1; i < kingPos2; i++) { | |
115 | if (this.board[i][colKing] != V.EMPTY) intercept++; | |
116 | } | |
117 | if (intercept >= 2) return moves; | |
118 | // intercept == 1 (0 is impossible): | |
119 | // Any move not removing intercept is OK | |
120 | return moves.filter(m => { | |
121 | return ( | |
122 | // From another column? | |
123 | m.start.y != colKing || | |
124 | // From behind a king? (including kings themselves!) | |
125 | m.start.x <= kingPos1 || | |
126 | m.start.x >= kingPos2 || | |
127 | // Intercept piece moving: must remain in-between | |
128 | ( | |
129 | m.end.y == colKing && | |
130 | m.end.x > kingPos1 && | |
131 | m.end.x < kingPos2 | |
132 | ) | |
133 | ); | |
134 | }); | |
135 | } | |
136 | if (this.kingPos['w'][0] == this.kingPos['b'][0]) { | |
137 | const rowKing = this.kingPos['w'][0]; | |
138 | let intercept = 0; //count intercepting pieces | |
139 | let [kingPos1, kingPos2] = [this.kingPos['w'][1], this.kingPos['b'][1]]; | |
140 | if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1]; | |
141 | for (let i = kingPos1 + 1; i < kingPos2; i++) { | |
142 | if (this.board[rowKing][i] != V.EMPTY) intercept++; | |
143 | } | |
144 | if (intercept >= 2) return moves; | |
145 | // intercept == 1 (0 is impossible): | |
146 | // Any move not removing intercept is OK | |
147 | return moves.filter(m => { | |
148 | return ( | |
149 | // From another row? | |
150 | m.start.x != rowKing || | |
151 | // From "behind" a king? (including kings themselves!) | |
152 | m.start.y <= kingPos1 || | |
153 | m.start.y >= kingPos2 || | |
154 | // Intercept piece moving: must remain in-between | |
155 | ( | |
156 | m.end.x == rowKing && | |
157 | m.end.y > kingPos1 && | |
158 | m.end.y < kingPos2 | |
159 | ) | |
160 | ); | |
161 | }); | |
162 | } | |
163 | // piece == king: check only if move.end.y == enemy king column, | |
164 | // or if move.end.x == enemy king rank. | |
165 | const color = this.getColor(sq[0], sq[1]); | |
166 | const oppCol = V.GetOppCol(color); | |
167 | // check == -1 if (row, or col) unchecked, 1 if checked and occupied, | |
168 | // 0 if checked and clear | |
169 | let check = [-1, -1]; | |
170 | return moves.filter(m => { | |
171 | if ( | |
172 | m.end.y != this.kingPos[oppCol][1] && | |
173 | m.end.x != this.kingPos[oppCol][0] | |
174 | ) { | |
175 | return true; | |
176 | } | |
177 | // TODO: factor two next "if"... | |
178 | if (m.end.x == this.kingPos[oppCol][0]) { | |
179 | if (check[0] < 0) { | |
180 | // Do the check: | |
181 | check[0] = 0; | |
182 | let [kingPos1, kingPos2] = | |
183 | [this.kingPos[color][1], this.kingPos[oppCol][1]]; | |
184 | if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1]; | |
185 | for (let i = kingPos1 + 1; i < kingPos2; i++) { | |
186 | if (this.board[m.end.x][i] != V.EMPTY) { | |
187 | check[0]++; | |
188 | break; | |
189 | } | |
190 | } | |
191 | return check[0] == 1; | |
192 | } | |
193 | // Check already done: | |
194 | return check[0] == 1; | |
195 | } | |
196 | //if (m.end.y == this.kingPos[oppCol][1]) //true... | |
197 | if (check[1] < 0) { | |
198 | // Do the check: | |
199 | check[1] = 0; | |
200 | let [kingPos1, kingPos2] = | |
201 | [this.kingPos[color][0], this.kingPos[oppCol][0]]; | |
202 | if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1]; | |
203 | for (let i = kingPos1 + 1; i < kingPos2; i++) { | |
204 | if (this.board[i][m.end.y] != V.EMPTY) { | |
205 | check[1]++; | |
206 | break; | |
207 | } | |
208 | } | |
209 | return check[1] == 1; | |
210 | } | |
211 | // Check already done: | |
212 | return check[1] == 1; | |
213 | }); | |
214 | } | |
215 | ||
216 | getSlideNJumpMoves_([x, y], steps, oneStep) { | |
217 | let moves = []; | |
218 | outerLoop: for (let step of steps) { | |
219 | const s = step.s; | |
220 | let i = x + s[0]; | |
221 | let j = y + s[1]; | |
222 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
223 | if (!step.onlyTake) moves.push(this.getBasicMove([x, y], [i, j])); | |
224 | // NOTE: (bad) HACK here, since onlyTake is true only for Eagle | |
225 | // capturing moves, which are oneStep... | |
226 | if (!!oneStep || !!step.onlyTake) continue outerLoop; | |
227 | i += s[0]; | |
228 | j += s[1]; | |
229 | } | |
230 | if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !step.onlyMove) | |
231 | moves.push(this.getBasicMove([x, y], [i, j])); | |
232 | } | |
233 | return moves; | |
234 | } | |
235 | ||
236 | static get steps() { | |
237 | return ( | |
238 | Object.assign( | |
239 | { | |
240 | t: [ | |
241 | { s: [-1, 0] }, | |
242 | { s: [1, 0] }, | |
243 | { s: [0, -1] }, | |
244 | { s: [0, 1] }, | |
245 | { s: [-1, -1], onlyMove: true }, | |
246 | { s: [-1, 1], onlyMove: true }, | |
247 | { s: [1, -1], onlyMove: true }, | |
248 | { s: [1, 1], onlyMove: true } | |
249 | ], | |
250 | c: [ | |
251 | { s: [-1, 0], onlyMove: true }, | |
252 | { s: [1, 0], onlyMove: true }, | |
253 | { s: [0, -1], onlyMove: true }, | |
254 | { s: [0, 1], onlyMove: true }, | |
255 | { s: [-1, -1] }, | |
256 | { s: [-1, 1] }, | |
257 | { s: [1, -1] }, | |
258 | { s: [1, 1] } | |
259 | ], | |
260 | e: [ | |
261 | { s: [-1, 0], onlyMove: true }, | |
262 | { s: [1, 0], onlyMove: true }, | |
263 | { s: [0, -1], onlyMove: true }, | |
264 | { s: [0, 1], onlyMove: true }, | |
265 | { s: [-1, -1], onlyMove: true }, | |
266 | { s: [-1, 1], onlyMove: true }, | |
267 | { s: [1, -1], onlyMove: true }, | |
268 | { s: [1, 1], onlyMove: true }, | |
269 | { s: [-2, -1], onlyTake: true }, | |
270 | { s: [-2, 1], onlyTake: true }, | |
271 | { s: [-1, -2], onlyTake: true }, | |
272 | { s: [-1, 2], onlyTake: true }, | |
273 | { s: [1, -2], onlyTake: true }, | |
274 | { s: [1, 2], onlyTake: true }, | |
275 | { s: [2, -1], onlyTake: true }, | |
276 | { s: [2, 1], onlyTake: true } | |
277 | ] | |
278 | }, | |
279 | ChessRules.steps | |
280 | ) | |
281 | ); | |
282 | } | |
283 | ||
284 | getPotentialTowerMoves(sq) { | |
285 | return this.getSlideNJumpMoves_(sq, V.steps[V.TOWER]); | |
286 | } | |
287 | ||
288 | getPotentialCardinalMoves(sq) { | |
289 | return this.getSlideNJumpMoves_(sq, V.steps[V.CARDINAL]); | |
290 | } | |
291 | ||
292 | getPotentialEagleMoves(sq) { | |
293 | return this.getSlideNJumpMoves_(sq, V.steps[V.EAGLE]); | |
294 | } | |
295 | ||
296 | getPotentialDukeMoves([x, y]) { | |
297 | // Anything to capture around? mark other steps to explore after | |
298 | let steps = []; | |
299 | const oppCol = V.GetOppCol(this.getColor(x, y)); | |
300 | let moves = []; | |
301 | for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { | |
302 | const [i, j] = [x + s[0], y + s[1]]; | |
303 | if ( | |
304 | V.OnBoard(i, j) && | |
305 | this.board[i][j] != V.EMPTY && | |
306 | this.getColor(i, j) == oppCol | |
307 | ) { | |
308 | moves.push(super.getBasicMove([x, y], [i, j])); | |
309 | } | |
310 | else steps.push({ s: s, onlyMove: true }); | |
311 | } | |
312 | if (steps.length > 0) { | |
313 | const noncapturingMoves = this.getSlideNJumpMoves_([x, y], steps); | |
314 | Array.prototype.push.apply(moves, noncapturingMoves); | |
315 | } | |
316 | return moves; | |
317 | } | |
318 | ||
319 | getPotentialKingMoves([x, y]) { | |
320 | if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]); | |
321 | // Empire doesn't castle: | |
322 | return super.getSlideNJumpMoves( | |
323 | [x, y], | |
324 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), | |
325 | "oneStep" | |
326 | ); | |
327 | } | |
328 | ||
329 | getPotentialSoldierMoves([x, y]) { | |
330 | const c = this.getColor(x, y); | |
331 | const shiftX = (c == 'w' ? -1 : 1); | |
332 | const lastRank = (c == 'w' && x == 0 || c == 'b' && x == 9); | |
333 | let steps = []; | |
334 | if (!lastRank) steps.push([shiftX, 0]); | |
335 | if (y > 0) steps.push([0, -1]); | |
336 | if (y < 9) steps.push([0, 1]); | |
337 | return super.getSlideNJumpMoves([x, y], steps, "oneStep"); | |
338 | } | |
339 | ||
340 | isAttacked(sq, color) { | |
341 | if (color == 'b') return super.isAttacked(sq, color); | |
342 | // Empire: only pawn and king (+ queen if promotion) in common: | |
343 | return ( | |
344 | super.isAttackedByPawn(sq, color) || | |
345 | this.isAttackedByTower(sq, color) || | |
346 | this.isAttackedByEagle(sq, color) || | |
347 | this.isAttackedByCardinal(sq, color) || | |
348 | this.isAttackedByDuke(sq, color) || | |
349 | this.isAttackedBySoldier(sq, color) || | |
350 | super.isAttackedByKing(sq, color) || | |
351 | super.isAttackedByQueen(sq, color) | |
352 | ); | |
353 | } | |
354 | ||
355 | isAttackedByTower(sq, color) { | |
356 | return super.isAttackedBySlideNJump(sq, color, V.TOWER, V.steps[V.ROOK]); | |
357 | } | |
358 | ||
359 | isAttackedByEagle(sq, color) { | |
360 | return super.isAttackedBySlideNJump( | |
361 | sq, color, V.EAGLE, V.steps[V.KNIGHT], "oneStep"); | |
362 | } | |
363 | ||
364 | isAttackedByCardinal(sq, color) { | |
365 | return super.isAttackedBySlideNJump( | |
366 | sq, color, V.CARDINAL, V.steps[V.BISHOP]); | |
367 | } | |
368 | ||
369 | isAttackedByDuke(sq, color) { | |
370 | return ( | |
371 | super.isAttackedBySlideNJump( | |
372 | sq, color, V.DUKE, | |
373 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep" | |
374 | ) | |
375 | ); | |
376 | } | |
377 | ||
378 | isAttackedBySoldier([x, y], color) { | |
379 | const shiftX = (color == 'w' ? 1 : -1); //shift from king | |
380 | return super.isAttackedBySlideNJump( | |
381 | [x, y], color, V.SOLDIER, [[shiftX, 0], [0, 1], [0, -1]], "oneStep"); | |
382 | } | |
383 | ||
384 | updateCastleFlags(move, piece) { | |
385 | // Only black can castle: | |
386 | const firstRank = 0; | |
387 | if (piece == V.KING && move.appear[0].c == 'b') | |
388 | this.castleFlags['b'] = [8, 8]; | |
389 | else if ( | |
390 | move.start.x == firstRank && | |
391 | this.castleFlags['b'].includes(move.start.y) | |
392 | ) { | |
393 | const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1); | |
394 | this.castleFlags['b'][flagIdx] = 8; | |
395 | } | |
396 | else if ( | |
397 | move.end.x == firstRank && | |
398 | this.castleFlags['b'].includes(move.end.y) | |
399 | ) { | |
400 | const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1); | |
401 | this.castleFlags['b'][flagIdx] = 8; | |
402 | } | |
403 | } | |
404 | ||
405 | getCurrentScore() { | |
406 | // Turn has changed: | |
407 | const color = V.GetOppCol(this.turn); | |
408 | const lastRank = (color == 'w' ? 0 : 7); | |
409 | if (this.kingPos[color][0] == lastRank) | |
410 | // The opposing edge is reached! | |
411 | return color == "w" ? "1-0" : "0-1"; | |
412 | if (this.atLeastOneMove()) return "*"; | |
413 | // Game over | |
414 | const oppCol = this.turn; | |
415 | return (oppCol == "w" ? "0-1" : "1-0"); | |
416 | } | |
417 | ||
418 | static get VALUES() { | |
419 | return Object.assign( | |
420 | {}, | |
421 | ChessRules.VALUES, | |
422 | { | |
423 | t: 7, | |
424 | e: 7, | |
425 | c: 4, | |
426 | d: 4, | |
427 | s: 2 | |
428 | } | |
429 | ); | |
430 | } | |
431 | ||
432 | static get SEARCH_DEPTH() { | |
433 | return 2; | |
434 | } | |
435 | ||
436 | }; |