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 | ||
4313762d BA |
26 | static GenRandInitFen(options) { |
27 | if (options.randomness == 0) | |
1269441e BA |
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 | ||
4313762d | 39 | const baseFen = ChessRules.GenRandInitFen(options); |
1269441e BA |
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); | |
1269441e BA |
167 | return moves.filter(m => { |
168 | if ( | |
169 | m.end.y != this.kingPos[oppCol][1] && | |
170 | m.end.x != this.kingPos[oppCol][0] | |
171 | ) { | |
172 | return true; | |
173 | } | |
7caf0e69 BA |
174 | // check == -1 if (row, or col) unchecked, 1 if checked and occupied, |
175 | // 0 if checked and clear | |
176 | let check = [-1, -1]; | |
1269441e BA |
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; | |
7caf0e69 | 182 | let [kingPos1, kingPos2] = [m.end.y, this.kingPos[oppCol][1]]; |
1269441e BA |
183 | if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1]; |
184 | for (let i = kingPos1 + 1; i < kingPos2; i++) { | |
185 | if (this.board[m.end.x][i] != V.EMPTY) { | |
186 | check[0]++; | |
187 | break; | |
188 | } | |
189 | } | |
190 | return check[0] == 1; | |
191 | } | |
192 | // Check already done: | |
193 | return check[0] == 1; | |
194 | } | |
195 | //if (m.end.y == this.kingPos[oppCol][1]) //true... | |
196 | if (check[1] < 0) { | |
197 | // Do the check: | |
198 | check[1] = 0; | |
7caf0e69 | 199 | let [kingPos1, kingPos2] = [m.end.x, this.kingPos[oppCol][0]]; |
1269441e BA |
200 | if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1]; |
201 | for (let i = kingPos1 + 1; i < kingPos2; i++) { | |
202 | if (this.board[i][m.end.y] != V.EMPTY) { | |
203 | check[1]++; | |
204 | break; | |
205 | } | |
206 | } | |
207 | return check[1] == 1; | |
208 | } | |
209 | // Check already done: | |
210 | return check[1] == 1; | |
211 | }); | |
212 | } | |
213 | ||
4313762d | 214 | // TODO: some merging to do with Orda method (and into base_rules.js) |
1269441e BA |
215 | getSlideNJumpMoves_([x, y], steps, oneStep) { |
216 | let moves = []; | |
217 | outerLoop: for (let step of steps) { | |
218 | const s = step.s; | |
219 | let i = x + s[0]; | |
220 | let j = y + s[1]; | |
221 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
222 | if (!step.onlyTake) moves.push(this.getBasicMove([x, y], [i, j])); | |
223 | // NOTE: (bad) HACK here, since onlyTake is true only for Eagle | |
224 | // capturing moves, which are oneStep... | |
4313762d | 225 | if (oneStep || step.onlyTake) continue outerLoop; |
1269441e BA |
226 | i += s[0]; |
227 | j += s[1]; | |
228 | } | |
229 | if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !step.onlyMove) | |
230 | moves.push(this.getBasicMove([x, y], [i, j])); | |
231 | } | |
232 | return moves; | |
233 | } | |
234 | ||
235 | static get steps() { | |
236 | return ( | |
237 | Object.assign( | |
238 | { | |
239 | t: [ | |
240 | { s: [-1, 0] }, | |
241 | { s: [1, 0] }, | |
242 | { s: [0, -1] }, | |
243 | { s: [0, 1] }, | |
244 | { s: [-1, -1], onlyMove: true }, | |
245 | { s: [-1, 1], onlyMove: true }, | |
246 | { s: [1, -1], onlyMove: true }, | |
247 | { s: [1, 1], onlyMove: true } | |
248 | ], | |
249 | c: [ | |
250 | { s: [-1, 0], onlyMove: true }, | |
251 | { s: [1, 0], onlyMove: true }, | |
252 | { s: [0, -1], onlyMove: true }, | |
253 | { s: [0, 1], onlyMove: true }, | |
254 | { s: [-1, -1] }, | |
255 | { s: [-1, 1] }, | |
256 | { s: [1, -1] }, | |
257 | { s: [1, 1] } | |
258 | ], | |
259 | e: [ | |
260 | { s: [-1, 0], onlyMove: true }, | |
261 | { s: [1, 0], onlyMove: true }, | |
262 | { s: [0, -1], onlyMove: true }, | |
263 | { s: [0, 1], onlyMove: true }, | |
264 | { s: [-1, -1], onlyMove: true }, | |
265 | { s: [-1, 1], onlyMove: true }, | |
266 | { s: [1, -1], onlyMove: true }, | |
267 | { s: [1, 1], onlyMove: true }, | |
268 | { s: [-2, -1], onlyTake: true }, | |
269 | { s: [-2, 1], onlyTake: true }, | |
270 | { s: [-1, -2], onlyTake: true }, | |
271 | { s: [-1, 2], onlyTake: true }, | |
272 | { s: [1, -2], onlyTake: true }, | |
273 | { s: [1, 2], onlyTake: true }, | |
274 | { s: [2, -1], onlyTake: true }, | |
275 | { s: [2, 1], onlyTake: true } | |
276 | ] | |
277 | }, | |
278 | ChessRules.steps | |
279 | ) | |
280 | ); | |
281 | } | |
282 | ||
283 | getPotentialTowerMoves(sq) { | |
284 | return this.getSlideNJumpMoves_(sq, V.steps[V.TOWER]); | |
285 | } | |
286 | ||
287 | getPotentialCardinalMoves(sq) { | |
288 | return this.getSlideNJumpMoves_(sq, V.steps[V.CARDINAL]); | |
289 | } | |
290 | ||
291 | getPotentialEagleMoves(sq) { | |
292 | return this.getSlideNJumpMoves_(sq, V.steps[V.EAGLE]); | |
293 | } | |
294 | ||
295 | getPotentialDukeMoves([x, y]) { | |
296 | // Anything to capture around? mark other steps to explore after | |
297 | let steps = []; | |
298 | const oppCol = V.GetOppCol(this.getColor(x, y)); | |
299 | let moves = []; | |
300 | for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { | |
301 | const [i, j] = [x + s[0], y + s[1]]; | |
302 | if ( | |
303 | V.OnBoard(i, j) && | |
304 | this.board[i][j] != V.EMPTY && | |
305 | this.getColor(i, j) == oppCol | |
306 | ) { | |
307 | moves.push(super.getBasicMove([x, y], [i, j])); | |
308 | } | |
309 | else steps.push({ s: s, onlyMove: true }); | |
310 | } | |
311 | if (steps.length > 0) { | |
312 | const noncapturingMoves = this.getSlideNJumpMoves_([x, y], steps); | |
313 | Array.prototype.push.apply(moves, noncapturingMoves); | |
314 | } | |
315 | return moves; | |
316 | } | |
317 | ||
318 | getPotentialKingMoves([x, y]) { | |
319 | if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]); | |
320 | // Empire doesn't castle: | |
321 | return super.getSlideNJumpMoves( | |
4313762d | 322 | [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1); |
1269441e BA |
323 | } |
324 | ||
325 | getPotentialSoldierMoves([x, y]) { | |
326 | const c = this.getColor(x, y); | |
327 | const shiftX = (c == 'w' ? -1 : 1); | |
328 | const lastRank = (c == 'w' && x == 0 || c == 'b' && x == 9); | |
329 | let steps = []; | |
330 | if (!lastRank) steps.push([shiftX, 0]); | |
331 | if (y > 0) steps.push([0, -1]); | |
332 | if (y < 9) steps.push([0, 1]); | |
4313762d | 333 | return super.getSlideNJumpMoves([x, y], steps, 1); |
1269441e BA |
334 | } |
335 | ||
336 | isAttacked(sq, color) { | |
337 | if (color == 'b') return super.isAttacked(sq, color); | |
338 | // Empire: only pawn and king (+ queen if promotion) in common: | |
339 | return ( | |
340 | super.isAttackedByPawn(sq, color) || | |
341 | this.isAttackedByTower(sq, color) || | |
342 | this.isAttackedByEagle(sq, color) || | |
343 | this.isAttackedByCardinal(sq, color) || | |
344 | this.isAttackedByDuke(sq, color) || | |
345 | this.isAttackedBySoldier(sq, color) || | |
346 | super.isAttackedByKing(sq, color) || | |
347 | super.isAttackedByQueen(sq, color) | |
348 | ); | |
349 | } | |
350 | ||
351 | isAttackedByTower(sq, color) { | |
352 | return super.isAttackedBySlideNJump(sq, color, V.TOWER, V.steps[V.ROOK]); | |
353 | } | |
354 | ||
355 | isAttackedByEagle(sq, color) { | |
356 | return super.isAttackedBySlideNJump( | |
4313762d | 357 | sq, color, V.EAGLE, V.steps[V.KNIGHT], 1); |
1269441e BA |
358 | } |
359 | ||
360 | isAttackedByCardinal(sq, color) { | |
361 | return super.isAttackedBySlideNJump( | |
362 | sq, color, V.CARDINAL, V.steps[V.BISHOP]); | |
363 | } | |
364 | ||
365 | isAttackedByDuke(sq, color) { | |
366 | return ( | |
367 | super.isAttackedBySlideNJump( | |
368 | sq, color, V.DUKE, | |
4313762d | 369 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1 |
1269441e BA |
370 | ) |
371 | ); | |
372 | } | |
373 | ||
374 | isAttackedBySoldier([x, y], color) { | |
375 | const shiftX = (color == 'w' ? 1 : -1); //shift from king | |
376 | return super.isAttackedBySlideNJump( | |
4313762d | 377 | [x, y], color, V.SOLDIER, [[shiftX, 0], [0, 1], [0, -1]], 1); |
1269441e BA |
378 | } |
379 | ||
380 | updateCastleFlags(move, piece) { | |
381 | // Only black can castle: | |
382 | const firstRank = 0; | |
383 | if (piece == V.KING && move.appear[0].c == 'b') | |
384 | this.castleFlags['b'] = [8, 8]; | |
385 | else if ( | |
386 | move.start.x == firstRank && | |
387 | this.castleFlags['b'].includes(move.start.y) | |
388 | ) { | |
389 | const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1); | |
390 | this.castleFlags['b'][flagIdx] = 8; | |
391 | } | |
392 | else if ( | |
393 | move.end.x == firstRank && | |
394 | this.castleFlags['b'].includes(move.end.y) | |
395 | ) { | |
396 | const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1); | |
397 | this.castleFlags['b'][flagIdx] = 8; | |
398 | } | |
399 | } | |
400 | ||
401 | getCurrentScore() { | |
402 | // Turn has changed: | |
403 | const color = V.GetOppCol(this.turn); | |
404 | const lastRank = (color == 'w' ? 0 : 7); | |
405 | if (this.kingPos[color][0] == lastRank) | |
406 | // The opposing edge is reached! | |
407 | return color == "w" ? "1-0" : "0-1"; | |
408 | if (this.atLeastOneMove()) return "*"; | |
409 | // Game over | |
410 | const oppCol = this.turn; | |
411 | return (oppCol == "w" ? "0-1" : "1-0"); | |
412 | } | |
413 | ||
414 | static get VALUES() { | |
415 | return Object.assign( | |
416 | {}, | |
417 | ChessRules.VALUES, | |
418 | { | |
419 | t: 7, | |
420 | e: 7, | |
421 | c: 4, | |
422 | d: 4, | |
423 | s: 2 | |
424 | } | |
425 | ); | |
426 | } | |
427 | ||
428 | static get SEARCH_DEPTH() { | |
429 | return 2; | |
430 | } | |
431 | ||
432 | }; |