Commit | Line | Data |
---|---|---|
73fbcfc8 BA |
1 | import { ChessRules, PiPo, Move } from "@/base_rules"; |
2 | import { ArrayFun } from "@/utils/array"; | |
3 | ||
4 | export class ShogunRules extends ChessRules { | |
5 | ||
6 | static get CAPTAIN() { | |
7 | return 'c'; | |
8 | } | |
9 | static get GENERAL() { | |
10 | return 'g'; | |
11 | } | |
12 | static get ARCHBISHOP() { | |
13 | return 'a'; | |
14 | } | |
15 | static get MORTAR() { | |
16 | return 'm'; | |
17 | } | |
18 | static get DUCHESS() { | |
19 | return 'f'; | |
20 | } | |
21 | ||
22 | static get PIECES() { | |
23 | return ( | |
24 | ChessRules.PIECES | |
25 | .concat([V.CAPTAIN, V.GENERAL, V.ARCHBISHOP, V.MORTAR, V.DUCHESS]) | |
26 | ); | |
27 | } | |
28 | ||
29 | getPpath(b) { | |
30 | return "Shogun/" + b; | |
31 | } | |
32 | ||
33 | getReservePpath(index, color) { | |
34 | return "Shogun/" + color + V.RESERVE_PIECES[index]; | |
35 | } | |
36 | ||
37 | static IsGoodFen(fen) { | |
38 | if (!ChessRules.IsGoodFen(fen)) return false; | |
39 | const fenParsed = V.ParseFen(fen); | |
40 | // 5) Check reserves | |
41 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/)) | |
42 | return false; | |
43 | return true; | |
44 | } | |
45 | ||
46 | static ParseFen(fen) { | |
47 | const fenParts = fen.split(" "); | |
48 | return Object.assign( | |
49 | ChessRules.ParseFen(fen), | |
50 | { reserve: fenParts[5] } | |
51 | ); | |
52 | } | |
53 | ||
4313762d BA |
54 | static GenRandInitFen(options) { |
55 | return ChessRules.GenRandInitFen(options) + " 0000000000"; | |
73fbcfc8 BA |
56 | } |
57 | ||
58 | getFen() { | |
59 | return super.getFen() + " " + this.getReserveFen(); | |
60 | } | |
61 | ||
62 | getFenForRepeat() { | |
63 | return super.getFenForRepeat() + "_" + this.getReserveFen(); | |
64 | } | |
65 | ||
66 | getReserveFen() { | |
67 | let counts = new Array(10); | |
68 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
69 | counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]]; | |
70 | counts[5 + i] = this.reserve["b"][V.RESERVE_PIECES[i]]; | |
71 | } | |
72 | return counts.join(""); | |
73 | } | |
74 | ||
75 | setOtherVariables(fen) { | |
76 | super.setOtherVariables(fen); | |
77 | // Also init reserves (used by the interface to show landable pieces) | |
78 | const reserve = | |
79 | V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); | |
80 | this.reserve = { | |
81 | w: { | |
82 | [V.PAWN]: reserve[0], | |
83 | [V.ROOK]: reserve[1], | |
84 | [V.KNIGHT]: reserve[2], | |
85 | [V.BISHOP]: reserve[3], | |
86 | [V.DUCHESS]: reserve[4] | |
87 | }, | |
88 | b: { | |
89 | [V.PAWN]: reserve[5], | |
90 | [V.ROOK]: reserve[6], | |
91 | [V.KNIGHT]: reserve[7], | |
92 | [V.BISHOP]: reserve[8], | |
93 | [V.DUCHESS]: reserve[9] | |
94 | } | |
95 | }; | |
96 | } | |
97 | ||
98 | getColor(i, j) { | |
99 | if (i >= V.size.x) return i == V.size.x ? "w" : "b"; | |
100 | return this.board[i][j].charAt(0); | |
101 | } | |
102 | ||
103 | getPiece(i, j) { | |
104 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; | |
105 | return this.board[i][j].charAt(1); | |
106 | } | |
107 | ||
108 | // Ordering on reserve pieces | |
109 | static get RESERVE_PIECES() { | |
110 | return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.DUCHESS]; | |
111 | } | |
112 | ||
113 | getReserveMoves([x, y]) { | |
114 | const color = this.turn; | |
115 | const iZone = (color == 'w' ? [3, 4, 5, 6, 7] : [0, 1, 2, 3, 4]); | |
116 | const p = V.RESERVE_PIECES[y]; | |
117 | if (this.reserve[color][p] == 0) return []; | |
118 | let moves = []; | |
119 | for (let i of iZone) { | |
120 | for (let j = 0; j < V.size.y; j++) { | |
121 | if (this.board[i][j] == V.EMPTY) { | |
122 | let mv = new Move({ | |
123 | appear: [ | |
124 | new PiPo({ | |
125 | x: i, | |
126 | y: j, | |
127 | c: color, | |
128 | p: p | |
129 | }) | |
130 | ], | |
131 | vanish: [], | |
132 | start: { x: x, y: y }, //a bit artificial... | |
133 | end: { x: i, y: j } | |
134 | }); | |
135 | moves.push(mv); | |
136 | } | |
137 | } | |
138 | } | |
139 | return moves; | |
140 | } | |
141 | ||
142 | static get MapUnpromoted() { | |
143 | return { | |
144 | f: 'q', | |
145 | r: 'm', | |
146 | b: 'a', | |
147 | p: 'c', | |
148 | n: 'g' | |
149 | }; | |
150 | } | |
151 | ||
152 | getPotentialMovesFrom([x, y]) { | |
153 | if (x >= V.size.x) | |
154 | // Reserves, outside of board: x == sizeX(+1) | |
155 | return this.getReserveMoves([x, y]); | |
156 | // Standard moves | |
157 | const piece = this.getPiece(x, y); | |
158 | const sq = [x, y]; | |
159 | if (piece == V.KING) return super.getPotentialKingMoves(sq); | |
160 | let moves = []; | |
161 | switch (piece) { | |
162 | // Unpromoted | |
163 | case V.PAWN: | |
164 | return this.getPotentialPawnMoves(sq); | |
165 | case V.ROOK: | |
166 | moves = super.getPotentialRookMoves(sq); | |
167 | break; | |
168 | case V.KNIGHT: | |
169 | moves = super.getPotentialKnightMoves(sq); | |
170 | break; | |
171 | case V.BISHOP: | |
172 | moves = super.getPotentialBishopMoves(sq); | |
173 | break; | |
174 | case V.DUCHESS: | |
175 | moves = this.getPotentialDuchessMoves(sq); | |
176 | break; | |
177 | } | |
178 | if ([V.ROOK, V.KNIGHT, V.BISHOP, V.DUCHESS].includes(piece)) { | |
179 | let extraMoves = []; | |
180 | // Check that no promoted form is already on board: | |
181 | const promotedForm = V.MapUnpromoted[piece]; | |
182 | const c = this.turn; | |
183 | if ( | |
184 | this.board.some(b => | |
185 | b.some(cell => | |
186 | cell[0] == c && cell[1] == promotedForm) | |
187 | ) | |
188 | ) { | |
189 | return moves; | |
190 | } | |
191 | const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]); | |
192 | moves.forEach(m => { | |
193 | if ( | |
194 | promotionZone.includes(m.end.x) || | |
195 | promotionZone.includes(m.start.x) | |
196 | ) { | |
197 | let newMove = JSON.parse(JSON.stringify(m)); | |
198 | newMove.appear[0].p = promotedForm; | |
199 | extraMoves.push(newMove); | |
200 | } | |
201 | }); | |
202 | return moves.concat(extraMoves); | |
203 | } | |
204 | switch (piece) { | |
205 | // Promoted | |
206 | case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); | |
207 | case V.MORTAR: return this.getPotentialMortarMoves(sq); | |
208 | case V.GENERAL: return this.getPotentialGeneralMoves(sq); | |
209 | case V.ARCHBISHOP: return this.getPotentialArchbishopMoves(sq); | |
210 | case V.QUEEN: return super.getPotentialQueenMoves(sq); | |
211 | } | |
212 | return []; //never reached | |
213 | } | |
214 | ||
215 | getPotentialPawnMoves([x, y]) { | |
216 | // NOTE: apply promotion freely, but not on en-passant | |
217 | const c = this.turn; | |
218 | const oppCol = V.GetOppCol(c); | |
219 | const forward = (c == 'w' ? -1 : 1); | |
220 | const initialRank = (c == 'w' ? 6 : 1); | |
221 | let moves = []; | |
222 | // Pawn push | |
223 | let [i, j] = [x + forward, y]; | |
224 | if (this.board[i][j] == V.EMPTY) { | |
225 | moves.push(this.getBasicMove([x, y], [i, j])); | |
226 | if (x == initialRank && this.board[i + forward][j] == V.EMPTY) | |
227 | moves.push(this.getBasicMove([x, y], [i + forward, j])); | |
228 | } | |
229 | // Captures | |
230 | for (let shiftY of [-1, 1]) { | |
231 | [i, j] = [x + forward, y + shiftY]; | |
232 | if ( | |
233 | V.OnBoard(i, j) && | |
234 | this.board[i][j] != V.EMPTY && | |
235 | this.getColor(i, j) == oppCol | |
236 | ) { | |
237 | moves.push(this.getBasicMove([x, y], [i, j])); | |
238 | } | |
239 | } | |
240 | let extraMoves = []; | |
241 | const promotionZone = (this.turn == 'w' ? [1, 2] : [5, 6]); | |
242 | const lastRank = (c == 'w' ? 0 : 7); | |
243 | moves.forEach(m => { | |
244 | if (m.end.x == lastRank) | |
245 | // Force promotion | |
246 | m.appear[0].p = V.CAPTAIN; | |
247 | else if (promotionZone.includes(m.end.x)) { | |
248 | let newMove = JSON.parse(JSON.stringify(m)); | |
249 | newMove.appear[0].p = V.CAPTAIN; | |
250 | extraMoves.push(newMove); | |
251 | } | |
252 | }); | |
253 | return ( | |
254 | moves.concat(extraMoves) | |
255 | .concat(super.getEnpassantCaptures([x, y], forward)) | |
256 | ); | |
257 | } | |
258 | ||
259 | getPotentialDuchessMoves(sq) { | |
4313762d | 260 | return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1); |
73fbcfc8 BA |
261 | } |
262 | ||
263 | getPotentialCaptainMoves(sq) { | |
264 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
4313762d | 265 | return super.getSlideNJumpMoves(sq, steps, 1); |
73fbcfc8 BA |
266 | } |
267 | ||
268 | getPotentialMortarMoves(sq) { | |
269 | return ( | |
270 | super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) | |
4313762d | 271 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1)) |
73fbcfc8 BA |
272 | ); |
273 | } | |
274 | ||
275 | getPotentialGeneralMoves(sq) { | |
276 | const steps = | |
277 | V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); | |
4313762d | 278 | return super.getSlideNJumpMoves(sq, steps, 1); |
73fbcfc8 BA |
279 | } |
280 | ||
281 | getPotentialArchbishopMoves(sq) { | |
282 | return ( | |
283 | super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) | |
4313762d | 284 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1)) |
73fbcfc8 BA |
285 | ); |
286 | } | |
287 | ||
288 | isAttacked(sq, color) { | |
289 | return ( | |
290 | super.isAttacked(sq, color) || | |
291 | this.isAttackedByDuchess(sq, color) || | |
292 | this.isAttackedByCaptain(sq, color) || | |
293 | this.isAttackedByMortar(sq, color) || | |
294 | this.isAttackedByGeneral(sq, color) || | |
295 | this.isAttackedByArchbishop(sq, color) | |
296 | ); | |
297 | } | |
298 | ||
299 | isAttackedByDuchess(sq, color) { | |
300 | return ( | |
301 | super.isAttackedBySlideNJump( | |
4313762d | 302 | sq, color, V.DUCHESS, V.steps[V.BISHOP], 1) |
73fbcfc8 BA |
303 | ); |
304 | } | |
305 | ||
306 | isAttackedByCaptain(sq, color) { | |
307 | const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]); | |
308 | return ( | |
4313762d | 309 | super.isAttackedBySlideNJump(sq, color, V.DUCHESS, steps, 1) |
73fbcfc8 BA |
310 | ); |
311 | } | |
312 | ||
313 | isAttackedByMortar(sq, color) { | |
314 | return ( | |
315 | super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) || | |
316 | super.isAttackedBySlideNJump( | |
4313762d | 317 | sq, color, V.MORTAR, V.steps[V.KNIGHT], 1) |
73fbcfc8 BA |
318 | ); |
319 | } | |
320 | ||
321 | isAttackedByGeneral(sq, color) { | |
322 | const steps = | |
323 | V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); | |
324 | return ( | |
4313762d | 325 | super.isAttackedBySlideNJump(sq, color, V.GENERAL, steps, 1) |
73fbcfc8 BA |
326 | ); |
327 | } | |
328 | ||
329 | isAttackedByArchbishop(sq, color) { | |
330 | return ( | |
331 | super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP]) | |
332 | || | |
333 | super.isAttackedBySlideNJump( | |
4313762d | 334 | sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], 1) |
73fbcfc8 BA |
335 | ); |
336 | } | |
337 | ||
338 | getAllValidMoves() { | |
339 | let moves = super.getAllPotentialMoves(); | |
340 | const color = this.turn; | |
341 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
342 | moves = moves.concat( | |
343 | this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) | |
344 | ); | |
345 | } | |
346 | return this.filterValid(moves); | |
347 | } | |
348 | ||
349 | atLeastOneMove() { | |
350 | if (!super.atLeastOneMove()) { | |
351 | // Search one reserve move | |
352 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
353 | let moves = this.filterValid( | |
354 | this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) | |
355 | ); | |
356 | if (moves.length > 0) return true; | |
357 | } | |
358 | return false; | |
359 | } | |
360 | return true; | |
361 | } | |
362 | ||
363 | static get MapPromoted() { | |
364 | return { | |
365 | q: 'f', | |
366 | m: 'r', | |
367 | a: 'b', | |
368 | c: 'p', | |
369 | g: 'n' | |
370 | }; | |
371 | } | |
372 | ||
373 | getUnpromotedForm(piece) { | |
374 | if (Object.keys(V.MapPromoted).includes(piece)) | |
375 | return V.MapPromoted[piece]; | |
376 | return piece; | |
377 | } | |
378 | ||
379 | postPlay(move) { | |
380 | super.postPlay(move); | |
381 | // Skip castle: | |
382 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
383 | const color = move.appear[0].c; | |
384 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; | |
385 | else if (move.vanish.length == 2) | |
386 | this.reserve[color][this.getUnpromotedForm(move.vanish[1].p)]++; | |
387 | } | |
388 | ||
389 | postUndo(move) { | |
390 | super.postUndo(move); | |
391 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
392 | const color = this.turn; | |
393 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; | |
394 | else if (move.vanish.length == 2) | |
395 | this.reserve[color][this.getUnpromotedForm(move.vanish[1].p)]--; | |
396 | } | |
397 | ||
398 | static get SEARCH_DEPTH() { | |
399 | return 2; | |
400 | } | |
401 | ||
402 | static get VALUES() { | |
403 | return ( | |
404 | Object.assign( | |
405 | { | |
406 | c: 4, | |
407 | g: 5, | |
408 | a: 7, | |
409 | m: 7, | |
410 | f: 2 | |
411 | }, | |
412 | ChessRules.VALUES | |
413 | ) | |
414 | ); | |
415 | } | |
416 | ||
417 | evalPosition() { | |
418 | let evaluation = super.evalPosition(); | |
419 | // Add reserves: | |
420 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
421 | const p = V.RESERVE_PIECES[i]; | |
422 | evaluation += this.reserve["w"][p] * V.VALUES[p]; | |
423 | evaluation -= this.reserve["b"][p] * V.VALUES[p]; | |
424 | } | |
425 | return evaluation; | |
426 | } | |
427 | ||
428 | getNotation(move) { | |
429 | if (move.vanish.length > 0) return super.getNotation(move); | |
430 | // Rebirth: | |
431 | const piece = | |
432 | move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; | |
433 | return piece + "@" + V.CoordsToSquare(move.end); | |
434 | } | |
435 | ||
436 | }; |