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