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