1 import { ChessRules
} from "@/base_rules";
3 export class RollerballRules
extends ChessRules
{
9 static get HasEnpassant() {
13 static get HasCastle() {
17 static get DarkBottomRight() {
22 return [V
.PAWN
, V
.KING
, V
.ROOK
, V
.BISHOP
];
26 return { x: 7, y: 7 };
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
) {
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
++;
41 const num
= parseInt(row
[i
], 10);
42 if (isNaN(num
)) return false;
46 if (sumElts
!= V
.size
.y
) return false;
48 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
52 // NOTE: canTake() is wrong, but next method is enough
53 static OnBoard(x
, y
) {
55 (x
>= 0 && x
<= 6 && y
>= 0 && y
<= 6) &&
56 (![2, 3, 4].includes(x
) || ![2, 3, 4].includes(y
))
60 static IsGoodFlags(flags
) {
61 // 2 for kings: last zone reached
62 return !!flags
.match(/^[0-7]{2,2}$/);
67 w: parseInt(fenflags
.charAt(0), 10),
68 b: parseInt(fenflags
.charAt(1), 10)
73 return this.kingFlags
;
76 disaggregateFlags(flags
) {
77 this.kingFlags
= flags
;
81 return this.kingFlags
['w'].toString() + this.kingFlags
['b'].toString();
84 // For space in the middle:
85 static get NOTHING() {
90 if (b
[0] == 'x') return 'x';
91 return ChessRules
.board2fen(b
);
95 if (f
== 'x') return V
.NOTHING
;
96 return ChessRules
.fen2board(f
);
100 if (b
[0] == 'x') return "Omega/nothing";
104 static GenRandInitFen() {
105 return "2rbp2/2rkp2/2xxx2/2xxx2/2xxx2/2PKR2/2PBR2 w 0 00";
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
);
118 getPotentialPawnMoves([x
, y
]) {
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?
125 const forward
= (y
<= 1 ? -1 : 1);
127 super.getSlideNJumpMoves(
128 [x
, y
], [[forward
, -1], [forward
, 0], [forward
, 1]], 1)
132 const forward
= (x
<= 1 ? 1 : -1);
134 super.getSlideNJumpMoves(
135 [x
, y
], [[-1, forward
], [0, forward
], [1, forward
]], 1);
136 // Promotions may happen:
140 (c
== 'w' && x
<= 1 && m
.end
.y
== 4) ||
141 (c
== 'b' && x
>= 5 && m
.end
.y
== 2)
143 m
.appear
[0].p
= V
.ROOK
;
144 let m2
= JSON
.parse(JSON
.stringify(m
));
145 m2
.appear
[0].p
= V
.BISHOP
;
149 Array
.prototype.push
.apply(moves
, extraMoves
);
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);
160 super.getSlideNJumpMoves(
161 [x
, y
], [[-1, forward
], [0, forward
], [1, forward
]], 1)
164 const forward
= (toUp
? -1 : 1);
166 super.getSlideNJumpMoves(
167 [x
, y
], [[forward
, -1], [forward
, 0], [forward
, 1]], 1)
171 getPotentialRookMoves([x
, y
]) {
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]);
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
]));
191 if (V
.OnBoard(i
, j
)) {
192 if (this.getColor(i
, j
) != c
)
193 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
198 // Potential rebound if away from initial square
199 if (i
!= x
|| j
!= y
) {
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];
209 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
210 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
214 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) != c
)
215 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
223 static get DictBishopSteps() {
232 getPotentialBishopMoves([x
, y
]) {
235 multiStep
["-1_1"] = [-1, 1];
236 multiStep
["1_1"] = [1, 1];
239 multiStep
["-1_-1"] = [-1, -1];
240 if (!multiStep
["-1_1"]) multiStep
["-1_1"] = [-1, 1];
243 multiStep
["1_-1"] = [1, -1];
244 if (!multiStep
["-1_-1"]) multiStep
["-1_-1"] = [-1, -1];
247 if (!multiStep
["1_-1"]) multiStep
["1_-1"] = [1, -1];
248 if (!multiStep
["1_1"]) multiStep
["1_1"] = [1, 1];
251 Object
.keys(V
.DictBishopSteps
).forEach(str
=> {
252 if (!multiStep
[str
]) oneStep
.push(V
.DictBishopSteps
[str
]);
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
]));
263 if (V
.OnBoard(i
, j
)) {
264 if (this.getColor(i
, j
) != c
)
265 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
270 // Rebound, if we moved away from initial square
271 if (i
!= x
|| j
!= y
) {
273 if (step
[0] == -1 && step
[1] == -1) {
274 if (j
== 0) nextStep
= [-1, 1];
275 else nextStep
= [1, -1];
277 else if (step
[0] == -1 && step
[1] == 1) {
278 if (i
== 0) nextStep
= [1, 1];
279 else nextStep
= [-1, -1];
281 else if (step
[0] == 1 && step
[1] == -1) {
282 if (i
== 6) nextStep
= [-1, -1];
283 else nextStep
= [1, 1];
287 if (j
== 6) nextStep
= [1, -1];
288 else nextStep
= [-1, 1];
292 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
293 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
297 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) != c
)
298 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
305 isAttacked(sq
, color
) {
307 super.isAttackedByKing(sq
, color
) ||
308 this.isAttackedByRook(sq
, color
) ||
309 this.isAttackedByBishop(sq
, color
) ||
310 this.isAttackedByPawn(sq
, color
)
314 isAttackedByPawn([x
, y
], color
) {
315 // Determine zone, shifted according to pawn movement
319 ([1, 2, 3, 4].includes(x
) && y
<= 1) ||
322 attackDir
= "vertical";
326 ([2, 3, 4, 5].includes(x
) && [5, 6].includes(y
)) ||
329 attackDir
= "vertical";
333 (x
<= 1 && [2, 3, 4, 5].includes(y
)) ||
336 attackDir
= "horizontal";
340 (x
>= 5 && [1, 2, 3, 4].includes(y
)) ||
343 attackDir
= "horizontal";
348 attackDir
== "vertical"
349 ? [ [forward
, -1], [forward
, 0], [forward
, 1] ]
350 : [ [-1, forward
], [0, forward
], [1, forward
] ];
352 super.isAttackedBySlideNJump([x
, y
], color
, V
.PAWN
, steps
, 1)
355 // In a corner: can be attacked by one square only
358 if (y
== 0) step
= [1, 0];
362 if (y
== 0) step
= [0, 1];
366 super.isAttackedBySlideNJump([x
, y
], color
, V
.PAWN
, [step
], 1)
370 isAttackedByRook([x
, y
], color
) {
371 // "Reversing" the code of getPotentialRookMoves()
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]);
383 super.isAttackedBySlideNJump([x
, y
], color
, V
.ROOK
, oneStep
, 1)
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
) {
393 if (V
.OnBoard(i
, j
)) {
394 if (this.getColor(i
, j
) == color
&& this.getPiece(i
, j
) == V
.ROOK
)
400 if (i
!= x
|| j
!= y
) {
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];
409 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
415 this.getColor(i
, j
) == color
&&
416 this.getPiece(i
, j
) == V
.ROOK
427 isAttackedByBishop([x
, y
], color
) {
428 // "Reversing" the code of getPotentiaBishopMoves()
431 multiStep
["1_-1"] = [1, -1];
432 multiStep
["-1_-1"] = [-1, -1];
435 multiStep
["1_1"] = [1, 1];
436 if (!multiStep
["1_-1"]) multiStep
["1_-1"] = [1, -1];
439 multiStep
["-1_1"] = [-1, 1];
440 if (!multiStep
["1_1"]) multiStep
["1_1"] = [1, 1];
443 if (!multiStep
["-1_-1"]) multiStep
["-1_-1"] = [-1, -1];
444 if (!multiStep
["-1_1"]) multiStep
["-1_1"] = [-1, 1];
447 Object
.keys(V
.DictBishopSteps
).forEach(str
=> {
448 if (!multiStep
[str
]) oneStep
.push(V
.DictBishopSteps
[str
]);
451 super.isAttackedBySlideNJump([x
, y
], color
, V
.BISHOP
, oneStep
, 1)
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
) {
461 if (V
.OnBoard(i
, j
)) {
462 if (this.getColor(i
, j
) == color
&& this.getPiece(i
, j
) == V
.BISHOP
)
468 if (i
!= x
|| j
!= y
) {
470 if (step
[0] == -1 && step
[1] == -1) {
471 if (j
== 0) nextStep
= [-1, 1];
472 else nextStep
= [1, -1];
474 else if (step
[0] == -1 && step
[1] == 1) {
475 if (i
== 0) nextStep
= [1, 1];
476 else nextStep
= [-1, -1];
478 else if (step
[0] == 1 && step
[1] == -1) {
479 if (i
== 6) nextStep
= [-1, -1];
480 else nextStep
= [1, 1];
484 if (j
== 6) nextStep
= [1, -1];
485 else nextStep
= [-1, 1];
489 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
495 this.getColor(i
, j
) == color
&&
496 this.getPiece(i
, j
) == V
.BISHOP
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
) {
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
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
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
);
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
544 this.kingFlags
[c
] = z2
;
550 const oppCol
= V
.GetOppCol(this.turn
);
551 if (this.kingFlags
[oppCol
] == 7) return (oppCol
== 'w' ? "1-0" : "0-1");
552 return super.getCurrentScore();
555 static get SEARCH_DEPTH() {
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
];
570 // Taking flags into account in a rather naive way
571 return evaluation
+ this.kingFlags
['w'] - this.kingFlags
['b'];