More balanced Shinobi according to Couch Tomato + Fables tests
[vchess.git] / client / src / variants / Crazyhouse.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export class CrazyhouseRules extends ChessRules {
5
6 static get PawnSpecs() {
7 return Object.assign(
8 {},
9 ChessRules.PawnSpecs,
10 // Change names to know that this goes back to pawn after capture:
11 { promotions: ['u', 'o', 'c', 't'] }
12 );
13 }
14
15 static get PIECES() {
16 return ChessRules.PIECES.concat(['u', 'o', 'c', 't']);
17 }
18
19 getPpath(b) {
20 const prefix = (ChessRules.PIECES.includes(b[1]) ? "" : "Crazyhouse/");
21 return prefix + b;
22 }
23
24 static IsGoodFen(fen) {
25 if (!ChessRules.IsGoodFen(fen)) return false;
26 const fenParsed = V.ParseFen(fen);
27 // 5) Check reserves
28 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
29 return false;
30 return true;
31 }
32
33 static ParseFen(fen) {
34 const fenParts = fen.split(" ");
35 return Object.assign(
36 ChessRules.ParseFen(fen),
37 { reserve: fenParts[5] }
38 );
39 }
40
41 static GenRandInitFen(randomness) {
42 return ChessRules.GenRandInitFen(randomness) + " 0000000000 -";
43 }
44
45 getFen() {
46 return super.getFen() + " " + this.getReserveFen();
47 }
48
49 getFenForRepeat() {
50 return super.getFenForRepeat() + "_" + this.getReserveFen();
51 }
52
53 getReserveFen() {
54 let counts = new Array(10);
55 for (
56 let i = 0;
57 i < V.PIECES.length - 1;
58 i++ //-1: no king reserve
59 ) {
60 counts[i] = this.reserve["w"][V.PIECES[i]];
61 counts[5 + i] = this.reserve["b"][V.PIECES[i]];
62 }
63 return counts.join("");
64 }
65
66 setOtherVariables(fen) {
67 super.setOtherVariables(fen);
68 const fenParsed = V.ParseFen(fen);
69 // Also init reserves (used by the interface to show landable pieces)
70 const reserve = fenParsed.reserve.split("").map(x => parseInt(x, 10));
71 this.reserve = {
72 w: {
73 [V.PAWN]: reserve[0],
74 [V.ROOK]: reserve[1],
75 [V.KNIGHT]: reserve[2],
76 [V.BISHOP]: reserve[3],
77 [V.QUEEN]: reserve[4]
78 },
79 b: {
80 [V.PAWN]: reserve[5],
81 [V.ROOK]: reserve[6],
82 [V.KNIGHT]: reserve[7],
83 [V.BISHOP]: reserve[8],
84 [V.QUEEN]: reserve[9]
85 }
86 };
87 }
88
89 getColor(i, j) {
90 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
91 return this.board[i][j].charAt(0);
92 }
93
94 // Pieces types after pawn promotion
95 static get PromotionMap() {
96 return {
97 u: 'r',
98 o: 'n',
99 c: 'b',
100 t: 'q'
101 };
102 }
103
104 getPiece(i, j) {
105 if (i >= V.size.x) return V.RESERVE_PIECES[j];
106 const p = this.board[i][j].charAt(1);
107 if (ChessRules.PIECES.includes(p)) return p;
108 // Pawn promotion:
109 return V.PromotionMap[p];
110 }
111
112 // Used by the interface:
113 getReservePpath(index, color) {
114 return color + V.RESERVE_PIECES[index];
115 }
116 // // Version if some day I have pieces with numbers printed on it:
117 // getReservePpath(index, color) {
118 // return (
119 // "Crazyhouse/" +
120 // color + V.RESERVE_PIECES[index] +
121 // "_" + this.vr.reserve[playingColor][V.RESERVE_PIECES[i]]
122 // );
123 // }
124
125 // Ordering on reserve pieces
126 static get RESERVE_PIECES() {
127 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
128 }
129
130 getReserveMoves([x, y]) {
131 const color = this.turn;
132 const p = V.RESERVE_PIECES[y];
133 if (this.reserve[color][p] == 0) return [];
134 let moves = [];
135 const pawnShift = p == V.PAWN ? 1 : 0;
136 for (let i = pawnShift; i < V.size.x - pawnShift; i++) {
137 for (let j = 0; j < V.size.y; j++) {
138 if (this.board[i][j] == V.EMPTY) {
139 let mv = new Move({
140 appear: [
141 new PiPo({
142 x: i,
143 y: j,
144 c: color,
145 p: p
146 })
147 ],
148 vanish: [],
149 start: { x: x, y: y }, //a bit artificial...
150 end: { x: i, y: j }
151 });
152 moves.push(mv);
153 }
154 }
155 }
156 return moves;
157 }
158
159 getPotentialMovesFrom([x, y]) {
160 if (x >= V.size.x)
161 // Reserves, outside of board: x == sizeX(+1)
162 return this.getReserveMoves([x, y]);
163 // Standard moves
164 return super.getPotentialMovesFrom([x, y]);
165 }
166
167 getAllValidMoves() {
168 let moves = super.getAllPotentialMoves();
169 const color = this.turn;
170 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
171 moves = moves.concat(
172 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
173 );
174 }
175 return this.filterValid(moves);
176 }
177
178 atLeastOneMove() {
179 if (super.atLeastOneMove()) return true;
180 // Search one reserve move
181 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
182 const moves = this.filterValid(
183 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
184 );
185 if (moves.length > 0) return true;
186 }
187 return false;
188 }
189
190 postPlay(move) {
191 super.postPlay(move);
192 // Skip castle:
193 if (move.vanish.length == 2 && move.appear.length == 2) return;
194 const color = move.appear[0].c;
195 if (move.vanish.length == 0) {
196 this.reserve[color][move.appear[0].p]--;
197 return;
198 }
199 if (move.vanish.length == 2) {
200 if (V.PawnSpecs.promotions.includes(move.vanish[1].p))
201 this.reserve[color][V.PAWN]++;
202 else this.reserve[color][move.vanish[1].p]++;
203 }
204 }
205
206 postUndo(move) {
207 super.postUndo(move);
208 if (move.vanish.length == 2 && move.appear.length == 2) return;
209 const color = this.turn;
210 if (move.vanish.length == 0) {
211 this.reserve[color][move.appear[0].p]++;
212 return;
213 }
214 if (move.vanish.length == 2) {
215 if (V.PawnSpecs.promotions.includes(move.vanish[1].p))
216 this.reserve[color][V.PAWN]--;
217 else this.reserve[color][move.vanish[1].p]--;
218 }
219 }
220
221 static get SEARCH_DEPTH() {
222 return 2;
223 }
224
225 evalPosition() {
226 let evaluation = super.evalPosition();
227 // Add reserves:
228 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
229 const p = V.RESERVE_PIECES[i];
230 evaluation += this.reserve["w"][p] * V.VALUES[p];
231 evaluation -= this.reserve["b"][p] * V.VALUES[p];
232 }
233 return evaluation;
234 }
235
236 getNotation(move) {
237 if (move.vanish.length > 0) return super.getNotation(move);
238 // Rebirth:
239 let piece = move.appear[0].p;
240 if (ChessRules.PIECES.includes(piece)) {
241 if (move.appear[0].p != V.PAWN) piece = move.appear[0].p.toUpperCase();
242 }
243 else piece = V.PromotionMap[piece].toUpperCase();
244 return piece + "@" + V.CoordsToSquare(move.end);
245 }
246
247 };