Draft code reorganisation (+ fix Alice rules + stateless VariantRules object)
[vchess.git] / public / javascripts / variants / Crazyhouse.js
1 class CrazyhouseRules extends ChessRules
2 {
3 static IsGoodFen(fen)
4 {
5 if (!ChessRules.IsGoodFen(fen))
6 return false;
7 const fenParsed = V.ParseFen(fen);
8 // 5) Check reserves
9 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
10 return false;
11 // 6) Check promoted array
12 if (!fenParsed.promoted)
13 return false;
14 if (fenParsed.promoted == "-")
15 return true; //no promoted piece on board
16 const squares = fenParsed.promoted.split(",");
17 for (let square of squares)
18 {
19 const c = V.SquareToCoords(square);
20 if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
21 return false;
22 }
23 return true;
24 }
25
26 static ParseFen(fen)
27 {
28 const fenParts = fen.split(" ");
29 return Object.assign(
30 ChessRules.ParseFen(fen),
31 {
32 reserve: fenParts[5],
33 promoted: fenParts[6],
34 }
35 );
36 }
37
38 static GenRandInitFen()
39 {
40 return ChessRules.GenRandInitFen() + " 0000000000 -";
41 }
42
43 getFen()
44 {
45 return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
46 }
47
48 getReserveFen()
49 {
50 let counts = new Array(10);
51 for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
52 {
53 counts[i] = this.reserve["w"][V.PIECES[i]];
54 counts[5+i] = this.reserve["b"][V.PIECES[i]];
55 }
56 return counts.join("");
57 }
58
59 getPromotedFen()
60 {
61 let res = "";
62 for (let i=0; i<V.size.x; i++)
63 {
64 for (let j=0; j<V.size.y; j++)
65 {
66 if (this.promoted[i][j])
67 res += V.CoordsToSquare({x:i,y:j});
68 }
69 }
70 if (res.length > 0)
71 res = res.slice(0,-1); //remove last comma
72 else
73 res = "-";
74 return res;
75 }
76
77 setOtherVariables(fen)
78 {
79 super.setOtherVariables(fen);
80 const fenParsed = V.ParseFen(fen);
81 // Also init reserves (used by the interface to show landable pieces)
82 this.reserve =
83 {
84 "w":
85 {
86 [V.PAWN]: parseInt(fenParsed.reserve[0]),
87 [V.ROOK]: parseInt(fenParsed.reserve[1]),
88 [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
89 [V.BISHOP]: parseInt(fenParsed.reserve[3]),
90 [V.QUEEN]: parseInt(fenParsed.reserve[4]),
91 },
92 "b":
93 {
94 [V.PAWN]: parseInt(fenParsed.reserve[5]),
95 [V.ROOK]: parseInt(fenParsed.reserve[6]),
96 [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
97 [V.BISHOP]: parseInt(fenParsed.reserve[8]),
98 [V.QUEEN]: parseInt(fenParsed.reserve[9]),
99 }
100 };
101 this.promoted = doubleArray(V.size.x, V.size.y, false);
102 if (fenParsed.promoted != "-")
103 {
104 for (let square of fenParsed.promoted.split(","))
105 {
106 const [x,y] = V.SquareToCoords(square);
107 promoted[x][y] = true;
108 }
109 }
110 }
111
112 getColor(i,j)
113 {
114 if (i >= V.size.x)
115 return (i==V.size.x ? "w" : "b");
116 return this.board[i][j].charAt(0);
117 }
118
119 getPiece(i,j)
120 {
121 if (i >= V.size.x)
122 return V.RESERVE_PIECES[j];
123 return this.board[i][j].charAt(1);
124 }
125
126 // Used by the interface:
127 getReservePpath(color, index)
128 {
129 return color + V.RESERVE_PIECES[index];
130 }
131
132 // Ordering on reserve pieces
133 static get RESERVE_PIECES()
134 {
135 return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
136 }
137
138 getReserveMoves([x,y])
139 {
140 const color = this.turn;
141 const p = V.RESERVE_PIECES[y];
142 if (this.reserve[color][p] == 0)
143 return [];
144 let moves = [];
145 const pawnShift = (p==V.PAWN ? 1 : 0);
146 for (let i=pawnShift; i<V.size.x-pawnShift; i++)
147 {
148 for (let j=0; j<V.size.y; j++)
149 {
150 if (this.board[i][j] == V.EMPTY)
151 {
152 let mv = new Move({
153 appear: [
154 new PiPo({
155 x: i,
156 y: j,
157 c: color,
158 p: p
159 })
160 ],
161 vanish: [],
162 start: {x:x, y:y}, //a bit artificial...
163 end: {x:i, y:j}
164 });
165 moves.push(mv);
166 }
167 }
168 }
169 return moves;
170 }
171
172 getPotentialMovesFrom([x,y])
173 {
174 if (x >= V.size.x)
175 {
176 // Reserves, outside of board: x == sizeX(+1)
177 return this.getReserveMoves([x,y]);
178 }
179 // Standard moves
180 return super.getPotentialMovesFrom([x,y]);
181 }
182
183 getAllValidMoves()
184 {
185 let moves = super.getAllValidMoves();
186 const color = this.turn;
187 for (let i=0; i<V.RESERVE_PIECES.length; i++)
188 moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
189 return this.filterValid(moves);
190 }
191
192 atLeastOneMove()
193 {
194 if (!super.atLeastOneMove())
195 {
196 const color = this.turn;
197 // Search one reserve move
198 for (let i=0; i<V.RESERVE_PIECES.length; i++)
199 {
200 let moves = this.filterValid(
201 this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
202 if (moves.length > 0)
203 return true;
204 }
205 return false;
206 }
207 return true;
208 }
209
210 updateVariables(move)
211 {
212 super.updateVariables(move);
213 if (move.vanish.length == 2 && move.appear.length == 2)
214 return; //skip castle
215 const color = move.appear[0].c;
216 if (move.vanish.length == 0)
217 {
218 this.reserve[color][move.appear[0].p]--;
219 return;
220 }
221 move.movePromoted = this.promoted[move.start.x][move.start.y];
222 move.capturePromoted = this.promoted[move.end.x][move.end.y]
223 this.promoted[move.start.x][move.start.y] = false;
224 this.promoted[move.end.x][move.end.y] = move.movePromoted
225 || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
226 if (move.capturePromoted)
227 this.reserve[color][V.PAWN]++;
228 else if (move.vanish.length == 2)
229 this.reserve[color][move.vanish[1].p]++;
230 }
231
232 unupdateVariables(move)
233 {
234 super.unupdateVariables(move);
235 if (move.vanish.length == 2 && move.appear.length == 2)
236 return;
237 const color = this.turn;
238 if (move.vanish.length == 0)
239 {
240 this.reserve[color][move.appear[0].p]++;
241 return;
242 }
243 if (move.movePromoted)
244 this.promoted[move.start.x][move.start.y] = true;
245 this.promoted[move.end.x][move.end.y] = move.capturePromoted;
246 if (move.capturePromoted)
247 this.reserve[color][V.PAWN]--;
248 else if (move.vanish.length == 2)
249 this.reserve[color][move.vanish[1].p]--;
250 }
251
252 static get SEARCH_DEPTH() { return 2; } //high branching factor
253
254 evalPosition()
255 {
256 let evaluation = super.evalPosition();
257 // Add reserves:
258 for (let i=0; i<V.RESERVE_PIECES.length; i++)
259 {
260 const p = V.RESERVE_PIECES[i];
261 evaluation += this.reserve["w"][p] * V.VALUES[p];
262 evaluation -= this.reserve["b"][p] * V.VALUES[p];
263 }
264 return evaluation;
265 }
266
267 getNotation(move)
268 {
269 if (move.vanish.length > 0)
270 return super.getNotation(move);
271 // Rebirth:
272 const piece =
273 (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
274 return piece + "@" + V.CoordsToSquare(move.end);
275 }
276
277 getLongNotation(move)
278 {
279 if (move.vanish.length > 0)
280 return super.getLongNotation(move);
281 return "@" + V.CoordsToSquare(move.end);
282 }
283 }
284
285 const VariantRules = CrazyhouseRules;