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