Add Yote
[vchess.git] / client / src / variants / Yote.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2
3 export class YoteRules extends ChessRules {
4
5 static get HasFlags() {
6 return false;
7 }
8
9 static get HasEnpassant() {
10 return false;
11 }
12
13 static get Monochrome() {
14 return true;
15 }
16
17 static get Notoodark() {
18 return true;
19 }
20
21 static get ReverseColors() {
22 return true;
23 }
24
25 static IsGoodFen(fen) {
26 if (!ChessRules.IsGoodFen(fen)) return false;
27 const fenParsed = V.ParseFen(fen);
28 // 3) Check reserves
29 if (
30 !fenParsed.reserve ||
31 !fenParsed.reserve.match(/^([0-9]{1,2},){2,2}$/)
32 ) {
33 return false;
34 }
35 // 4) Check lastMove
36 if (!fenParsed.lastMove) return false;
37 const lmParts = fenParsed.lastMove.split(",");
38 for (lp of lmParts) {
39 if (lp != "-" && !lp.match(/^([a-f][1-5]){2,2}$/)) return false;
40 }
41 return true;
42 }
43
44 static ParseFen(fen) {
45 const fenParts = fen.split(" ");
46 return Object.assign(
47 ChessRules.ParseFen(fen),
48 {
49 reserve: fenParts[3],
50 lastMove: fenParts[4]
51 }
52 );
53 }
54
55 static GenRandInitFen(randomness) {
56 return "6/6/6/6/6 w 0 12,12 -,-";
57 }
58
59 getFen() {
60 return (
61 super.getFen() + " " +
62 this.getReserveFen() + " " +
63 this.getLastmoveFen()
64 );
65 }
66
67 getFenForRepeat() {
68 return super.getFenForRepeat() + "_" + this.getReserveFen();
69 }
70
71 getReserveFen() {
72 return (
73 (!this.reserve["w"] ? 0 : this.reserve["w"][V.PAWN]) + "," +
74 (!this.reserve["b"] ? 0 : this.reserve["b"][V.PAWN])
75 );
76 }
77
78 getLastmoveFen() {
79 const L = this.lastMove.length;
80 const lm = this.lastMove[L-1];
81 return (
82 (
83 !lm['w']
84 ? '-'
85 : V.CoordsToSquare(lm['w'].start) + V.CoordsToSquare(lm['w'].end)
86 )
87 + "," +
88 (
89 !lm['b']
90 ? '-'
91 : V.CoordsToSquare(lm['b'].start) + V.CoordsToSquare(lm['b'].end)
92 )
93 );
94 }
95
96 setOtherVariables(fen) {
97 const fenParsed = V.ParseFen(fen);
98 const reserve = fenParsed.reserve.split(",").map(x => parseInt(x, 10));
99 this.reserve = {
100 w: { [V.PAWN]: reserve[0] },
101 b: { [V.PAWN]: reserve[1] }
102 };
103 // And last moves (to avoid undoing your last move)
104 const lmParts = fenParsed.lastMove.split(",");
105 this.lastMove = [{ w: null, b: null }];
106 ['w', 'b'].forEach((c, i) => {
107 if (lmParts[i] != '-') {
108 this.lastMove[0][c] = {
109 start: V.SquareToCoords(lmParts[i].substr(0, 2)),
110 end: V.SquareToCoords(lmParts[i].substr(2))
111 };
112 }
113 });
114 // Local stack to know if (current) last move captured something
115 this.captures = [false];
116 }
117
118 static get size() {
119 return { x: 5, y: 6 };
120 }
121
122 static get PIECES() {
123 return [V.PAWN];
124 }
125
126 getColor(i, j) {
127 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
128 return this.board[i][j].charAt(0);
129 }
130
131 getPiece() {
132 return V.PAWN;
133 }
134
135 getPpath(b) {
136 return "Yote/" + b;
137 }
138
139 getReservePpath(index, color) {
140 return "Yote/" + color + V.PAWN;
141 }
142
143 static get RESERVE_PIECES() {
144 return [V.PAWN];
145 }
146
147 canIplay(side, [x, y]) {
148 if (this.turn != side) return false;
149 const L = this.captures.length;
150 if (!this.captures[L-1]) return this.getColor(x, y) == side;
151 return (x < V.size.x && this.getColor(x, y) != side);
152 }
153
154 hoverHighlight(x, y) {
155 const L = this.captures.length;
156 if (!this.captures[L-1]) return false;
157 const oppCol = V.GetOppCol(this.turn);
158 return (this.board[x][y] != V.EMPTY && this.getColor(x, y) == oppCol);
159 }
160
161 // TODO: onlyClick() doesn't fulfill exactly its role.
162 // Seems that there is some lag... TOFIX
163 onlyClick([x, y]) {
164 const L = this.captures.length;
165 return (this.captures[L-1] && this.getColor(x, y) != this.turn);
166 }
167
168 // PATCH related to above TO-DO:
169 getPossibleMovesFrom([x, y]) {
170 if (x < V.size.x && this.board[x][y] == V.EMPTY) return [];
171 return super.getPossibleMovesFrom([x, y]);
172 }
173
174 doClick([x, y]) {
175 const L = this.captures.length;
176 if (!this.captures[L-1]) return null;
177 const oppCol = V.GetOppCol(this.turn);
178 if (this.board[x][y] == V.EMPTY || this.getColor(x, y) != oppCol)
179 return null;
180 return new Move({
181 appear: [],
182 vanish: [ new PiPo({ x: x, y: y, c: oppCol, p: V.PAWN }) ],
183 end: { x: x, y: y }
184 });
185 }
186
187 getReserveMoves(x) {
188 const color = this.turn;
189 if (this.reserve[color][V.PAWN] == 0) return [];
190 let moves = [];
191 for (let i = 0; i < V.size.x; i++) {
192 for (let j = 0; j < V.size.y; j++) {
193 if (this.board[i][j] == V.EMPTY) {
194 let mv = new Move({
195 appear: [
196 new PiPo({
197 x: i,
198 y: j,
199 c: color,
200 p: V.PAWN
201 })
202 ],
203 vanish: [],
204 start: { x: x, y: 0 }, //a bit artificial...
205 end: { x: i, y: j }
206 });
207 moves.push(mv);
208 }
209 }
210 }
211 return moves;
212 }
213
214 getPotentialMovesFrom([x, y]) {
215 const L = this.captures.length;
216 if (this.captures[L-1]) {
217 if (x >= V.size.x) return [];
218 const mv = this.doClick([x, y]);
219 return (!!mv ? [mv] : []);
220 }
221 if (x >= V.size.x)
222 return this.getReserveMoves([x, y]);
223 return this.getPotentialPawnMoves([x, y]);
224 }
225
226 getPotentialPawnMoves([x, y]) {
227 let moves = [];
228 const color = this.turn;
229 const L = this.lastMove.length;
230 const lm = this.lastMove[L-1];
231 let forbiddenStep = null;
232 if (!!lm[color]) {
233 forbiddenStep = [
234 lm[color].start.x - lm[color].end.x,
235 lm[color].start.y - lm[color].end.y
236 ];
237 }
238 const oppCol = V.GetOppCol(color);
239 for (let s of V.steps[V.ROOK]) {
240 if (
241 !!forbiddenStep &&
242 s[0] == forbiddenStep[0] && s[1] == forbiddenStep[1]
243 ) {
244 continue;
245 }
246 const [i1, j1] = [x + s[0], y + s[1]];
247 if (V.OnBoard(i1, j1)) {
248 if (this.board[i1][j1] == V.EMPTY)
249 moves.push(super.getBasicMove([x, y], [i1, j1]));
250 else if (this.getColor(i1, j1) == oppCol) {
251 const [i2, j2] = [i1 + s[0], j1 + s[1]];
252 if (V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY) {
253 let mv = new Move({
254 appear: [
255 new PiPo({ x: i2, y: j2, c: color, p: V.PAWN })
256 ],
257 vanish: [
258 new PiPo({ x: x, y: y, c: color, p: V.PAWN }),
259 new PiPo({ x: i1, y: j1, c: oppCol, p: V.PAWN })
260 ]
261 });
262 moves.push(mv);
263 }
264 }
265 }
266 }
267 return moves;
268 }
269
270 getAllPotentialMoves() {
271 let moves = super.getAllPotentialMoves();
272 const color = this.turn;
273 moves = moves.concat(
274 this.getReserveMoves(V.size.x + (color == "w" ? 0 : 1)));
275 return moves;
276 }
277
278 filterValid(moves) {
279 return moves;
280 }
281
282 getCheckSquares() {
283 return [];
284 }
285
286 atLeastOneMove() {
287 if (!super.atLeastOneMove()) {
288 // Search one reserve move
289 const moves =
290 this.getReserveMoves(V.size.x + (this.turn == "w" ? 0 : 1));
291 if (moves.length > 0) return true;
292 return false;
293 }
294 return true;
295 }
296
297 play(move) {
298 const color = this.turn;
299 move.turn = color; //for undo
300 const L = this.lastMove.length;
301 if (color == 'w')
302 this.lastMove.push({ w: null, b: this.lastMove[L-1]['b'] });
303 if (move.appear.length == move.vanish.length) { //== 1
304 // Normal move (non-capturing, non-dropping, non-removal)
305 let lm = this.lastMove[L - (color == 'w' ? 0 : 1)];
306 if (!lm[color]) lm[color] = {};
307 lm[color].start = move.start;
308 lm[color].end = move.end;
309 }
310 const oppCol = V.GetOppCol(color);
311 V.PlayOnBoard(this.board, move);
312 const captureNotEnding = (
313 move.vanish.length == 2 &&
314 this.board.some(b => b.some(cell => cell != "" && cell[0] == oppCol))
315 );
316 this.captures.push(captureNotEnding);
317 // Change turn unless I just captured something,
318 // and an opponent stone can be removed from board.
319 if (!captureNotEnding) {
320 this.turn = oppCol;
321 this.movesCount++;
322 }
323 this.postPlay(move);
324 }
325
326 undo(move) {
327 V.UndoOnBoard(this.board, move);
328 if (this.turn == 'b') this.lastMove.pop();
329 else this.lastMove['b'] = null;
330 this.captures.pop();
331 if (move.turn != this.turn) {
332 this.turn = move.turn;
333 this.movesCount--;
334 }
335 this.postUndo(move);
336 }
337
338 postPlay(move) {
339 if (move.vanish.length == 0) {
340 const color = move.appear[0].c;
341 this.reserve[color][V.PAWN]--;
342 if (this.reserve[color][V.PAWN] == 0) delete this.reserve[color];
343 }
344 }
345
346 postUndo(move) {
347 if (move.vanish.length == 0) {
348 const color = move.appear[0].c;
349 if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 0 };
350 this.reserve[color][V.PAWN]++;
351 }
352 }
353
354 getCurrentScore() {
355 if (this.movesCount <= 2) return "*";
356 const color = this.turn;
357 // If no stones on board, or no move available, I lose
358 if (
359 this.board.every(b => {
360 return b.every(cell => {
361 return (cell == "" || cell[0] != color);
362 });
363 })
364 ||
365 !this.atLeastOneMove()
366 ) {
367 return (color == 'w' ? "0-1" : "1-0");
368 }
369 return "*";
370 }
371
372 static get SEARCH_DEPTH() {
373 return 4;
374 }
375
376 evalPosition() {
377 let evaluation = super.evalPosition();
378 // Add reserves:
379 evaluation += this.reserve["w"][V.PAWN];
380 evaluation -= this.reserve["b"][V.PAWN];
381 return evaluation;
382 }
383
384 getNotation(move) {
385 if (move.vanish.length == 0)
386 // Placement:
387 return "@" + V.CoordsToSquare(move.end);
388 if (move.appear.length == 0)
389 // Removal after capture:
390 return V.CoordsToSquare(move.start) + "X";
391 return (
392 V.CoordsToSquare(move.start) +
393 (move.vanish.length == 2 ? "x" : "") +
394 V.CoordsToSquare(move.end)
395 );
396 }
397
398 };