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