Commit | Line | Data |
---|---|---|
269f9cfd BA |
1 | import { ChessRules, PiPo, Move } from "@/base_rules"; |
2 | import { ArrayFun } from "@/utils/array"; | |
3 | ||
4 | export class ShinobiRules extends ChessRules { | |
5 | ||
6 | /* Would be unused: | |
7 | static get PawnSpecs() { | |
8 | return Object.assign( | |
9 | { promotions: [V.PAWN] }, | |
10 | ChessRules.PawnSpecs | |
11 | ); | |
12 | } */ | |
13 | ||
14 | static get CAPTAIN() { | |
15 | return 'c'; | |
16 | } | |
17 | static get NINJA() { | |
18 | return 'j'; | |
19 | } | |
20 | static get SAMURAI() { | |
21 | return 's'; | |
22 | } | |
23 | static get MONK() { | |
24 | return 'm'; | |
25 | } | |
26 | static get HORSE() { | |
27 | return 'h'; | |
28 | } | |
29 | static get LANCE() { | |
30 | return 'l'; | |
31 | } | |
32 | ||
33 | static get PIECES() { | |
34 | return ( | |
35 | ChessRules.PIECES | |
36 | .concat([V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE]) | |
37 | ); | |
38 | } | |
39 | ||
40 | getPpath(b) { | |
41 | if (b[0] == 'b' && b[1] != 'c') return b; | |
42 | return "Shinobi/" + b; | |
43 | } | |
44 | ||
45 | getReservePpath(index, color) { | |
46 | return "Shinobi/" + color + V.RESERVE_PIECES[index]; | |
47 | } | |
48 | ||
49 | static IsGoodFen(fen) { | |
50 | if (!ChessRules.IsGoodFen(fen)) return false; | |
51 | const fenParsed = V.ParseFen(fen); | |
52 | // 5) Check reserve | |
53 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{6,6}$/)) | |
54 | return false; | |
55 | return true; | |
56 | } | |
57 | ||
58 | static ParseFen(fen) { | |
59 | const fenParts = fen.split(" "); | |
60 | return Object.assign( | |
61 | ChessRules.ParseFen(fen), | |
62 | { reserve: fenParts[5] } | |
63 | ); | |
64 | } | |
65 | ||
66 | // In hand initially: another captain, a ninja + a samurai, | |
67 | // and 2 x monk, horse, lance (TODO) | |
68 | static GenRandInitFen(randomness) { | |
69 | const baseFen = ChessRules.GenRandInitFen(Math.min(randomness, 1)); | |
70 | return ( | |
71 | baseFen.substr(0, 33) + "3CK3 " + | |
72 | "w 0 " + baseFen.substr(38, 2) + " - 111222" | |
73 | ); | |
74 | } | |
75 | ||
76 | getFen() { | |
77 | return super.getFen() + " " + this.getReserveFen(); | |
78 | } | |
79 | ||
80 | getFenForRepeat() { | |
81 | return super.getFenForRepeat() + "_" + this.getReserveFen(); | |
82 | } | |
83 | ||
84 | getReserveFen() { | |
85 | // TODO: can simplify other drops variants with this code: | |
86 | return Object.values(this.reserve['w']).join(""); | |
87 | } | |
88 | ||
89 | setOtherVariables(fen) { | |
90 | super.setOtherVariables(fen); | |
91 | const reserve = | |
92 | V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); | |
93 | this.reserve = { | |
94 | w: { | |
95 | [V.CAPTAIN]: reserve[0], | |
96 | [V.NINJA]: reserve[1], | |
97 | [V.SAMURAI]: reserve[2], | |
98 | [V.MONK]: reserve[3], | |
99 | [V.HORSE]: reserve[4] | |
100 | [V.LANCE]: reserve[5] | |
101 | } | |
102 | }; | |
103 | } | |
104 | ||
105 | getColor(i, j) { | |
106 | if (i >= V.size.x) return i == V.size.x ? "w" : "b"; | |
107 | return this.board[i][j].charAt(0); | |
108 | } | |
109 | ||
110 | getPiece(i, j) { | |
111 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; | |
112 | return this.board[i][j].charAt(1); | |
113 | } | |
114 | ||
115 | // Ordering on reserve pieces | |
116 | static get RESERVE_PIECES() { | |
117 | return [V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE]; | |
118 | } | |
119 | ||
120 | getReserveMoves([x, y]) { | |
121 | // color == 'w', no drops for black. | |
122 | const p = V.RESERVE_PIECES[y]; | |
123 | if (this.reserve['w'][p] == 0) return []; | |
124 | let moves = []; | |
125 | for (let i of [4, 5, 6, 7]) { | |
126 | for (let j = 0; j < V.size.y; j++) { | |
127 | if (this.board[i][j] == V.EMPTY) { | |
128 | let mv = new Move({ | |
129 | appear: [ | |
130 | new PiPo({ | |
131 | x: i, | |
132 | y: j, | |
133 | c: color, | |
134 | p: p | |
135 | }) | |
136 | ], | |
137 | vanish: [], | |
138 | start: { x: x, y: y }, | |
139 | end: { x: i, y: j } | |
140 | }); | |
141 | moves.push(mv); | |
142 | } | |
143 | } | |
144 | } | |
145 | return moves; | |
146 | } | |
147 | ||
148 | static get MapUnpromoted() { | |
149 | return { | |
150 | m: 'b', | |
151 | h: 'n', | |
152 | l: 'r', | |
153 | p: 'c' | |
154 | }; | |
155 | } | |
156 | ||
157 | getPotentialMovesFrom([x, y]) { | |
158 | if (x >= V.size.x) { | |
159 | // Reserves, outside of board: x == sizeX(+1) | |
160 | if (this.turn == 'b') return []; | |
161 | return this.getReserveMoves([x, y]); | |
162 | } | |
163 | // Standard moves | |
164 | const piece = this.getPiece(x, y); | |
165 | const sq = [x, y]; | |
166 | if (ChessRules.includes(piece)) return super.getPotentialMovesFrom(sq); | |
167 | switch (piece) { | |
168 | case V.KING: return super.getPotentialKingMoves(sq); | |
169 | case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); | |
170 | case V.NINJA: return this.getPotentialNinjaMoves(sq); | |
171 | case V.SAMURAI: return this.getPotentialSamuraiMoves(sq); | |
172 | } | |
173 | let moves = []; | |
174 | switch (piece) { | |
175 | // Unpromoted | |
176 | case V.PAWN: | |
177 | moves = super.getPotentialPawnMoves(sq); | |
178 | case V.MONK: | |
179 | moves = this.getPotentialMonkMoves(sq); | |
180 | break; | |
181 | case V.HORSE: | |
182 | moves = this.getPotentialHorseMoves(sq); | |
183 | break; | |
184 | case V.LANCE: | |
185 | moves = this.getPotentialLanceMoves(sq); | |
186 | break; | |
187 | } | |
188 | const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]); | |
189 | const promotedForm = V.MapUnpromoted[piece]; | |
190 | moves.forEach(m => { | |
191 | if (promotionZone.includes(m.end.x)) move.appear[0].p = promotedForm; | |
192 | }); | |
193 | return moves; | |
194 | } | |
195 | ||
196 | getPotentialCaptainMoves([x, y]) { | |
197 | } | |
198 | ||
199 | // TODO: adapt... | |
200 | getPotentialNinjaMoves(sq) { | |
201 | return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"); | |
202 | } | |
203 | ||
204 | getPotentialSamuraiMoves(sq) { | |
205 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
206 | return super.getSlideNJumpMoves(sq, steps, "oneStep"); | |
207 | } | |
208 | ||
209 | getPotentialMonkMoves(sq) { | |
210 | return ( | |
211 | super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) | |
212 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) | |
213 | ); | |
214 | } | |
215 | ||
216 | getPotentialHorseMoves(sq) { | |
217 | const steps = | |
218 | V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); | |
219 | return super.getSlideNJumpMoves(sq, steps, "oneStep"); | |
220 | } | |
221 | ||
222 | getPotentialLanceMoves(sq) { | |
223 | return ( | |
224 | super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) | |
225 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) | |
226 | ); | |
227 | } | |
228 | ||
229 | isAttacked(sq, color) { | |
230 | if (color == 'b') | |
231 | return (super.isAttacked(sq, 'b') || this.isAttackedByCaptain(sq, 'b')); | |
232 | // Attacked by white: | |
233 | return ( | |
234 | super.isAttackedByKing(sq, 'w') || | |
235 | this.isAttackedByCaptain(sq, 'w') || | |
236 | this.isAttackedByNinja(sq, 'w') | |
237 | this.isAttackedBySamurai(sq, 'w') | |
238 | this.isAttackedByMonk(sq, 'w') || | |
239 | this.isAttackedByHorse(sq, 'w') || | |
240 | this.isAttackedByLance(sq, 'w') || | |
241 | super.isAttackedByBishop(sq, 'w') || | |
242 | super.isAttackedByKnight(sq, 'w') || | |
243 | super.isAttackedByRook(sq, 'w') | |
244 | ); | |
245 | } | |
246 | ||
247 | isAttackedByCaptain(sq, color) { | |
248 | const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]); | |
249 | return ( | |
250 | super.isAttackedBySlideNJump(sq, color, V.DUCHESS, steps, "oneStep") | |
251 | ); | |
252 | } | |
253 | ||
254 | isAttackedByNinja(sq, color) { | |
255 | return ( | |
256 | super.isAttackedBySlideNJump( | |
257 | sq, color, V.DUCHESS, V.steps[V.BISHOP], "oneStep") | |
258 | ); | |
259 | } | |
260 | ||
261 | isAttackedBySamurai(sq, color) { | |
262 | return ( | |
263 | super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) || | |
264 | super.isAttackedBySlideNJump( | |
265 | sq, color, V.MORTAR, V.steps[V.KNIGHT], "oneStep") | |
266 | ); | |
267 | } | |
268 | ||
269 | isAttackedByMonk(sq, color) { | |
270 | const steps = | |
271 | V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]); | |
272 | return ( | |
273 | super.isAttackedBySlideNJump(sq, color, V.GENERAL, steps, "oneStep") | |
274 | ); | |
275 | } | |
276 | ||
277 | isAttackedByHorse(sq, color) { | |
278 | return ( | |
279 | super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP]) | |
280 | || | |
281 | super.isAttackedBySlideNJump( | |
282 | sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep") | |
283 | ); | |
284 | } | |
285 | ||
286 | isAttackedByLance(sq, color) { | |
287 | return ( | |
288 | super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP]) | |
289 | || | |
290 | super.isAttackedBySlideNJump( | |
291 | sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep") | |
292 | ); | |
293 | } | |
294 | ||
295 | getAllValidMoves() { | |
296 | let moves = super.getAllPotentialMoves(); | |
297 | const color = this.turn; | |
298 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
299 | moves = moves.concat( | |
300 | this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) | |
301 | ); | |
302 | } | |
303 | return this.filterValid(moves); | |
304 | } | |
305 | ||
306 | atLeastOneMove() { | |
307 | if (!super.atLeastOneMove()) { | |
308 | // Search one reserve move | |
309 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
310 | let moves = this.filterValid( | |
311 | this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) | |
312 | ); | |
313 | if (moves.length > 0) return true; | |
314 | } | |
315 | return false; | |
316 | } | |
317 | return true; | |
318 | } | |
319 | ||
320 | // TODO: only black can castle (see Orda) | |
321 | ||
322 | postPlay(move) { | |
323 | super.postPlay(move); | |
324 | // Skip castle: | |
325 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
326 | const color = move.appear[0].c; | |
327 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; | |
328 | } | |
329 | ||
330 | postUndo(move) { | |
331 | super.postUndo(move); | |
332 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
333 | const color = this.turn; | |
334 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; | |
335 | } | |
336 | ||
337 | /* | |
338 | static get SEARCH_DEPTH() { | |
339 | return 2; | |
340 | } */ | |
341 | ||
342 | // TODO: | |
343 | static get VALUES() { | |
344 | return ( | |
345 | Object.assign( | |
346 | { | |
347 | c: 4, | |
348 | g: 5, | |
349 | a: 7, | |
350 | m: 7, | |
351 | f: 2 | |
352 | }, | |
353 | ChessRules.VALUES | |
354 | ) | |
355 | ); | |
356 | } | |
357 | ||
358 | evalPosition() { | |
359 | let evaluation = super.evalPosition(); | |
360 | // Add reserves: | |
361 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
362 | const p = V.RESERVE_PIECES[i]; | |
363 | evaluation += this.reserve["w"][p] * V.VALUES[p]; | |
364 | evaluation -= this.reserve["b"][p] * V.VALUES[p]; | |
365 | } | |
366 | return evaluation; | |
367 | } | |
368 | ||
369 | getNotation(move) { | |
370 | if (move.vanish.length > 0) return super.getNotation(move); | |
371 | // Drop: | |
372 | const piece = | |
373 | move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; | |
374 | return piece + "@" + V.CoordsToSquare(move.end); | |
375 | } | |
376 | ||
377 | }; |