Commit | Line | Data |
---|---|---|
6c00a6e5 BA |
1 | import { ChessRules, PiPo, Move } from "@/base_rules"; |
2 | import { ArrayFun } from "@/utils/array"; | |
3 | import { sample, shuffle } from "@/utils/alea"; | |
4 | ||
5 | export class DobutsuRules extends ChessRules { | |
6 | ||
4313762d BA |
7 | static get Options() { |
8 | return null; | |
9 | } | |
10 | ||
6c00a6e5 BA |
11 | static get HasFlags() { |
12 | return false; | |
13 | } | |
14 | ||
15 | static get HasEnpassant() { | |
16 | return false; | |
17 | } | |
18 | ||
19 | static get Monochrome() { | |
20 | return true; | |
21 | } | |
22 | ||
b27300c2 BA |
23 | get showFirstTurn() { |
24 | return true; | |
25 | } | |
26 | ||
6c00a6e5 BA |
27 | static IsGoodFen(fen) { |
28 | if (!ChessRules.IsGoodFen(fen)) return false; | |
29 | const fenParsed = V.ParseFen(fen); | |
30 | // 3) Check reserves | |
31 | if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{14,14}$/)) | |
32 | return false; | |
33 | return true; | |
34 | } | |
35 | ||
36 | static ParseFen(fen) { | |
37 | const fenParts = fen.split(" "); | |
38 | return Object.assign( | |
39 | ChessRules.ParseFen(fen), | |
40 | { reserve: fenParts[3] } | |
41 | ); | |
42 | } | |
43 | ||
44 | static get ELEPHANT() { | |
45 | return "e"; | |
46 | } | |
47 | static get GIRAFFE() { | |
48 | return "g"; | |
49 | } | |
50 | static get HEN() { | |
51 | return "h"; | |
52 | } | |
53 | ||
54 | static get PIECES() { | |
55 | return [ | |
56 | ChessRules.PAWN, | |
57 | ChessRules.KING, | |
58 | V.ELEPHANT, | |
59 | V.GIRAFFE, | |
60 | V.HEN | |
61 | ]; | |
62 | } | |
63 | ||
64 | getPpath(b, color, score, orientation) { | |
65 | // 'i' for "inversed": | |
66 | const suffix = (b[0] == orientation ? "" : "i"); | |
67 | return "Dobutsu/" + b + suffix; | |
68 | } | |
69 | ||
70 | getPPpath(m, orientation) { | |
71 | return ( | |
72 | this.getPpath( | |
73 | m.appear[0].c + m.appear[0].p, | |
74 | null, | |
75 | null, | |
76 | orientation | |
77 | ) | |
78 | ); | |
79 | } | |
80 | ||
81 | static GenRandInitFen() { | |
82 | return "gke/1p1/1P1/EKG w 0 00000000"; | |
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 | let counts = new Array(6); | |
95 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
96 | counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]]; | |
97 | counts[3 + i] = this.reserve["b"][V.RESERVE_PIECES[i]]; | |
98 | } | |
99 | return counts.join(""); | |
100 | } | |
101 | ||
102 | setOtherVariables(fen) { | |
103 | super.setOtherVariables(fen); | |
104 | // Also init reserves (used by the interface to show landable pieces) | |
105 | const reserve = | |
106 | V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); | |
107 | this.reserve = { | |
108 | w: { | |
109 | [V.PAWN]: reserve[0], | |
110 | [V.ELEPHANT]: reserve[1], | |
111 | [V.GIRAFFE]: reserve[2] | |
112 | }, | |
113 | b: { | |
114 | [V.PAWN]: reserve[3], | |
115 | [V.ELEPHANT]: reserve[4], | |
116 | [V.GIRAFFE]: reserve[5] | |
117 | } | |
118 | }; | |
119 | } | |
120 | ||
121 | // Goal is to capture the king, easier to not track kings | |
122 | scanKings() {} | |
123 | ||
124 | getColor(i, j) { | |
125 | if (i >= V.size.x) return i == V.size.x ? "w" : "b"; | |
126 | return this.board[i][j].charAt(0); | |
127 | } | |
128 | ||
129 | getPiece(i, j) { | |
130 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; | |
131 | return this.board[i][j].charAt(1); | |
132 | } | |
133 | ||
134 | static get size() { | |
135 | return { x: 4, y: 3}; | |
136 | } | |
137 | ||
138 | getReservePpath(index, color, orientation) { | |
139 | return ( | |
140 | "Dobutsu/" + color + V.RESERVE_PIECES[index] + | |
141 | (color != orientation ? 'i' : '') | |
142 | ); | |
143 | } | |
144 | ||
145 | // Ordering on reserve pieces | |
146 | static get RESERVE_PIECES() { | |
147 | return ( | |
148 | // No king, since the goal is to capture it | |
149 | [V.PAWN, V.ELEPHANT, V.GIRAFFE] | |
150 | ); | |
151 | } | |
152 | ||
153 | getReserveMoves([x, y]) { | |
154 | const color = this.turn; | |
155 | const p = V.RESERVE_PIECES[y]; | |
156 | if (this.reserve[color][p] == 0) return []; | |
157 | let moves = []; | |
158 | for (let i = 0; i < V.size.x; i++) { | |
159 | for (let j = 0; j < V.size.y; j++) { | |
160 | if (this.board[i][j] == V.EMPTY) { | |
161 | let mv = new Move({ | |
162 | appear: [ | |
163 | new PiPo({ | |
164 | x: i, | |
165 | y: j, | |
166 | c: color, | |
167 | p: p | |
168 | }) | |
169 | ], | |
170 | vanish: [], | |
171 | start: { x: x, y: y }, //a bit artificial... | |
172 | end: { x: i, y: j } | |
173 | }); | |
174 | moves.push(mv); | |
175 | } | |
176 | } | |
177 | } | |
178 | return moves; | |
179 | } | |
180 | ||
181 | getPotentialMovesFrom(sq) { | |
182 | if (sq[0] >= V.size.x) { | |
183 | // Reserves, outside of board: x == sizeX(+1) | |
184 | return this.getReserveMoves(sq); | |
185 | } | |
186 | switch (this.getPiece(sq[0], sq[1])) { | |
187 | case V.PAWN: return this.getPotentialPawnMoves(sq); | |
ecb8f91b | 188 | case V.HEN: return this.getPotentialHenMoves(sq); |
6c00a6e5 BA |
189 | case V.ELEPHANT: return this.getPotentialElephantMoves(sq); |
190 | case V.GIRAFFE: return this.getPotentialGiraffeMoves(sq); | |
191 | case V.KING: return super.getPotentialKingMoves(sq); | |
192 | } | |
193 | return []; //never reached | |
194 | } | |
195 | ||
196 | getPotentialPawnMoves([x, y]) { | |
197 | const c = this.turn; | |
198 | const beforeLastRank = (c == 'w' ? 1 : 2); | |
199 | const forward = (c == 'w' ? -1 : 1); | |
200 | if (!V.OnBoard(x + forward, y)) return []; //stuck pawn | |
201 | if ( | |
202 | this.board[x + forward][y] == V.EMPTY || | |
203 | this.getColor(x + forward, y) != c | |
204 | ) { | |
205 | const tr = (x == beforeLastRank ? { p: V.HEN, c: c } : null); | |
206 | return [super.getBasicMove([x, y], [x + forward, y], tr)]; | |
207 | } | |
208 | } | |
209 | ||
e7cb433f | 210 | getPotentialHenMoves(sq) { |
ecb8f91b BA |
211 | const c = this.turn; |
212 | const forward = (c == 'w' ? -1 : 1); | |
213 | const steps = V.steps[V.ROOK].concat([[forward, 1], [forward, -1]]); | |
4313762d | 214 | return super.getSlideNJumpMoves(sq, steps, 1); |
ecb8f91b BA |
215 | } |
216 | ||
6c00a6e5 | 217 | getPotentialElephantMoves(sq) { |
4313762d | 218 | return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1); |
6c00a6e5 BA |
219 | } |
220 | ||
221 | getPotentialGiraffeMoves(sq) { | |
4313762d | 222 | return super.getSlideNJumpMoves(sq, V.steps[V.ROOK], 1); |
6c00a6e5 BA |
223 | } |
224 | ||
225 | getAllValidMoves() { | |
226 | let moves = super.getAllPotentialMoves(); | |
227 | const color = this.turn; | |
228 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
229 | moves = moves.concat( | |
230 | this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) | |
231 | ); | |
232 | } | |
233 | return moves; | |
234 | } | |
235 | ||
236 | // Goal is to capture the king: | |
237 | isAttacked() { | |
238 | return false; | |
239 | } | |
240 | filterValid(moves) { | |
241 | return moves; | |
242 | } | |
243 | getCheckSquares() { | |
244 | return []; | |
245 | } | |
246 | ||
247 | static MayDecode(piece) { | |
248 | if (piece == V.HEN) return V.PAWN; | |
249 | return piece; | |
250 | } | |
251 | ||
252 | postPlay(move) { | |
253 | const color = move.appear[0].c; | |
254 | if (move.vanish.length == 0) | |
255 | // Drop unpromoted piece: | |
256 | this.reserve[color][move.appear[0].p]--; | |
257 | else if (move.vanish.length == 2) | |
258 | // May capture a promoted piece: | |
259 | this.reserve[color][V.MayDecode(move.vanish[1].p)]++; | |
260 | } | |
261 | ||
262 | postUndo(move) { | |
263 | const color = this.turn; | |
264 | if (move.vanish.length == 0) | |
265 | this.reserve[color][move.appear[0].p]++; | |
266 | else if (move.vanish.length == 2) | |
267 | this.reserve[color][V.MayDecode(move.vanish[1].p)]--; | |
268 | } | |
269 | ||
270 | getCurrentScore() { | |
271 | const c = this.turn; | |
272 | if (this.board.every(row => row.every(cell => cell != c + 'k'))) | |
273 | return (c == 'w' ? "0-1" : "1-0"); | |
274 | const oppCol = V.GetOppCol(c); | |
275 | const oppLastRank = (c == 'w' ? 3 : 0); | |
276 | for (let j=0; j < V.size.y; j++) { | |
277 | if (this.board[oppLastRank][j] == oppCol + 'k') | |
278 | return (oppCol == 'w' ? "1-0" : "0-1"); | |
279 | } | |
280 | return "*"; | |
281 | } | |
282 | ||
283 | static get SEARCH_DEPTH() { | |
284 | return 4; | |
285 | } | |
286 | ||
287 | static get VALUES() { | |
288 | // NOTE: very arbitrary | |
289 | return { | |
290 | p: 1, | |
291 | h: 4, | |
292 | g: 3, | |
293 | e: 2, | |
294 | k: 1000 | |
295 | } | |
296 | } | |
297 | ||
298 | evalPosition() { | |
299 | let evaluation = super.evalPosition(); | |
300 | // Add reserves: | |
301 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { | |
302 | const p = V.RESERVE_PIECES[i]; | |
303 | evaluation += this.reserve["w"][p] * V.VALUES[p]; | |
304 | evaluation -= this.reserve["b"][p] * V.VALUES[p]; | |
305 | } | |
306 | return evaluation; | |
307 | } | |
308 | ||
309 | getNotation(move) { | |
310 | const finalSquare = V.CoordsToSquare(move.end); | |
311 | if (move.vanish.length == 0) { | |
312 | // Rebirth: | |
313 | const piece = move.appear[0].p.toUpperCase(); | |
314 | return (piece != 'P' ? piece : "") + "@" + finalSquare; | |
315 | } | |
316 | const piece = move.vanish[0].p.toUpperCase(); | |
317 | return ( | |
318 | (piece != 'P' || move.vanish.length == 2 ? piece : "") + | |
319 | (move.vanish.length == 2 ? "x" : "") + | |
320 | finalSquare + | |
321 | ( | |
322 | move.appear[0].p != move.vanish[0].p | |
323 | ? "=" + move.appear[0].p.toUpperCase() | |
324 | : "" | |
325 | ) | |
326 | ); | |
327 | } | |
328 | ||
329 | }; |