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 | } | |
15 | static get SAMURAI() { | |
16 | return 's'; | |
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 | |
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 | ||
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 | |
63 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{6,6}$/)) | |
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 | ||
d5af4af2 | 76 | // In hand initially: captain, ninja, samurai + 2 x monk, horse, lance. |
269f9cfd BA |
77 | static GenRandInitFen(randomness) { |
78 | const baseFen = ChessRules.GenRandInitFen(Math.min(randomness, 1)); | |
79 | return ( | |
d5af4af2 BA |
80 | baseFen.substr(0, 35) + "3CK3 " + |
81 | "w 0 " + baseFen.substr(48, 2) + " - 111222" | |
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: { | |
104 | [V.CAPTAIN]: reserve[0], | |
105 | [V.NINJA]: reserve[1], | |
106 | [V.SAMURAI]: reserve[2], | |
107 | [V.MONK]: reserve[3], | |
d5af4af2 | 108 | [V.HORSE]: reserve[4], |
269f9cfd BA |
109 | [V.LANCE]: reserve[5] |
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 BA |
124 | static get RESERVE_PIECES() { |
125 | return [V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE]; | |
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 BA |
176 | switch (piece) { |
177 | case V.KING: return super.getPotentialKingMoves(sq); | |
178 | case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); | |
179 | case V.NINJA: return this.getPotentialNinjaMoves(sq); | |
180 | case V.SAMURAI: return this.getPotentialSamuraiMoves(sq); | |
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 | } | |
198 | const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]); | |
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 | ||
228 | getPotentialSamuraiMoves(sq) { | |
269f9cfd BA |
229 | return ( |
230 | super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) | |
231 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) | |
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') || | |
253 | this.isAttackedByCaptain(sq, 'w') || | |
d5af4af2 BA |
254 | this.isAttackedByNinja(sq, 'w') || |
255 | this.isAttackedBySamurai(sq, 'w') || | |
269f9cfd BA |
256 | this.isAttackedByMonk(sq, 'w') || |
257 | this.isAttackedByHorse(sq, 'w') || | |
258 | this.isAttackedByLance(sq, 'w') || | |
259 | super.isAttackedByBishop(sq, 'w') || | |
260 | super.isAttackedByKnight(sq, 'w') || | |
261 | super.isAttackedByRook(sq, 'w') | |
262 | ); | |
263 | } | |
264 | ||
265 | isAttackedByCaptain(sq, color) { | |
266 | const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]); | |
267 | return ( | |
d5af4af2 | 268 | super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, "oneStep") |
269f9cfd BA |
269 | ); |
270 | } | |
271 | ||
272 | isAttackedByNinja(sq, color) { | |
273 | return ( | |
d5af4af2 | 274 | super.isAttackedBySlideNJump(sq, color, V.NINJA, V.steps[V.BISHOP]) || |
269f9cfd | 275 | super.isAttackedBySlideNJump( |
d5af4af2 | 276 | sq, color, V.NINJA, V.steps[V.KNIGHT], "oneStep") |
269f9cfd BA |
277 | ); |
278 | } | |
279 | ||
280 | isAttackedBySamurai(sq, color) { | |
281 | return ( | |
d5af4af2 | 282 | super.isAttackedBySlideNJump(sq, color, V.SAMURAI, V.steps[V.ROOK]) || |
269f9cfd | 283 | super.isAttackedBySlideNJump( |
d5af4af2 | 284 | sq, color, V.SAMURAI, V.steps[V.KNIGHT], "oneStep") |
269f9cfd BA |
285 | ); |
286 | } | |
287 | ||
288 | isAttackedByMonk(sq, color) { | |
269f9cfd | 289 | return ( |
d5af4af2 BA |
290 | super.isAttackedBySlideNJump( |
291 | sq, color, V.MONK, V.steps[V.BISHOP], "oneStep") | |
269f9cfd BA |
292 | ); |
293 | } | |
294 | ||
295 | isAttackedByHorse(sq, color) { | |
296 | return ( | |
269f9cfd | 297 | super.isAttackedBySlideNJump( |
d5af4af2 | 298 | sq, color, V.HORSE, [ [2, 1], [2, -1] ], "oneStep") |
269f9cfd BA |
299 | ); |
300 | } | |
301 | ||
302 | isAttackedByLance(sq, color) { | |
d5af4af2 | 303 | return super.isAttackedBySlideNJump(sq, color, V.LANCE, [ [1, 0] ]); |
269f9cfd BA |
304 | } |
305 | ||
306 | getAllValidMoves() { | |
307 | let moves = super.getAllPotentialMoves(); | |
d5af4af2 BA |
308 | if (this.turn == 'w') { |
309 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
310 | moves = moves.concat( | |
311 | this.getReserveMoves([V.size.x, i]) | |
312 | ); | |
313 | } | |
269f9cfd BA |
314 | } |
315 | return this.filterValid(moves); | |
316 | } | |
317 | ||
318 | atLeastOneMove() { | |
d5af4af2 BA |
319 | if (super.atLeastOneMove()) return true; |
320 | if (this.turn == 'w') { | |
269f9cfd BA |
321 | // Search one reserve move |
322 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
323 | let moves = this.filterValid( | |
d5af4af2 | 324 | this.getReserveMoves([V.size.x, i]) |
269f9cfd BA |
325 | ); |
326 | if (moves.length > 0) return true; | |
327 | } | |
269f9cfd | 328 | } |
d5af4af2 BA |
329 | return false; |
330 | } | |
331 | ||
332 | updateCastleFlags(move, piece) { | |
333 | // Only black can castle: | |
334 | const firstRank = 0; | |
335 | if (piece == V.KING && move.appear[0].c == 'b') | |
336 | this.castleFlags['b'] = [8, 8]; | |
337 | else if ( | |
338 | move.start.x == firstRank && | |
339 | this.castleFlags['b'].includes(move.start.y) | |
340 | ) { | |
341 | const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1); | |
342 | this.castleFlags['b'][flagIdx] = 8; | |
343 | } | |
344 | else if ( | |
345 | move.end.x == firstRank && | |
346 | this.castleFlags['b'].includes(move.end.y) | |
347 | ) { | |
348 | const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1); | |
349 | this.castleFlags['b'][flagIdx] = 8; | |
350 | } | |
269f9cfd BA |
351 | } |
352 | ||
269f9cfd BA |
353 | postPlay(move) { |
354 | super.postPlay(move); | |
355 | // Skip castle: | |
356 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
357 | const color = move.appear[0].c; | |
358 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; | |
359 | } | |
360 | ||
361 | postUndo(move) { | |
362 | super.postUndo(move); | |
363 | if (move.vanish.length == 2 && move.appear.length == 2) return; | |
364 | const color = this.turn; | |
365 | if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; | |
366 | } | |
367 | ||
269f9cfd BA |
368 | static get SEARCH_DEPTH() { |
369 | return 2; | |
d5af4af2 BA |
370 | } |
371 | ||
372 | getCurrentScore() { | |
373 | const color = this.turn; | |
374 | const nodrawResult = (color == "w" ? "0-1" : "1-0"); | |
375 | const oppLastRank = (color == 'w' ? 7 : 0); | |
376 | if (this.kingPos[V.GetOppCol(color)][0] == oppLastRank) | |
377 | return nodrawResult; | |
378 | if (this.atLeastOneMove()) return "*"; | |
379 | return nodrawResult; | |
380 | } | |
269f9cfd | 381 | |
269f9cfd BA |
382 | static get VALUES() { |
383 | return ( | |
384 | Object.assign( | |
385 | { | |
386 | c: 4, | |
d5af4af2 BA |
387 | j: 7, |
388 | s: 8, | |
389 | m: 2, | |
390 | h: 2, | |
391 | l: 2 | |
269f9cfd BA |
392 | }, |
393 | ChessRules.VALUES | |
394 | ) | |
395 | ); | |
396 | } | |
397 | ||
398 | evalPosition() { | |
399 | let evaluation = super.evalPosition(); | |
d5af4af2 | 400 | // Add reserve: |
269f9cfd BA |
401 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { |
402 | const p = V.RESERVE_PIECES[i]; | |
403 | evaluation += this.reserve["w"][p] * V.VALUES[p]; | |
269f9cfd BA |
404 | } |
405 | return evaluation; | |
406 | } | |
407 | ||
408 | getNotation(move) { | |
409 | if (move.vanish.length > 0) return super.getNotation(move); | |
410 | // Drop: | |
411 | const piece = | |
412 | move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; | |
413 | return piece + "@" + V.CoordsToSquare(move.end); | |
414 | } | |
415 | ||
416 | }; |