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