60d0017db15448334b2ab02788be700b73af2acf
[vchess.git] / client / src / variants / Dobutsu.js
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);
180 case V.HEN: return this.getPotentialHenMoves(sq);
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
202 getPotentialHenMoves(sq) {
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
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 };