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