82895f351d345410c70988f49291da6b5a1f86f4
[vchess.git] / client / src / variants / Recycle.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export const VariantRules = class RecycleRules extends ChessRules {
5 static IsGoodFen(fen) {
6 if (!ChessRules.IsGoodFen(fen)) 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 return true;
12 }
13
14 static ParseFen(fen) {
15 const fenParts = fen.split(" ");
16 return Object.assign(ChessRules.ParseFen(fen), {
17 reserve: fenParts[5],
18 });
19 }
20
21 getEpSquare(moveOrSquare) {
22 if (typeof moveOrSquare !== "object" || moveOrSquare.vanish.length > 0)
23 return super.getEpSquare(moveOrSquare);
24 // Landing move: no en-passant
25 return undefined;
26 }
27
28 static GenRandInitFen(randomness) {
29 return ChessRules.GenRandInitFen(randomness) + " 0000000000";
30 }
31
32 getFen() {
33 return (
34 super.getFen() + " " + this.getReserveFen()
35 );
36 }
37
38 getFenForRepeat() {
39 return (
40 this.getBaseFen() + "_" +
41 this.getTurnFen() + "_" +
42 this.getFlagsFen() + "_" +
43 this.getEnpassantFen() + "_" +
44 this.getReserveFen()
45 );
46 }
47
48 getReserveFen() {
49 let counts = new Array(10);
50 for (
51 let i = 0;
52 i < V.PIECES.length - 1;
53 i++ //-1: no king reserve
54 ) {
55 counts[i] = this.reserve["w"][V.PIECES[i]];
56 counts[5 + i] = this.reserve["b"][V.PIECES[i]];
57 }
58 return counts.join("");
59 }
60
61 setOtherVariables(fen) {
62 super.setOtherVariables(fen);
63 const fenParsed = V.ParseFen(fen);
64 // Also init reserves (used by the interface to show landable pieces)
65 this.reserve = {
66 w: {
67 [V.PAWN]: parseInt(fenParsed.reserve[0]),
68 [V.ROOK]: parseInt(fenParsed.reserve[1]),
69 [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
70 [V.BISHOP]: parseInt(fenParsed.reserve[3]),
71 [V.QUEEN]: parseInt(fenParsed.reserve[4])
72 },
73 b: {
74 [V.PAWN]: parseInt(fenParsed.reserve[5]),
75 [V.ROOK]: parseInt(fenParsed.reserve[6]),
76 [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
77 [V.BISHOP]: parseInt(fenParsed.reserve[8]),
78 [V.QUEEN]: parseInt(fenParsed.reserve[9])
79 }
80 };
81 }
82
83 getColor(i, j) {
84 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
85 return this.board[i][j].charAt(0);
86 }
87
88 getPiece(i, j) {
89 if (i >= V.size.x) return V.RESERVE_PIECES[j];
90 return this.board[i][j].charAt(1);
91 }
92
93 // Used by the interface:
94 getReservePpath(index, color) {
95 return color + V.RESERVE_PIECES[index];
96 }
97
98 // Ordering on reserve pieces
99 static get RESERVE_PIECES() {
100 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
101 }
102
103 getReserveMoves([x, y]) {
104 const color = this.turn;
105 const p = V.RESERVE_PIECES[y];
106 if (this.reserve[color][p] == 0) return [];
107 let moves = [];
108 const pawnShift = p == V.PAWN ? 1 : 0;
109 for (let i = pawnShift; i < V.size.x - pawnShift; i++) {
110 for (let j = 0; j < V.size.y; j++) {
111 if (this.board[i][j] == V.EMPTY) {
112 let mv = new Move({
113 appear: [
114 new PiPo({
115 x: i,
116 y: j,
117 c: color,
118 p: p
119 })
120 ],
121 vanish: [],
122 start: { x: x, y: y }, //a bit artificial...
123 end: { x: i, y: j }
124 });
125 moves.push(mv);
126 }
127 }
128 }
129 return moves;
130 }
131
132 getPotentialMovesFrom([x, y]) {
133 if (x >= V.size.x) {
134 // Reserves, outside of board: x == sizeX(+1)
135 return this.getReserveMoves([x, y]);
136 }
137 // Standard moves
138 return super.getPotentialMovesFrom([x, y]);
139 }
140
141 getPotentialPawnMoves([x, y]) {
142 const color = this.turn;
143 let moves = [];
144 const [sizeX, sizeY] = [V.size.x, V.size.y];
145 const shiftX = color == "w" ? -1 : 1;
146 const startRank = color == "w" ? sizeX - 2 : 1;
147 const lastRank = color == "w" ? 0 : sizeX - 1;
148
149 // One square forward
150 if (this.board[x + shiftX][y] == V.EMPTY) {
151 moves.push(
152 this.getBasicMove([x, y], [x + shiftX, y])
153 );
154 // Next condition because pawns on 1st rank can generally jump
155 if (
156 x == startRank &&
157 this.board[x + 2 * shiftX][y] == V.EMPTY
158 ) {
159 // Two squares jump
160 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
161 }
162 }
163 // Captures
164 for (let shiftY of [-1, 1]) {
165 if (
166 y + shiftY >= 0 &&
167 y + shiftY < sizeY &&
168 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
169 this.canTake([x, y], [x + shiftX, y + shiftY])
170 ) {
171 moves.push(
172 this.getBasicMove([x, y], [x + shiftX, y + shiftY])
173 );
174 }
175 }
176
177 // En passant
178 const Lep = this.epSquares.length;
179 const epSquare = this.epSquares[Lep - 1]; //always at least one element
180 if (
181 !!epSquare &&
182 epSquare.x == x + shiftX &&
183 Math.abs(epSquare.y - y) == 1
184 ) {
185 let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
186 enpassantMove.vanish.push({
187 x: x,
188 y: epSquare.y,
189 p: "p",
190 c: this.getColor(x, epSquare.y)
191 });
192 moves.push(enpassantMove);
193 }
194
195 // Post-processing: remove falling pawns
196 if (x + shiftX == lastRank) {
197 moves.forEach(m => {
198 m.appear.pop();
199 });
200 }
201
202 return moves;
203 }
204
205 getAllValidMoves() {
206 let moves = super.getAllValidMoves();
207 const color = this.turn;
208 for (let i = 0; i < V.RESERVE_PIECES.length; i++)
209 moves = moves.concat(
210 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
211 );
212 return this.filterValid(moves);
213 }
214
215 atLeastOneMove() {
216 if (!super.atLeastOneMove()) {
217 // Search one reserve move
218 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
219 let moves = this.filterValid(
220 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
221 );
222 if (moves.length > 0) return true;
223 }
224 return false;
225 }
226 return true;
227 }
228
229 canTake([x1, y1], [x2, y2]) {
230 // Self-captures allowed, except for the king:
231 return this.getPiece(x2, y2) != V.KING;
232 }
233
234 updateVariables(move) {
235 super.updateVariables(move);
236 if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle
237 const color = V.GetOppCol(this.turn);
238 if (move.vanish.length == 0) {
239 this.reserve[color][move.appear[0].p]--;
240 return;
241 }
242 else if (move.vanish.length == 2 && move.vanish[1].c == color) {
243 // Self-capture
244 this.reserve[color][move.vanish[1].p]++;
245 }
246 }
247
248 unupdateVariables(move) {
249 super.unupdateVariables(move);
250 if (move.vanish.length == 2 && move.appear.length == 2) return;
251 const color = this.turn;
252 if (move.vanish.length == 0) {
253 this.reserve[color][move.appear[0].p]++;
254 return;
255 }
256 else if (move.vanish.length == 2 && move.vanish[1].c == color) {
257 this.reserve[color][move.vanish[1].p]--;
258 }
259 }
260
261 static get SEARCH_DEPTH() {
262 return 2;
263 }
264
265 evalPosition() {
266 let evaluation = super.evalPosition();
267 // Add reserves:
268 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
269 const p = V.RESERVE_PIECES[i];
270 evaluation += this.reserve["w"][p] * V.VALUES[p];
271 evaluation -= this.reserve["b"][p] * V.VALUES[p];
272 }
273 return evaluation;
274 }
275
276 getNotation(move) {
277 const finalSquare = V.CoordsToSquare(move.end);
278 if (move.vanish.length > 0) {
279 if (move.appear.length > 0) {
280 // Standard move
281 return super.getNotation(move);
282 } else {
283 // Pawn fallen: capturing or not
284 let res = "";
285 if (move.vanish.length == 2)
286 res += V.CoordToColumn(move.start.y) + "x";
287 return res + finalSquare;
288 }
289 }
290 // Rebirth:
291 const piece =
292 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
293 return piece + "@" + V.CoordsToSquare(move.end);
294 }
295 };