Several small improvements + integrate options + first working draft of Cwda
[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 Options() {
8 return null;
9 }
10
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
23 get showFirstTurn() {
24 return true;
25 }
26
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);
188 case V.HEN: return this.getPotentialHenMoves(sq);
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
210 getPotentialHenMoves(sq) {
211 const c = this.turn;
212 const forward = (c == 'w' ? -1 : 1);
213 const steps = V.steps[V.ROOK].concat([[forward, 1], [forward, -1]]);
214 return super.getSlideNJumpMoves(sq, steps, 1);
215 }
216
217 getPotentialElephantMoves(sq) {
218 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1);
219 }
220
221 getPotentialGiraffeMoves(sq) {
222 return super.getSlideNJumpMoves(sq, V.steps[V.ROOK], 1);
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 };