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