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 | |
5199c0d8 | 63 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{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 | ||
5199c0d8 | 76 | // In hand initially: 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 | 80 | baseFen.substr(0, 35) + "3CK3 " + |
5199c0d8 | 81 | "w 0 " + baseFen.substr(48, 2) + " - 11222" |
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 BA |
104 | [V.NINJA]: reserve[0], |
105 | [V.SAMURAI]: reserve[1], | |
106 | [V.MONK]: reserve[2], | |
107 | [V.HORSE]: reserve[3], | |
108 | [V.LANCE]: reserve[4] | |
269f9cfd BA |
109 | } |
110 | }; | |
111 | } | |
112 | ||
113 | getColor(i, j) { | |
d5af4af2 | 114 | if (i >= V.size.x) return 'w'; |
269f9cfd BA |
115 | return this.board[i][j].charAt(0); |
116 | } | |
117 | ||
118 | getPiece(i, j) { | |
119 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; | |
120 | return this.board[i][j].charAt(1); | |
121 | } | |
122 | ||
269f9cfd | 123 | static get RESERVE_PIECES() { |
5199c0d8 | 124 | return [V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE]; |
269f9cfd BA |
125 | } |
126 | ||
127 | getReserveMoves([x, y]) { | |
128 | // color == 'w', no drops for black. | |
129 | const p = V.RESERVE_PIECES[y]; | |
130 | if (this.reserve['w'][p] == 0) return []; | |
131 | let moves = []; | |
132 | for (let i of [4, 5, 6, 7]) { | |
133 | for (let j = 0; j < V.size.y; j++) { | |
134 | if (this.board[i][j] == V.EMPTY) { | |
135 | let mv = new Move({ | |
136 | appear: [ | |
137 | new PiPo({ | |
138 | x: i, | |
139 | y: j, | |
d5af4af2 | 140 | c: 'w', |
269f9cfd BA |
141 | p: p |
142 | }) | |
143 | ], | |
144 | vanish: [], | |
145 | start: { x: x, y: y }, | |
146 | end: { x: i, y: j } | |
147 | }); | |
148 | moves.push(mv); | |
149 | } | |
150 | } | |
151 | } | |
152 | return moves; | |
153 | } | |
154 | ||
155 | static get MapUnpromoted() { | |
156 | return { | |
157 | m: 'b', | |
158 | h: 'n', | |
159 | l: 'r', | |
160 | p: 'c' | |
161 | }; | |
162 | } | |
163 | ||
164 | getPotentialMovesFrom([x, y]) { | |
165 | if (x >= V.size.x) { | |
166 | // Reserves, outside of board: x == sizeX(+1) | |
167 | if (this.turn == 'b') return []; | |
168 | return this.getReserveMoves([x, y]); | |
169 | } | |
170 | // Standard moves | |
171 | const piece = this.getPiece(x, y); | |
172 | const sq = [x, y]; | |
d5af4af2 BA |
173 | if ([V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(piece)) |
174 | return super.getPotentialMovesFrom(sq); | |
269f9cfd | 175 | switch (piece) { |
131cad32 | 176 | case V.KING: return this.getPotentialKingMoves(sq); |
269f9cfd BA |
177 | case V.CAPTAIN: return this.getPotentialCaptainMoves(sq); |
178 | case V.NINJA: return this.getPotentialNinjaMoves(sq); | |
179 | case V.SAMURAI: return this.getPotentialSamuraiMoves(sq); | |
180 | } | |
181 | let moves = []; | |
182 | switch (piece) { | |
183 | // Unpromoted | |
184 | case V.PAWN: | |
185 | moves = super.getPotentialPawnMoves(sq); | |
d5af4af2 | 186 | break; |
269f9cfd BA |
187 | case V.MONK: |
188 | moves = this.getPotentialMonkMoves(sq); | |
189 | break; | |
190 | case V.HORSE: | |
191 | moves = this.getPotentialHorseMoves(sq); | |
192 | break; | |
193 | case V.LANCE: | |
194 | moves = this.getPotentialLanceMoves(sq); | |
195 | break; | |
196 | } | |
197 | const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]); | |
198 | const promotedForm = V.MapUnpromoted[piece]; | |
199 | moves.forEach(m => { | |
d5af4af2 | 200 | if (promotionZone.includes(m.end.x)) m.appear[0].p = promotedForm; |
269f9cfd BA |
201 | }); |
202 | return moves; | |
203 | } | |
204 | ||
d5af4af2 BA |
205 | getPotentialKingMoves([x, y]) { |
206 | if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]); | |
207 | // Clan doesn't castle: | |
208 | return super.getSlideNJumpMoves( | |
209 | [x, y], | |
210 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), | |
211 | "oneStep" | |
212 | ); | |
269f9cfd BA |
213 | } |
214 | ||
d5af4af2 | 215 | getPotentialCaptainMoves(sq) { |
269f9cfd BA |
216 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
217 | return super.getSlideNJumpMoves(sq, steps, "oneStep"); | |
218 | } | |
219 | ||
d5af4af2 BA |
220 | getPotentialNinjaMoves(sq) { |
221 | return ( | |
222 | super.getSlideNJumpMoves(sq, V.steps[V.BISHOP]) | |
223 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) | |
224 | ); | |
225 | } | |
226 | ||
227 | getPotentialSamuraiMoves(sq) { | |
269f9cfd BA |
228 | return ( |
229 | super.getSlideNJumpMoves(sq, V.steps[V.ROOK]) | |
230 | .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")) | |
231 | ); | |
232 | } | |
233 | ||
d5af4af2 BA |
234 | getPotentialMonkMoves(sq) { |
235 | return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"); | |
236 | } | |
237 | ||
269f9cfd | 238 | getPotentialHorseMoves(sq) { |
d5af4af2 | 239 | return super.getSlideNJumpMoves(sq, [ [-2, 1], [-2, -1] ], "oneStep"); |
269f9cfd BA |
240 | } |
241 | ||
242 | getPotentialLanceMoves(sq) { | |
d5af4af2 | 243 | return super.getSlideNJumpMoves(sq, [ [-1, 0] ]); |
269f9cfd BA |
244 | } |
245 | ||
246 | isAttacked(sq, color) { | |
247 | if (color == 'b') | |
248 | return (super.isAttacked(sq, 'b') || this.isAttackedByCaptain(sq, 'b')); | |
249 | // Attacked by white: | |
250 | return ( | |
251 | super.isAttackedByKing(sq, 'w') || | |
252 | this.isAttackedByCaptain(sq, 'w') || | |
d5af4af2 BA |
253 | this.isAttackedByNinja(sq, 'w') || |
254 | this.isAttackedBySamurai(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 ( | |
d5af4af2 | 267 | super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, "oneStep") |
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( |
d5af4af2 | 275 | sq, color, V.NINJA, V.steps[V.KNIGHT], "oneStep") |
269f9cfd BA |
276 | ); |
277 | } | |
278 | ||
279 | isAttackedBySamurai(sq, color) { | |
280 | return ( | |
d5af4af2 | 281 | super.isAttackedBySlideNJump(sq, color, V.SAMURAI, V.steps[V.ROOK]) || |
269f9cfd | 282 | super.isAttackedBySlideNJump( |
d5af4af2 | 283 | sq, color, V.SAMURAI, V.steps[V.KNIGHT], "oneStep") |
269f9cfd BA |
284 | ); |
285 | } | |
286 | ||
287 | isAttackedByMonk(sq, color) { | |
269f9cfd | 288 | return ( |
d5af4af2 BA |
289 | super.isAttackedBySlideNJump( |
290 | sq, color, V.MONK, V.steps[V.BISHOP], "oneStep") | |
269f9cfd BA |
291 | ); |
292 | } | |
293 | ||
294 | isAttackedByHorse(sq, color) { | |
295 | return ( | |
269f9cfd | 296 | super.isAttackedBySlideNJump( |
d5af4af2 | 297 | sq, color, V.HORSE, [ [2, 1], [2, -1] ], "oneStep") |
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 BA |
386 | j: 7, |
387 | s: 8, | |
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) { | |
408 | if (move.vanish.length > 0) return super.getNotation(move); | |
409 | // Drop: | |
410 | const piece = | |
411 | move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; | |
412 | return piece + "@" + V.CoordsToSquare(move.end); | |
413 | } | |
414 | ||
415 | }; |