Pandemonium 1 & 2, Stealthbomb 1 & 2
[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 get showFirstTurn() {
20 return true;
21 }
22
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);
184 case V.HEN: return this.getPotentialHenMoves(sq);
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
206 getPotentialHenMoves(sq) {
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
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 };