Draft Ball variant + some fixes, enhancements and code cleaning
[vchess.git] / client / src / variants / Recycle.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export class RecycleRules extends ChessRules {
5 static get PawnSpecs() {
6 return Object.assign(
7 {},
8 ChessRules.PawnSpecs,
9 { promotions: [V.PAWN] } //in fact, none
10 );
11 }
12
13 static IsGoodFen(fen) {
14 if (!ChessRules.IsGoodFen(fen)) return false;
15 const fenParsed = V.ParseFen(fen);
16 // 5) Check reserves
17 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
18 return false;
19 return true;
20 }
21
22 static ParseFen(fen) {
23 const fenParts = fen.split(" ");
24 return Object.assign(
25 ChessRules.ParseFen(fen),
26 { reserve: fenParts[5] }
27 );
28 }
29
30 getEpSquare(moveOrSquare) {
31 if (typeof moveOrSquare !== "object" || moveOrSquare.vanish.length > 0)
32 return super.getEpSquare(moveOrSquare);
33 // Landing move: no en-passant
34 return undefined;
35 }
36
37 static GenRandInitFen(randomness) {
38 return ChessRules.GenRandInitFen(randomness) + " 0000000000";
39 }
40
41 getFen() {
42 return super.getFen() + " " + this.getReserveFen();
43 }
44
45 getFenForRepeat() {
46 return super.getFenForRepeat() + "_" + this.getReserveFen();
47 }
48
49 getReserveFen() {
50 let counts = new Array(10);
51 for (
52 let i = 0;
53 i < V.PIECES.length - 1;
54 i++ //-1: no king reserve
55 ) {
56 counts[i] = this.reserve["w"][V.PIECES[i]];
57 counts[5 + i] = this.reserve["b"][V.PIECES[i]];
58 }
59 return counts.join("");
60 }
61
62 setOtherVariables(fen) {
63 super.setOtherVariables(fen);
64 const fenParsed = V.ParseFen(fen);
65 // Also init reserves (used by the interface to show landable pieces)
66 this.reserve = {
67 w: {
68 [V.PAWN]: parseInt(fenParsed.reserve[0]),
69 [V.ROOK]: parseInt(fenParsed.reserve[1]),
70 [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
71 [V.BISHOP]: parseInt(fenParsed.reserve[3]),
72 [V.QUEEN]: parseInt(fenParsed.reserve[4])
73 },
74 b: {
75 [V.PAWN]: parseInt(fenParsed.reserve[5]),
76 [V.ROOK]: parseInt(fenParsed.reserve[6]),
77 [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
78 [V.BISHOP]: parseInt(fenParsed.reserve[8]),
79 [V.QUEEN]: parseInt(fenParsed.reserve[9])
80 }
81 };
82 }
83
84 getColor(i, j) {
85 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
86 return this.board[i][j].charAt(0);
87 }
88
89 getPiece(i, j) {
90 if (i >= V.size.x) return V.RESERVE_PIECES[j];
91 return this.board[i][j].charAt(1);
92 }
93
94 // Used by the interface:
95 getReservePpath(index, color) {
96 return color + V.RESERVE_PIECES[index];
97 }
98
99 // Ordering on reserve pieces
100 static get RESERVE_PIECES() {
101 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
102 }
103
104 getReserveMoves([x, y]) {
105 const color = this.turn;
106 const p = V.RESERVE_PIECES[y];
107 if (this.reserve[color][p] == 0) return [];
108 let moves = [];
109 const pawnShift = p == V.PAWN ? 1 : 0;
110 for (let i = pawnShift; i < V.size.x - pawnShift; i++) {
111 for (let j = 0; j < V.size.y; j++) {
112 if (this.board[i][j] == V.EMPTY) {
113 let mv = new Move({
114 appear: [
115 new PiPo({
116 x: i,
117 y: j,
118 c: color,
119 p: p
120 })
121 ],
122 vanish: [],
123 start: { x: x, y: y }, //a bit artificial...
124 end: { x: i, y: j }
125 });
126 moves.push(mv);
127 }
128 }
129 }
130 return moves;
131 }
132
133 getPotentialMovesFrom([x, y]) {
134 if (x >= V.size.x) {
135 // Reserves, outside of board: x == sizeX(+1)
136 return this.getReserveMoves([x, y]);
137 }
138 // Standard moves
139 return super.getPotentialMovesFrom([x, y]);
140 }
141
142 getPotentialPawnMoves([x, y]) {
143 let moves = super.getPotentialPawnMoves([x, y]);
144 // Remove pawns on 8th rank ("fallen"):
145 const color = this.turn;
146 const lastRank = (color == "w" ? 0 : V.size.x - 1);
147 moves.forEach(m => {
148 if (m.appear[0].x == lastRank) m.appear.pop();
149 });
150 return moves;
151 }
152
153 getAllValidMoves() {
154 let moves = super.getAllValidMoves();
155 const color = this.turn;
156 for (let i = 0; i < V.RESERVE_PIECES.length; i++)
157 moves = moves.concat(
158 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
159 );
160 return this.filterValid(moves);
161 }
162
163 atLeastOneMove() {
164 if (!super.atLeastOneMove()) {
165 // Search one reserve move
166 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
167 let moves = this.filterValid(
168 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
169 );
170 if (moves.length > 0) return true;
171 }
172 return false;
173 }
174 return true;
175 }
176
177 canTake([x1, y1], [x2, y2]) {
178 // Self-captures allowed, except for the king:
179 return this.getPiece(x2, y2) != V.KING;
180 }
181
182 prePlay(move) {
183 super.prePlay(move);
184 if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle
185 const color = this.turn;
186 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
187 else if (move.vanish.length == 2 && move.vanish[1].c == color)
188 // Self-capture
189 this.reserve[color][move.vanish[1].p]++;
190 }
191
192 postUndo(move) {
193 super.postUndo(move);
194 if (move.vanish.length == 2 && move.appear.length == 2) return;
195 const color = this.turn;
196 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
197 else if (move.vanish.length == 2 && move.vanish[1].c == color)
198 this.reserve[color][move.vanish[1].p]--;
199 }
200
201 static get SEARCH_DEPTH() {
202 return 2;
203 }
204
205 evalPosition() {
206 let evaluation = super.evalPosition();
207 // Add reserves:
208 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
209 const p = V.RESERVE_PIECES[i];
210 evaluation += this.reserve["w"][p] * V.VALUES[p];
211 evaluation -= this.reserve["b"][p] * V.VALUES[p];
212 }
213 return evaluation;
214 }
215
216 getNotation(move) {
217 const finalSquare = V.CoordsToSquare(move.end);
218 if (move.vanish.length > 0) {
219 if (move.appear.length > 0) {
220 // Standard move
221 return super.getNotation(move);
222 } else {
223 // Pawn fallen: capturing or not
224 let res = "";
225 if (move.vanish.length == 2)
226 res += V.CoordToColumn(move.start.y) + "x";
227 return res + finalSquare;
228 }
229 }
230 // Rebirth:
231 const piece =
232 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
233 return piece + "@" + V.CoordsToSquare(move.end);
234 }
235 };