Attempt to improve scrolling on smartphone
[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(options) {
24 // Capturing and non-capturing reserves:
25 return ChessRules.GenRandInitFen(options) + " 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 canTake([x1, y1], [x2, y2]) {
199 return (
200 this.getColor(x1, y1) !== this.getColor(x2, y2) &&
201 ChessRules.PIECES.includes(this.getPiece(x1, y1))
202 );
203 }
204
205 getAllValidMoves() {
206 let moves = super.getAllPotentialMoves();
207 const color = this.turn;
208 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
209 moves = moves.concat(
210 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
211 );
212 }
213 return this.filterValid(moves);
214 }
215
216 atLeastOneMove() {
217 if (!super.atLeastOneMove()) {
218 // Search one reserve move
219 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
220 let moves = this.filterValid(
221 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
222 );
223 if (moves.length > 0) return true;
224 }
225 return false;
226 }
227 return true;
228 }
229
230 prePlay(move) {
231 super.prePlay(move);
232 // Skip castle:
233 if (move.vanish.length == 2 && move.appear.length == 2) return;
234 const color = this.turn;
235 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
236 else if (move.vanish.length == 2) {
237 // Capture
238 const normal = ChessRules.PIECES.includes(move.vanish[1].p);
239 const pIdx =
240 normal
241 ? ChessRules.PIECES.findIndex(p => p == move.vanish[1].p)
242 : V.NON_VIOLENT.findIndex(p => p == move.vanish[1].p);
243 const rPiece = (normal ? V.NON_VIOLENT : ChessRules.PIECES)[pIdx];
244 this.reserve[move.vanish[1].c][rPiece]++;
245 }
246 }
247
248 postUndo(move) {
249 super.postUndo(move);
250 if (move.vanish.length == 2 && move.appear.length == 2) return;
251 const color = this.turn;
252 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
253 else if (move.vanish.length == 2) {
254 const normal = ChessRules.PIECES.includes(move.vanish[1].p);
255 const pIdx =
256 normal
257 ? ChessRules.PIECES.findIndex(p => p == move.vanish[1].p)
258 : V.NON_VIOLENT.findIndex(p => p == move.vanish[1].p);
259 const rPiece = (normal ? V.NON_VIOLENT : ChessRules.PIECES)[pIdx];
260 this.reserve[move.vanish[1].c][rPiece]--;
261 }
262 }
263
264 static get SEARCH_DEPTH() {
265 return 2;
266 }
267
268 static get VALUES() {
269 return Object.assign(
270 {
271 s: 0.75,
272 u: 4,
273 o: 2,
274 c: 2,
275 t: 7
276 },
277 ChessRules.VALUES
278 );
279 }
280
281 evalPosition() {
282 let evaluation = super.evalPosition();
283 // Add reserves:
284 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
285 const p = V.RESERVE_PIECES[i];
286 evaluation += this.reserve["w"][p] * V.VALUES[p];
287 evaluation -= this.reserve["b"][p] * V.VALUES[p];
288 }
289 return evaluation;
290 }
291
292 getNotation(move) {
293 const finalSquare = V.CoordsToSquare(move.end);
294 if (move.vanish.length > 0) {
295 // Standard move (maybe with non-violent piece)
296 let notation = super.getNotation(move);
297 if (move.vanish[0].p == 's' && move.appear[0].p != 's')
298 // Fix non-violent promotions:
299 notation += "=" + move.appear[0].p.toUpperCase();
300 return notation;
301 }
302 // Rebirth:
303 const piece =
304 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
305 return piece + "@" + V.CoordsToSquare(move.end);
306 }
307
308 };