Commit | Line | Data |
---|---|---|
269f9cfd | 1 | import { ChessRules, PiPo, Move } from "@/base_rules"; |
269f9cfd BA |
2 | |
3 | export class ShinobiRules extends ChessRules { | |
4 | ||
d5af4af2 BA |
5 | static get LoseOnRepetition() { |
6 | return true; | |
7 | } | |
269f9cfd BA |
8 | |
9 | static get CAPTAIN() { | |
10 | return 'c'; | |
11 | } | |
12 | static get NINJA() { | |
13 | return 'j'; | |
14 | } | |
b0116a67 BA |
15 | static get DRAGON() { |
16 | return 'd'; | |
269f9cfd BA |
17 | } |
18 | static get MONK() { | |
19 | return 'm'; | |
20 | } | |
21 | static get HORSE() { | |
22 | return 'h'; | |
23 | } | |
24 | static get LANCE() { | |
25 | return 'l'; | |
26 | } | |
27 | ||
d5af4af2 BA |
28 | static IsGoodFlags(flags) { |
29 | // Only black can castle | |
30 | return !!flags.match(/^[a-z]{2,2}$/); | |
31 | } | |
32 | ||
269f9cfd BA |
33 | static get PIECES() { |
34 | return ( | |
35 | ChessRules.PIECES | |
b0116a67 | 36 | .concat([V.CAPTAIN, V.NINJA, V.DRAGON, V.MONK, V.HORSE, V.LANCE]) |
269f9cfd BA |
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 | ||
d5af4af2 BA |
49 | getFlagsFen() { |
50 | return this.castleFlags['b'].map(V.CoordToColumn).join(""); | |
51 | } | |
52 | ||
53 | setFlags(fenflags) { | |
54 | this.castleFlags = { 'b': [-1, -1] }; | |
55 | for (let i = 0; i < 2; i++) | |
56 | this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i)); | |
57 | } | |
58 | ||
269f9cfd BA |
59 | static IsGoodFen(fen) { |
60 | if (!ChessRules.IsGoodFen(fen)) return false; | |
61 | const fenParsed = V.ParseFen(fen); | |
62 | // 5) Check reserve | |
cee75a57 | 63 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-2]{5,5}$/)) |
269f9cfd BA |
64 | return false; |
65 | return true; | |
66 | } | |
67 | ||
68 | static ParseFen(fen) { | |
69 | const fenParts = fen.split(" "); | |
70 | return Object.assign( | |
71 | ChessRules.ParseFen(fen), | |
72 | { reserve: fenParts[5] } | |
73 | ); | |
74 | } | |
75 | ||
b0116a67 | 76 | // In hand initially: ninja, dragon, 2 x (monk, horse), lance, pawn. |
cee75a57 BA |
77 | static GenRandInitFen(options) { |
78 | const baseFen = ChessRules.GenRandInitFen(options); | |
79 | const position = baseFen.substr(0, 43) | |
80 | .replace('Q', 'C') | |
81 | .replace(/B/g, '1') | |
82 | .replace(/R/g, 'L') | |
83 | .replace(/N/g, 'H'); | |
84 | return position + " w 0 " + baseFen.substr(48, 2) + " - 11211"; | |
269f9cfd BA |
85 | } |
86 | ||
87 | getFen() { | |
88 | return super.getFen() + " " + this.getReserveFen(); | |
89 | } | |
90 | ||
91 | getFenForRepeat() { | |
92 | return super.getFenForRepeat() + "_" + this.getReserveFen(); | |
93 | } | |
94 | ||
95 | getReserveFen() { | |
96 | // TODO: can simplify other drops variants with this code: | |
97 | return Object.values(this.reserve['w']).join(""); | |
98 | } | |
99 | ||
100 | setOtherVariables(fen) { | |
101 | super.setOtherVariables(fen); | |
102 | const reserve = | |
103 | V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); | |
104 | this.reserve = { | |
105 | w: { | |
5199c0d8 | 106 | [V.NINJA]: reserve[0], |
b0116a67 | 107 | [V.DRAGON]: reserve[1], |
5199c0d8 BA |
108 | [V.MONK]: reserve[2], |
109 | [V.HORSE]: reserve[3], | |
cee75a57 | 110 | [V.LANCE]: reserve[4] |
269f9cfd BA |
111 | } |
112 | }; | |
113 | } | |
114 | ||
115 | getColor(i, j) { | |
d5af4af2 | 116 | if (i >= V.size.x) return 'w'; |
269f9cfd BA |
117 | return this.board[i][j].charAt(0); |
118 | } | |
119 | ||
120 | getPiece(i, j) { | |
121 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; | |
122 | return this.board[i][j].charAt(1); | |
123 | } | |
124 | ||
269f9cfd | 125 | static get RESERVE_PIECES() { |
cee75a57 | 126 | return [V.NINJA, V.DRAGON, V.MONK, V.HORSE, V.LANCE]; |
269f9cfd BA |
127 | } |
128 | ||
129 | getReserveMoves([x, y]) { | |
130 | // color == 'w', no drops for black. | |
131 | const p = V.RESERVE_PIECES[y]; | |
132 | if (this.reserve['w'][p] == 0) return []; | |
133 | let moves = []; | |
134 | for (let i of [4, 5, 6, 7]) { | |
135 | for (let j = 0; j < V.size.y; j++) { | |
136 | if (this.board[i][j] == V.EMPTY) { | |
137 | let mv = new Move({ | |
138 | appear: [ | |
139 | new PiPo({ | |
140 | x: i, | |
141 | y: j, | |
d5af4af2 | 142 | c: 'w', |
269f9cfd BA |
143 | p: p |
144 | }) | |
145 | ], | |
146 | vanish: [], | |
147 | start: { x: x, y: y }, | |
148 | end: { x: i, y: j } | |
149 | }); | |
150 | moves.push(mv); | |
151 | } | |
152 | } | |
153 | } | |
154 | return moves; | |
155 | } | |
156 | ||
157 | static get MapUnpromoted() { | |
158 | return { | |
159 | m: 'b', | |
160 | h: 'n', | |
161 | l: 'r', | |
162 | p: 'c' | |
163 | }; | |
164 | } | |
165 | ||
166 | getPotentialMovesFrom([x, y]) { | |
167 | if (x >= V.size.x) { | |
168 | // Reserves, outside of board: x == sizeX(+1) | |
169 | if (this.turn == 'b') return []; | |
170 | return this.getReserveMoves([x, y]); | |
171 | } | |
172 | // Standard moves | |
173 | const piece = this.getPiece(x, y); | |
174 | const sq = [x, y]; | |
d5af4af2 BA |
175 | if ([V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(piece)) |
176 | return super.getPotentialMovesFrom(sq); | |
269f9cfd | 177 | switch (piece) { |
131cad32 | 178 | case V.KING: return this.getPotentialKingMoves(sq); |
269f9cfd BA |
179 | case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); |
180 | case V.NINJA: return this.getPotentialNinjaMoves(sq); | |
b0116a67 | 181 | case V.DRAGON: return this.getPotentialDragonMoves(sq); |
269f9cfd BA |
182 | } |
183 | let moves = []; | |
184 | switch (piece) { | |
185 | // Unpromoted | |
186 | case V.PAWN: | |
187 | moves = super.getPotentialPawnMoves(sq); | |
d5af4af2 | 188 | break; |
269f9cfd BA |
189 | case V.MONK: |
190 | moves = this.getPotentialMonkMoves(sq); | |
191 | break; | |
192 | case V.HORSE: | |
193 | moves = this.getPotentialHorseMoves(sq); | |
194 | break; | |
195 | case V.LANCE: | |
196 | moves = this.getPotentialLanceMoves(sq); | |
197 | break; | |
198 | } | |
b0116a67 | 199 | const promotionZone = (this.turn == 'w' ? [0, 1] : [7, 6]); |
269f9cfd BA |
200 | const promotedForm = V.MapUnpromoted[piece]; |
201 | moves.forEach(m => { | |
d5af4af2 | 202 | if (promotionZone.includes(m.end.x)) m.appear[0].p = promotedForm; |
269f9cfd BA |
203 | }); |
204 | return moves; | |
205 | } | |
206 | ||
d5af4af2 BA |
207 | getPotentialKingMoves([x, y]) { |
208 | if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]); | |
209 | // Clan doesn't castle: | |
210 | return super.getSlideNJumpMoves( | |
4313762d | 211 | [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1); |
269f9cfd BA |
212 | } |
213 | ||
d5af4af2 | 214 | getPotentialCaptainMoves(sq) { |
269f9cfd | 215 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
4313762d | 216 | return super.getSlideNJumpMoves(sq, steps, 1); |
269f9cfd BA |
217 | } |
218 | ||
d5af4af2 BA |
219 | getPotentialNinjaMoves(sq) { |
220 | return ( | |
221 | super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) | |
4313762d | 222 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1)) |
d5af4af2 BA |
223 | ); |
224 | } | |
225 | ||
b0116a67 | 226 | getPotentialDragonMoves(sq) { |
269f9cfd BA |
227 | return ( |
228 | super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) | |
4313762d | 229 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1)) |
269f9cfd BA |
230 | ); |
231 | } | |
232 | ||
d5af4af2 | 233 | getPotentialMonkMoves(sq) { |
4313762d | 234 | return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1); |
d5af4af2 BA |
235 | } |
236 | ||
269f9cfd | 237 | getPotentialHorseMoves(sq) { |
4313762d | 238 | return super.getSlideNJumpMoves(sq, [ [-2, 1], [-2, -1] ], 1); |
269f9cfd BA |
239 | } |
240 | ||
241 | getPotentialLanceMoves(sq) { | |
d5af4af2 | 242 | return super.getSlideNJumpMoves(sq, [ [-1, 0] ]); |
269f9cfd BA |
243 | } |
244 | ||
245 | isAttacked(sq, color) { | |
246 | if (color == 'b') | |
247 | return (super.isAttacked(sq, 'b') || this.isAttackedByCaptain(sq, 'b')); | |
248 | // Attacked by white: | |
249 | return ( | |
250 | super.isAttackedByKing(sq, 'w') || | |
7e98914d | 251 | super.isAttackedByPawn(sq, 'w') || |
269f9cfd | 252 | this.isAttackedByCaptain(sq, 'w') || |
d5af4af2 | 253 | this.isAttackedByNinja(sq, 'w') || |
b0116a67 | 254 | this.isAttackedByDragon(sq, 'w') || |
269f9cfd BA |
255 | this.isAttackedByMonk(sq, 'w') || |
256 | this.isAttackedByHorse(sq, 'w') || | |
257 | this.isAttackedByLance(sq, 'w') || | |
258 | super.isAttackedByBishop(sq, 'w') || | |
259 | super.isAttackedByKnight(sq, 'w') || | |
260 | super.isAttackedByRook(sq, 'w') | |
261 | ); | |
262 | } | |
263 | ||
264 | isAttackedByCaptain(sq, color) { | |
265 | const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]); | |
266 | return ( | |
4313762d | 267 | super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, 1) |
269f9cfd BA |
268 | ); |
269 | } | |
270 | ||
271 | isAttackedByNinja(sq, color) { | |
272 | return ( | |
d5af4af2 | 273 | super.isAttackedBySlideNJump(sq, color, V.NINJA, V.steps[V.BISHOP]) || |
269f9cfd | 274 | super.isAttackedBySlideNJump( |
4313762d | 275 | sq, color, V.NINJA, V.steps[V.KNIGHT], 1) |
269f9cfd BA |
276 | ); |
277 | } | |
278 | ||
b0116a67 | 279 | isAttackedByDragon(sq, color) { |
269f9cfd | 280 | return ( |
b0116a67 | 281 | super.isAttackedBySlideNJump(sq, color, V.DRAGON, V.steps[V.ROOK]) || |
269f9cfd | 282 | super.isAttackedBySlideNJump( |
4313762d | 283 | sq, color, V.DRAGON, V.steps[V.BISHOP], 1) |
269f9cfd BA |
284 | ); |
285 | } | |
286 | ||
287 | isAttackedByMonk(sq, color) { | |
269f9cfd | 288 | return ( |
d5af4af2 | 289 | super.isAttackedBySlideNJump( |
4313762d | 290 | sq, color, V.MONK, V.steps[V.BISHOP], 1) |
269f9cfd BA |
291 | ); |
292 | } | |
293 | ||
294 | isAttackedByHorse(sq, color) { | |
295 | return ( | |
269f9cfd | 296 | super.isAttackedBySlideNJump( |
4313762d | 297 | sq, color, V.HORSE, [ [2, 1], [2, -1] ], 1) |
269f9cfd BA |
298 | ); |
299 | } | |
300 | ||
301 | isAttackedByLance(sq, color) { | |
d5af4af2 | 302 | return super.isAttackedBySlideNJump(sq, color, V.LANCE, [ [1, 0] ]); |
269f9cfd BA |
303 | } |
304 | ||
305 | getAllValidMoves() { | |
306 | let moves = super.getAllPotentialMoves(); | |
d5af4af2 BA |
307 | if (this.turn == 'w') { |
308 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
309 | moves = moves.concat( | |
310 | this.getReserveMoves([V.size.x, i]) | |
311 | ); | |
312 | } | |
269f9cfd BA |
313 | } |
314 | return this.filterValid(moves); | |
315 | } | |
316 | ||
317 | atLeastOneMove() { | |
d5af4af2 BA |
318 | if (super.atLeastOneMove()) return true; |
319 | if (this.turn == 'w') { | |
269f9cfd BA |
320 | // Search one reserve move |
321 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
322 | let moves = this.filterValid( | |
d5af4af2 | 323 | this.getReserveMoves([V.size.x, i]) |
269f9cfd BA |
324 | ); |
325 | if (moves.length > 0) return true; | |
326 | } | |
269f9cfd | 327 | } |
d5af4af2 BA |
328 | return false; |
329 | } | |
330 | ||
331 | updateCastleFlags(move, piece) { | |
332 | // Only black can castle: | |
333 | const firstRank = 0; | |
334 | if (piece == V.KING && move.appear[0].c == 'b') | |
335 | this.castleFlags['b'] = [8, 8]; | |
336 | else if ( | |
337 | move.start.x == firstRank && | |
338 | this.castleFlags['b'].includes(move.start.y) | |
339 | ) { | |
340 | const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1); | |
341 | this.castleFlags['b'][flagIdx] = 8; | |
342 | } | |
343 | else if ( | |
344 | move.end.x == firstRank && | |
345 | this.castleFlags['b'].includes(move.end.y) | |
346 | ) { | |
347 | const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1); | |
348 | this.castleFlags['b'][flagIdx] = 8; | |
349 | } | |
269f9cfd BA |
350 | } |
351 | ||
269f9cfd BA |
352 | postPlay(move) { |
353 | super.postPlay(move); | |
354 | // Skip castle: | |
355 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
356 | const color = move.appear[0].c; | |
357 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; | |
358 | } | |
359 | ||
360 | postUndo(move) { | |
361 | super.postUndo(move); | |
362 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
363 | const color = this.turn; | |
364 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; | |
365 | } | |
366 | ||
269f9cfd BA |
367 | static get SEARCH_DEPTH() { |
368 | return 2; | |
d5af4af2 BA |
369 | } |
370 | ||
371 | getCurrentScore() { | |
372 | const color = this.turn; | |
373 | const nodrawResult = (color == "w" ? "0-1" : "1-0"); | |
374 | const oppLastRank = (color == 'w' ? 7 : 0); | |
375 | if (this.kingPos[V.GetOppCol(color)][0] == oppLastRank) | |
376 | return nodrawResult; | |
377 | if (this.atLeastOneMove()) return "*"; | |
378 | return nodrawResult; | |
379 | } | |
269f9cfd | 380 | |
269f9cfd BA |
381 | static get VALUES() { |
382 | return ( | |
383 | Object.assign( | |
384 | { | |
385 | c: 4, | |
d5af4af2 | 386 | j: 7, |
cee75a57 | 387 | d: 6, |
d5af4af2 BA |
388 | m: 2, |
389 | h: 2, | |
390 | l: 2 | |
269f9cfd BA |
391 | }, |
392 | ChessRules.VALUES | |
393 | ) | |
394 | ); | |
395 | } | |
396 | ||
397 | evalPosition() { | |
398 | let evaluation = super.evalPosition(); | |
d5af4af2 | 399 | // Add reserve: |
269f9cfd BA |
400 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { |
401 | const p = V.RESERVE_PIECES[i]; | |
402 | evaluation += this.reserve["w"][p] * V.VALUES[p]; | |
269f9cfd BA |
403 | } |
404 | return evaluation; | |
405 | } | |
406 | ||
407 | getNotation(move) { | |
b0116a67 BA |
408 | if (move.vanish.length > 0) { |
409 | let notation = super.getNotation(move); | |
410 | if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p) | |
411 | notation += "=" + move.appear[0].p.toUpperCase(); | |
412 | return notation; | |
413 | } | |
269f9cfd BA |
414 | // Drop: |
415 | const piece = | |
416 | move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; | |
417 | return piece + "@" + V.CoordsToSquare(move.end); | |
418 | } | |
419 | ||
420 | }; |