4c54e941ea2bf48f2feb33e4fe734f9b51fda073
[vchess.git] / client / src / variants / Clorange.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export class ClorangeRules 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]{20,20}$/))
10 return false;
11 return true;
12 }
13
14 static ParseFen(fen) {
15 const fenParts = fen.split(" ");
16 return Object.assign(
17 ChessRules.ParseFen(fen),
18 { reserve: fenParts[5] }
19 );
20 }
21
22 static GenRandInitFen(randomness) {
23 // Capturing and non-capturing reserves:
24 return ChessRules.GenRandInitFen(randomness) + " 00000000000000000000";
25 }
26
27 getFen() {
28 return super.getFen() + " " + this.getReserveFen();
29 }
30
31 getFenForRepeat() {
32 return super.getFenForRepeat() + "_" + this.getReserveFen();
33 }
34
35 getReserveFen() {
36 return (
37 Object.keys(this.reserve).map(
38 c => Object.values(this.reserve[c]).join("")).join("")
39 );
40 }
41
42 getEpSquare(moveOrSquare) {
43 if (!moveOrSquare) return undefined;
44 if (typeof moveOrSquare === "string") {
45 const square = moveOrSquare;
46 if (square == "-") return undefined;
47 return V.SquareToCoords(square);
48 }
49 const move = moveOrSquare;
50 const s = move.start,
51 e = move.end;
52 if (
53 s.y == e.y &&
54 Math.abs(s.x - e.x) == 2 &&
55 move.vanish.length > 0 && ['p', 's'].includes(move.vanish[0].p)
56 ) {
57 return {
58 x: (s.x + e.x) / 2,
59 y: s.y
60 };
61 }
62 return undefined;
63 }
64
65 setOtherVariables(fen) {
66 super.setOtherVariables(fen);
67 // Also init reserves (used by the interface to show landable pieces)
68 const reserve =
69 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
70 this.reserve = {
71 w: {
72 'p': reserve[0],
73 'r': reserve[1],
74 'n': reserve[2],
75 'b': reserve[3],
76 'q': reserve[4],
77 's': reserve[5],
78 'u': reserve[6],
79 'o': reserve[7],
80 'c': reserve[8],
81 't': reserve[9]
82 },
83 b: {
84 'p': reserve[10],
85 'r': reserve[11],
86 'n': reserve[12],
87 'b': reserve[13],
88 'q': reserve[14],
89 's': reserve[15],
90 'u': reserve[16],
91 'o': reserve[17],
92 'c': reserve[18],
93 't': reserve[19]
94 }
95 };
96 }
97
98 getColor(i, j) {
99 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
100 return this.board[i][j].charAt(0);
101 }
102
103 getPiece(i, j) {
104 if (i >= V.size.x) return V.RESERVE_PIECES[j];
105 return this.board[i][j].charAt(1);
106 }
107
108 getPpath(b) {
109 return (V.NON_VIOLENT.includes(b[1]) ? "Clorange/" : "") + b;
110 }
111
112 getReservePpath(index, color) {
113 const prefix =
114 (V.NON_VIOLENT.includes(V.RESERVE_PIECES[index]) ? "Clorange/" : "");
115 return prefix + color + V.RESERVE_PIECES[index];
116 }
117
118 static get NON_VIOLENT() {
119 return ['s', 'u', 'o', 'c', 't'];
120 }
121
122 static get PIECES() {
123 return ChessRules.PIECES.concat(V.NON_VIOLENT);
124 }
125
126 // Ordering on reserve pieces
127 static get RESERVE_PIECES() {
128 return V.PIECES.filter(p => p != 'k');
129 }
130
131 getReserveMoves([x, y]) {
132 const color = this.turn;
133 const p = V.RESERVE_PIECES[y];
134 if (this.reserve[color][p] == 0) return [];
135 let moves = [];
136 let rank1 = 0;
137 let rank2 = V.size.x - 1;
138 if (['p', 's'].includes(p)) {
139 if (color == 'w') rank1++;
140 else rank2--;
141 }
142 for (let i = rank1; i <= rank2; i++) {
143 for (let j = 0; j < V.size.y; j++) {
144 if (this.board[i][j] == V.EMPTY) {
145 let mv = new Move({
146 appear: [
147 new PiPo({
148 x: i,
149 y: j,
150 c: color,
151 p: p
152 })
153 ],
154 vanish: [],
155 start: { x: x, y: y }, //a bit artificial...
156 end: { x: i, y: j }
157 });
158 moves.push(mv);
159 }
160 }
161 }
162 return moves;
163 }
164
165 getPotentialMovesFrom([x, y]) {
166 if (x >= V.size.x)
167 // Reserves, outside of board: x == sizeX(+1)
168 return this.getReserveMoves([x, y]);
169 // Standard moves
170 switch (this.getPiece(x, y)) {
171 case 's': return super.getPotentialPawnMoves([x, y]);
172 case 'u': return super.getPotentialRookMoves([x, y]);
173 case 'o': return super.getPotentialKnightMoves([x, y]);
174 case 'c': return super.getPotentialBishopMoves([x, y]);
175 case 't': return super.getPotentialQueenMoves([x, y]);
176 default: return super.getPotentialMovesFrom([x, y]);
177 }
178 return []; //never reached
179 }
180
181 getPotentialPawnMoves(sq) {
182 let moves = super.getPotentialPawnMoves(sq);
183 moves.forEach(m => {
184 if (m.vanish[0].p == 's' && m.appear[0].p != 's') {
185 // Promotion pieces should be non-violent as well:
186 const pIdx = ChessRules.PIECES.findIndex(p => p == m.appear[0].p)
187 m.appear[0].p = V.NON_VIOLENT[pIdx];
188 }
189 });
190 return moves;
191 }
192
193 getSlideNJumpMoves([x, y], steps, oneStep) {
194 let moves = [];
195 const canTake = ChessRules.PIECES.includes(this.getPiece(x, y));
196 outerLoop: for (let step of steps) {
197 let i = x + step[0];
198 let j = y + step[1];
199 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
200 moves.push(this.getBasicMove([x, y], [i, j]));
201 if (oneStep) continue outerLoop;
202 i += step[0];
203 j += step[1];
204 }
205 if (V.OnBoard(i, j) && canTake && this.canTake([x, y], [i, j]))
206 moves.push(this.getBasicMove([x, y], [i, j]));
207 }
208 return moves;
209 }
210
211 getAllValidMoves() {
212 let moves = super.getAllPotentialMoves();
213 const color = this.turn;
214 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
215 moves = moves.concat(
216 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
217 );
218 }
219 return this.filterValid(moves);
220 }
221
222 atLeastOneMove() {
223 if (!super.atLeastOneMove()) {
224 // Search one reserve move
225 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
226 let moves = this.filterValid(
227 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
228 );
229 if (moves.length > 0) return true;
230 }
231 return false;
232 }
233 return true;
234 }
235
236 prePlay(move) {
237 super.prePlay(move);
238 // Skip castle:
239 if (move.vanish.length == 2 && move.appear.length == 2) return;
240 const color = this.turn;
241 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
242 else if (move.vanish.length == 2) {
243 // Capture
244 const normal = ChessRules.PIECES.includes(move.vanish[1].p);
245 const pIdx =
246 normal
247 ? ChessRules.PIECES.findIndex(p => p == move.vanish[1].p)
248 : V.NON_VIOLENT.findIndex(p => p == move.vanish[1].p);
249 const rPiece = (normal ? V.NON_VIOLENT : ChessRules.PIECES)[pIdx];
250 this.reserve[move.vanish[1].c][rPiece]++;
251 }
252 }
253
254 postUndo(move) {
255 super.postUndo(move);
256 if (move.vanish.length == 2 && move.appear.length == 2) return;
257 const color = this.turn;
258 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
259 else if (move.vanish.length == 2) {
260 const normal = ChessRules.PIECES.includes(move.vanish[1].p);
261 const pIdx =
262 normal
263 ? ChessRules.PIECES.findIndex(p => p == move.vanish[1].p)
264 : V.NON_VIOLENT.findIndex(p => p == move.vanish[1].p);
265 const rPiece = (normal ? V.NON_VIOLENT : ChessRules.PIECES)[pIdx];
266 this.reserve[move.vanish[1].c][rPiece]--;
267 }
268 }
269
270 static get SEARCH_DEPTH() {
271 return 2;
272 }
273
274 static get VALUES() {
275 return Object.assign(
276 {
277 s: 0.75,
278 u: 4,
279 o: 2,
280 c: 2,
281 t: 7
282 },
283 ChessRules.VALUES
284 );
285 }
286
287 evalPosition() {
288 let evaluation = super.evalPosition();
289 // Add reserves:
290 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
291 const p = V.RESERVE_PIECES[i];
292 evaluation += this.reserve["w"][p] * V.VALUES[p];
293 evaluation -= this.reserve["b"][p] * V.VALUES[p];
294 }
295 return evaluation;
296 }
297
298 getNotation(move) {
299 const finalSquare = V.CoordsToSquare(move.end);
300 if (move.vanish.length > 0) {
301 // Standard move (maybe with non-violent piece)
302 let notation = super.getNotation(move);
303 if (move.vanish[0].p == 's' && move.appear[0].p != 's')
304 // Fix non-violent promotions:
305 notation += "=" + move.appear[0].p.toUpperCase();
306 return notation;
307 }
308 // Rebirth:
309 const piece =
310 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
311 return piece + "@" + V.CoordsToSquare(move.end);
312 }
313 };