Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Rollerball.js
1 import { ChessRules } from "@/base_rules";
2
3 export class RollerballRules extends ChessRules {
4
5 static get Options() {
6 return null;
7 }
8
9 static get HasEnpassant() {
10 return false;
11 }
12
13 static get HasCastle() {
14 return false;
15 }
16
17 static get DarkBottomRight() {
18 return true;
19 }
20
21 static get PIECES() {
22 return [V.PAWN, V.KING, V.ROOK, V.BISHOP];
23 }
24
25 static get size() {
26 return { x: 7, y: 7 };
27 }
28
29 // TODO: the wall position should be checked too
30 static IsGoodPosition(position) {
31 if (position.length == 0) return false;
32 const rows = position.split("/");
33 if (rows.length != V.size.x) return false;
34 let kings = { "k": 0, "K": 0 };
35 for (let row of rows) {
36 let sumElts = 0;
37 for (let i = 0; i < row.length; i++) {
38 if (['K','k'].includes(row[i])) kings[row[i]]++;
39 if (['x'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++;
40 else {
41 const num = parseInt(row[i], 10);
42 if (isNaN(num)) return false;
43 sumElts += num;
44 }
45 }
46 if (sumElts != V.size.y) return false;
47 }
48 if (Object.values(kings).some(v => v != 1)) return false;
49 return true;
50 }
51
52 // NOTE: canTake() is wrong, but next method is enough
53 static OnBoard(x, y) {
54 return (
55 (x >= 0 && x <= 6 && y >= 0 && y <= 6) &&
56 (![2, 3, 4].includes(x) || ![2, 3, 4].includes(y))
57 );
58 }
59
60 static IsGoodFlags(flags) {
61 // 2 for kings: last zone reached
62 return !!flags.match(/^[0-7]{2,2}$/);
63 }
64
65 setFlags(fenflags) {
66 this.kingFlags = {
67 w: parseInt(fenflags.charAt(0), 10),
68 b: parseInt(fenflags.charAt(1), 10)
69 };
70 }
71
72 aggregateFlags() {
73 return this.kingFlags;
74 }
75
76 disaggregateFlags(flags) {
77 this.kingFlags = flags;
78 }
79
80 getFlagsFen() {
81 return this.kingFlags['w'].toString() + this.kingFlags['b'].toString();
82 }
83
84 // For space in the middle:
85 static get NOTHING() {
86 return "xx";
87 }
88
89 static board2fen(b) {
90 if (b[0] == 'x') return 'x';
91 return ChessRules.board2fen(b);
92 }
93
94 static fen2board(f) {
95 if (f == 'x') return V.NOTHING;
96 return ChessRules.fen2board(f);
97 }
98
99 getPpath(b) {
100 if (b[0] == 'x') return "Omega/nothing";
101 return b;
102 }
103
104 static GenRandInitFen() {
105 return "2rbp2/2rkp2/2xxx2/2xxx2/2xxx2/2PKR2/2PBR2 w 0 00";
106 }
107
108 getPotentialMovesFrom(sq) {
109 switch (this.getPiece(sq[0], sq[1])) {
110 case V.PAWN: return this.getPotentialPawnMoves(sq);
111 case V.ROOK: return this.getPotentialRookMoves(sq);
112 case V.BISHOP: return this.getPotentialBishopMoves(sq);
113 case V.KING: return super.getPotentialKingMoves(sq);
114 }
115 return [];
116 }
117
118 getPotentialPawnMoves([x, y]) {
119 const c = this.turn;
120 // Need to know pawn area to deduce move options
121 const inMiddleX = [2, 3, 4].includes(x);
122 const inMiddleY = [2, 3, 4].includes(y);
123 // In rectangular areas on the sides?
124 if (inMiddleX) {
125 const forward = (y <= 1 ? -1 : 1);
126 return (
127 super.getSlideNJumpMoves(
128 [x, y], [[forward, -1], [forward, 0], [forward, 1]], 1)
129 );
130 }
131 if (inMiddleY) {
132 const forward = (x <= 1 ? 1 : -1);
133 let moves =
134 super.getSlideNJumpMoves(
135 [x, y], [[-1, forward], [0, forward], [1, forward]], 1);
136 // Promotions may happen:
137 let extraMoves = [];
138 moves.forEach(m => {
139 if (
140 (c == 'w' && x <= 1 && m.end.y == 4) ||
141 (c == 'b' && x >= 5 && m.end.y == 2)
142 ) {
143 m.appear[0].p = V.ROOK;
144 let m2 = JSON.parse(JSON.stringify(m));
145 m2.appear[0].p = V.BISHOP;
146 extraMoves.push(m2);
147 }
148 });
149 Array.prototype.push.apply(moves, extraMoves);
150 return moves;
151 }
152 // In a corner:
153 const toRight = (x == 0 && [0, 1, 5].includes(y)) || (x == 1 && y == 1);
154 const toLeft = (x == 6 && [1, 5, 6].includes(y)) || (x == 5 && y == 5);
155 const toUp = (y == 0 && [1, 5, 6].includes(x)) || (x == 5 && y == 1);
156 const toBottom = (y == 6 && [0, 1, 5].includes(x)) || (x == 1 && y == 5);
157 if (toRight || toLeft) {
158 const forward = (toRight ? 1 : -1);
159 return (
160 super.getSlideNJumpMoves(
161 [x, y], [[-1, forward], [0, forward], [1, forward]], 1)
162 );
163 }
164 const forward = (toUp ? -1 : 1);
165 return (
166 super.getSlideNJumpMoves(
167 [x, y], [[forward, -1], [forward, 0], [forward, 1]], 1)
168 );
169 }
170
171 getPotentialRookMoves([x, y]) {
172 let multiStep = [],
173 oneStep = [];
174 if (x <= 1) multiStep.push([0, 1]);
175 else oneStep.push([0, 1]);
176 if (y <= 1) multiStep.push([-1, 0]);
177 else oneStep.push([-1, 0]);
178 if (x >= 5) multiStep.push([0, -1]);
179 else oneStep.push([0, -1]);
180 if (y >= 5) multiStep.push([1, 0]);
181 else oneStep.push([1, 0]);
182 const c = this.turn;
183 let moves = super.getSlideNJumpMoves([x, y], oneStep, 1);
184 for (let step of multiStep) {
185 let [i, j] = [x + step[0], y + step[1]];
186 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
187 moves.push(this.getBasicMove([x, y], [i, j]));
188 i += step[0];
189 j += step[1];
190 }
191 if (V.OnBoard(i, j)) {
192 if (this.getColor(i, j) != c)
193 moves.push(this.getBasicMove([x, y], [i, j]));
194 }
195 else {
196 i -= step[0];
197 j -= step[1];
198 // Potential rebound if away from initial square
199 if (i != x || j != y) {
200 // Corners check
201 let nextStep = null;
202 if (i == 0 && j == 0) nextStep = [0, 1];
203 else if (i == 0 && j == 6) nextStep = [1, 0];
204 else if (i == 6 && j == 6) nextStep = [0, -1];
205 else if (i == 6 && j == 0) nextStep = [-1, 0];
206 if (!!nextStep) {
207 i += nextStep[0];
208 j += nextStep[1];
209 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
210 moves.push(this.getBasicMove([x, y], [i, j]));
211 i += nextStep[0];
212 j += nextStep[1];
213 }
214 if (V.OnBoard(i, j) && this.getColor(i, j) != c)
215 moves.push(this.getBasicMove([x, y], [i, j]));
216 }
217 }
218 }
219 }
220 return moves;
221 }
222
223 static get DictBishopSteps() {
224 return {
225 "-1_-1": [-1, -1],
226 "-1_1": [-1, 1],
227 "1_-1": [1, -1],
228 "1_1": [1, 1]
229 };
230 }
231
232 getPotentialBishopMoves([x, y]) {
233 let multiStep = {};
234 if (x <= 1) {
235 multiStep["-1_1"] = [-1, 1];
236 multiStep["1_1"] = [1, 1];
237 }
238 if (y <= 1) {
239 multiStep["-1_-1"] = [-1, -1];
240 if (!multiStep["-1_1"]) multiStep["-1_1"] = [-1, 1];
241 }
242 if (x >= 5) {
243 multiStep["1_-1"] = [1, -1];
244 if (!multiStep["-1_-1"]) multiStep["-1_-1"] = [-1, -1];
245 }
246 if (y >= 5) {
247 if (!multiStep["1_-1"]) multiStep["1_-1"] = [1, -1];
248 if (!multiStep["1_1"]) multiStep["1_1"] = [1, 1];
249 }
250 let oneStep = [];
251 Object.keys(V.DictBishopSteps).forEach(str => {
252 if (!multiStep[str]) oneStep.push(V.DictBishopSteps[str]);
253 });
254 const c = this.turn;
255 let moves = super.getSlideNJumpMoves([x, y], oneStep, 1);
256 for (let step of Object.values(multiStep)) {
257 let [i, j] = [x + step[0], y + step[1]];
258 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
259 moves.push(this.getBasicMove([x, y], [i, j]));
260 i += step[0];
261 j += step[1];
262 }
263 if (V.OnBoard(i, j)) {
264 if (this.getColor(i, j) != c)
265 moves.push(this.getBasicMove([x, y], [i, j]));
266 }
267 else {
268 i -= step[0];
269 j -= step[1];
270 // Rebound, if we moved away from initial square
271 if (i != x || j != y) {
272 let nextStep = null;
273 if (step[0] == -1 && step[1] == -1) {
274 if (j == 0) nextStep = [-1, 1];
275 else nextStep = [1, -1];
276 }
277 else if (step[0] == -1 && step[1] == 1) {
278 if (i == 0) nextStep = [1, 1];
279 else nextStep = [-1, -1];
280 }
281 else if (step[0] == 1 && step[1] == -1) {
282 if (i == 6) nextStep = [-1, -1];
283 else nextStep = [1, 1];
284 }
285 else {
286 // step == [1, 1]
287 if (j == 6) nextStep = [1, -1];
288 else nextStep = [-1, 1];
289 }
290 i += nextStep[0];
291 j += nextStep[1];
292 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
293 moves.push(this.getBasicMove([x, y], [i, j]));
294 i += nextStep[0];
295 j += nextStep[1];
296 }
297 if (V.OnBoard(i, j) && this.getColor(i, j) != c)
298 moves.push(this.getBasicMove([x, y], [i, j]));
299 }
300 }
301 }
302 return moves;
303 }
304
305 isAttacked(sq, color) {
306 return (
307 super.isAttackedByKing(sq, color) ||
308 this.isAttackedByRook(sq, color) ||
309 this.isAttackedByBishop(sq, color) ||
310 this.isAttackedByPawn(sq, color)
311 );
312 }
313
314 isAttackedByPawn([x, y], color) {
315 // Determine zone, shifted according to pawn movement
316 let attackDir = "";
317 let forward = 0;
318 if (
319 ([1, 2, 3, 4].includes(x) && y <= 1) ||
320 (x == 5 && y == 0)
321 ) {
322 attackDir = "vertical";
323 forward = 1;
324 }
325 else if (
326 ([2, 3, 4, 5].includes(x) && [5, 6].includes(y)) ||
327 (x == 1 && y == 6)
328 ) {
329 attackDir = "vertical";
330 forward = -1;
331 }
332 else if (
333 (x <= 1 && [2, 3, 4, 5].includes(y)) ||
334 (x == 0 && y == 1)
335 ) {
336 attackDir = "horizontal";
337 forward = -1;
338 }
339 else if (
340 (x >= 5 && [1, 2, 3, 4].includes(y)) ||
341 (x == 6 && y == 5)
342 ) {
343 attackDir = "horizontal";
344 forward = 1;
345 }
346 if (forward != 0) {
347 const steps =
348 attackDir == "vertical"
349 ? [ [forward, -1], [forward, 0], [forward, 1] ]
350 : [ [-1, forward], [0, forward], [1, forward] ];
351 return (
352 super.isAttackedBySlideNJump([x, y], color, V.PAWN, steps, 1)
353 );
354 }
355 // In a corner: can be attacked by one square only
356 let step = null;
357 if (x == 0) {
358 if (y == 0) step = [1, 0];
359 else step = [0, -1];
360 }
361 else {
362 if (y == 0) step = [0, 1];
363 else step = [-1, 0];
364 }
365 return (
366 super.isAttackedBySlideNJump([x, y], color, V.PAWN, [step], 1)
367 );
368 }
369
370 isAttackedByRook([x, y], color) {
371 // "Reversing" the code of getPotentialRookMoves()
372 let multiStep = [],
373 oneStep = [];
374 if (x <= 1) multiStep.push([0, -1]);
375 else oneStep.push([0, -1]);
376 if (y <= 1) multiStep.push([1, 0]);
377 else oneStep.push([1, 0]);
378 if (x >= 5) multiStep.push([0, 1]);
379 else oneStep.push([0, 1]);
380 if (y >= 5) multiStep.push([-1, 0]);
381 else oneStep.push([-1, 0]);
382 if (
383 super.isAttackedBySlideNJump([x, y], color, V.ROOK, oneStep, 1)
384 ) {
385 return true;
386 }
387 for (let step of multiStep) {
388 let [i, j] = [x + step[0], y + step[1]];
389 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
390 i += step[0];
391 j += step[1];
392 }
393 if (V.OnBoard(i, j)) {
394 if (this.getColor(i, j) == color && this.getPiece(i, j) == V.ROOK)
395 return true;
396 }
397 else {
398 i -= step[0];
399 j -= step[1];
400 if (i != x || j != y) {
401 let nextStep = null;
402 if (i == 0 && j == 0) nextStep = [1, 0];
403 else if (i == 0 && j == 6) nextStep = [0, -1];
404 else if (i == 6 && j == 6) nextStep = [-1, 0];
405 else if (i == 6 && j == 0) nextStep = [0, 1];
406 if (!!nextStep) {
407 i += nextStep[0];
408 j += nextStep[1];
409 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
410 i += nextStep[0];
411 j += nextStep[1];
412 }
413 if (
414 V.OnBoard(i, j) &&
415 this.getColor(i, j) == color &&
416 this.getPiece(i, j) == V.ROOK
417 ) {
418 return true;
419 }
420 }
421 }
422 }
423 }
424 return false;
425 }
426
427 isAttackedByBishop([x, y], color) {
428 // "Reversing" the code of getPotentiaBishopMoves()
429 let multiStep = {};
430 if (x <= 1) {
431 multiStep["1_-1"] = [1, -1];
432 multiStep["-1_-1"] = [-1, -1];
433 }
434 if (y <= 1) {
435 multiStep["1_1"] = [1, 1];
436 if (!multiStep["1_-1"]) multiStep["1_-1"] = [1, -1];
437 }
438 if (x >= 5) {
439 multiStep["-1_1"] = [-1, 1];
440 if (!multiStep["1_1"]) multiStep["1_1"] = [1, 1];
441 }
442 if (y >= 5) {
443 if (!multiStep["-1_-1"]) multiStep["-1_-1"] = [-1, -1];
444 if (!multiStep["-1_1"]) multiStep["-1_1"] = [-1, 1];
445 }
446 let oneStep = [];
447 Object.keys(V.DictBishopSteps).forEach(str => {
448 if (!multiStep[str]) oneStep.push(V.DictBishopSteps[str]);
449 });
450 if (
451 super.isAttackedBySlideNJump([x, y], color, V.BISHOP, oneStep, 1)
452 ) {
453 return true;
454 }
455 for (let step of Object.values(multiStep)) {
456 let [i, j] = [x + step[0], y + step[1]];
457 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
458 i += step[0];
459 j += step[1];
460 }
461 if (V.OnBoard(i, j)) {
462 if (this.getColor(i, j) == color && this.getPiece(i, j) == V.BISHOP)
463 return true;
464 }
465 else {
466 i -= step[0];
467 j -= step[1];
468 if (i != x || j != y) {
469 let nextStep = null;
470 if (step[0] == -1 && step[1] == -1) {
471 if (j == 0) nextStep = [-1, 1];
472 else nextStep = [1, -1];
473 }
474 else if (step[0] == -1 && step[1] == 1) {
475 if (i == 0) nextStep = [1, 1];
476 else nextStep = [-1, -1];
477 }
478 else if (step[0] == 1 && step[1] == -1) {
479 if (i == 6) nextStep = [-1, -1];
480 else nextStep = [1, 1];
481 }
482 else {
483 // step == [1, 1]
484 if (j == 6) nextStep = [1, -1];
485 else nextStep = [-1, 1];
486 }
487 i += nextStep[0];
488 j += nextStep[1];
489 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
490 i += nextStep[0];
491 j += nextStep[1];
492 }
493 if (
494 V.OnBoard(i, j) &&
495 this.getColor(i, j) == color &&
496 this.getPiece(i, j) == V.BISHOP
497 ) {
498 return true;
499 }
500 }
501 }
502 }
503 return false;
504 }
505
506 // The board is divided in areas determined by "distance to target"
507 // A zone n+1 must be reached from a zone n.
508 getKingZone([x, y], color) {
509 if (color == 'w') {
510 if (y >= 4) return -1; //"out of zone"
511 if (y == 3 && [5, 6].includes(x)) return 0;
512 if (x == 6) return 1;
513 if (x == 5) return 2;
514 if (x == 4) return 3;
515 if (x == 3 || y == 0) return 4;
516 if (y == 1) return 5;
517 if (x == 0 || y == 2) return 6;
518 return 7; //x == 1 && y == 3
519 }
520 // color == 'b':
521 if (y <= 2) return -1; //"out of zone"
522 if (y == 3 && [0, 1].includes(x)) return 0;
523 if (x == 0) return 1;
524 if (x == 1) return 2;
525 if (x == 2) return 3;
526 if (x == 3 || y == 6) return 4;
527 if (y == 5) return 5;
528 if (x == 6 || y == 4) return 6;
529 return 7; //x == 5 && y == 3
530 }
531
532 postPlay(move) {
533 super.postPlay(move);
534 if (move.vanish[0].p == V.KING) {
535 const c = move.vanish[0].c;
536 const z1 = this.getKingZone([move.vanish[0].x, move.vanish[0].y], c),
537 z2 = this.getKingZone([move.appear[0].x, move.appear[0].y], c);
538 if (
539 z1 >= 0 && z2 >= 0 && z1 < z2 &&
540 // There exist "zone jumps" (0 to 2 for example),
541 // so the following test "flag >= z1" is required.
542 this.kingFlags[c] >= z1 && this.kingFlags[c] < z2
543 ) {
544 this.kingFlags[c] = z2;
545 }
546 }
547 }
548
549 getCurrentScore() {
550 const oppCol = V.GetOppCol(this.turn);
551 if (this.kingFlags[oppCol] == 7) return (oppCol == 'w' ? "1-0" : "0-1");
552 return super.getCurrentScore();
553 }
554
555 static get SEARCH_DEPTH() {
556 return 4;
557 }
558
559 evalPosition() {
560 let evaluation = 0;
561 for (let i = 0; i < V.size.x; i++) {
562 for (let j = 0; j < V.size.y; j++) {
563 if (this.board[i][j] != V.EMPTY) {
564 const sign = this.getColor(i, j) == "w" ? 1 : -1;
565 const piece = this.getPiece(i, j);
566 if (piece != 'x') evaluation += sign * V.VALUES[piece];
567 }
568 }
569 }
570 // Taking flags into account in a rather naive way
571 return evaluation + this.kingFlags['w'] - this.kingFlags['b'];
572 }
573
574 };