Add Brotherhood, Maharajah (special version) + Dobutsu variants
[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.ELEPHANT: return this.getPotentialElephantMoves(sq);
181 case V.GIRAFFE: return this.getPotentialGiraffeMoves(sq);
182 case V.KING: return super.getPotentialKingMoves(sq);
183 }
184 return []; //never reached
185 }
186
187 getPotentialPawnMoves([x, y]) {
188 const c = this.turn;
189 const beforeLastRank = (c == 'w' ? 1 : 2);
190 const forward = (c == 'w' ? -1 : 1);
191 if (!V.OnBoard(x + forward, y)) return []; //stuck pawn
192 if (
193 this.board[x + forward][y] == V.EMPTY ||
194 this.getColor(x + forward, y) != c
195 ) {
196 const tr = (x == beforeLastRank ? { p: V.HEN, c: c } : null);
197 return [super.getBasicMove([x, y], [x + forward, y], tr)];
198 }
199 }
200
201 getPotentialElephantMoves(sq) {
202 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep");
203 }
204
205 getPotentialGiraffeMoves(sq) {
206 return super.getSlideNJumpMoves(sq, V.steps[V.ROOK], "oneStep");
207 }
208
209 getAllValidMoves() {
210 let moves = super.getAllPotentialMoves();
211 const color = this.turn;
212 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
213 moves = moves.concat(
214 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
215 );
216 }
217 return moves;
218 }
219
220 // Goal is to capture the king:
221 isAttacked() {
222 return false;
223 }
224 filterValid(moves) {
225 return moves;
226 }
227 getCheckSquares() {
228 return [];
229 }
230
231 static MayDecode(piece) {
232 if (piece == V.HEN) return V.PAWN;
233 return piece;
234 }
235
236 postPlay(move) {
237 const color = move.appear[0].c;
238 if (move.vanish.length == 0)
239 // Drop unpromoted piece:
240 this.reserve[color][move.appear[0].p]--;
241 else if (move.vanish.length == 2)
242 // May capture a promoted piece:
243 this.reserve[color][V.MayDecode(move.vanish[1].p)]++;
244 }
245
246 postUndo(move) {
247 const color = this.turn;
248 if (move.vanish.length == 0)
249 this.reserve[color][move.appear[0].p]++;
250 else if (move.vanish.length == 2)
251 this.reserve[color][V.MayDecode(move.vanish[1].p)]--;
252 }
253
254 getCurrentScore() {
255 const c = this.turn;
256 if (this.board.every(row => row.every(cell => cell != c + 'k')))
257 return (c == 'w' ? "0-1" : "1-0");
258 const oppCol = V.GetOppCol(c);
259 const oppLastRank = (c == 'w' ? 3 : 0);
260 for (let j=0; j < V.size.y; j++) {
261 if (this.board[oppLastRank][j] == oppCol + 'k')
262 return (oppCol == 'w' ? "1-0" : "0-1");
263 }
264 return "*";
265 }
266
267 static get SEARCH_DEPTH() {
268 return 4;
269 }
270
271 static get VALUES() {
272 // NOTE: very arbitrary
273 return {
274 p: 1,
275 h: 4,
276 g: 3,
277 e: 2,
278 k: 1000
279 }
280 }
281
282 evalPosition() {
283 let evaluation = super.evalPosition();
284 // Add reserves:
285 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
286 const p = V.RESERVE_PIECES[i];
287 evaluation += this.reserve["w"][p] * V.VALUES[p];
288 evaluation -= this.reserve["b"][p] * V.VALUES[p];
289 }
290 return evaluation;
291 }
292
293 getNotation(move) {
294 const finalSquare = V.CoordsToSquare(move.end);
295 if (move.vanish.length == 0) {
296 // Rebirth:
297 const piece = move.appear[0].p.toUpperCase();
298 return (piece != 'P' ? piece : "") + "@" + finalSquare;
299 }
300 const piece = move.vanish[0].p.toUpperCase();
301 return (
302 (piece != 'P' || move.vanish.length == 2 ? piece : "") +
303 (move.vanish.length == 2 ? "x" : "") +
304 finalSquare +
305 (
306 move.appear[0].p != move.vanish[0].p
307 ? "=" + move.appear[0].p.toUpperCase()
308 : ""
309 )
310 );
311 }
312
313 };