1 import { ChessRules
} from "@/base_rules";
3 export class RollerballRules
extends ChessRules
{
5 static get HasEnpassant() {
9 static get HasCastle() {
13 static get DarkBottomRight() {
18 return [V
.PAWN
, V
.KING
, V
.ROOK
, V
.BISHOP
];
22 return { x: 7, y: 7 };
25 // TODO: the wall position should be checked too
26 static IsGoodPosition(position
) {
27 if (position
.length
== 0) return false;
28 const rows
= position
.split("/");
29 if (rows
.length
!= V
.size
.x
) return false;
30 let kings
= { "k": 0, "K": 0 };
31 for (let row
of rows
) {
33 for (let i
= 0; i
< row
.length
; i
++) {
34 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
35 if (['x'].concat(V
.PIECES
).includes(row
[i
].toLowerCase())) sumElts
++;
37 const num
= parseInt(row
[i
], 10);
38 if (isNaN(num
)) return false;
42 if (sumElts
!= V
.size
.y
) return false;
44 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
48 // NOTE: canTake() is wrong, but next method is enough
49 static OnBoard(x
, y
) {
51 (x
>= 0 && x
<= 6 && y
>= 0 && y
<= 6) &&
52 (![2, 3, 4].includes(x
) || ![2, 3, 4].includes(y
))
56 static IsGoodFlags(flags
) {
57 // 2 for kings: last zone reached
58 return !!flags
.match(/^[0-7]{2,2}$/);
63 w: parseInt(fenflags
.charAt(0), 10),
64 b: parseInt(fenflags
.charAt(1), 10)
69 return this.kingFlags
;
72 disaggregateFlags(flags
) {
73 this.kingFlags
= flags
;
77 return this.kingFlags
['w'].toString() + this.kingFlags
['b'].toString();
80 // For space in the middle:
81 static get NOTHING() {
86 if (b
[0] == 'x') return 'x';
87 return ChessRules
.board2fen(b
);
91 if (f
== 'x') return V
.NOTHING
;
92 return ChessRules
.fen2board(f
);
96 if (b
[0] == 'x') return "Omega/nothing";
100 static GenRandInitFen() {
101 return "2rbp2/2rkp2/2xxx2/2xxx2/2xxx2/2PKR2/2PBR2 w 0 00";
104 getPotentialMovesFrom(sq
) {
105 switch (this.getPiece(sq
[0], sq
[1])) {
106 case V
.PAWN: return this.getPotentialPawnMoves(sq
);
107 case V
.ROOK: return this.getPotentialRookMoves(sq
);
108 case V
.BISHOP: return this.getPotentialBishopMoves(sq
);
109 case V
.KING: return super.getPotentialKingMoves(sq
);
114 getPotentialPawnMoves([x
, y
]) {
116 // Need to know pawn area to deduce move options
117 const inMiddleX
= [2, 3, 4].includes(x
);
118 const inMiddleY
= [2, 3, 4].includes(y
);
119 // In rectangular areas on the sides?
121 const forward
= (y
<= 1 ? -1 : 1);
123 super.getSlideNJumpMoves(
124 [x
, y
], [[forward
, -1], [forward
, 0], [forward
, 1]], "oneStep")
128 const forward
= (x
<= 1 ? 1 : -1);
130 super.getSlideNJumpMoves(
131 [x
, y
], [[-1, forward
], [0, forward
], [1, forward
]], "oneStep");
132 // Promotions may happen:
136 (c
== 'w' && x
<= 1 && m
.end
.y
== 4) ||
137 (c
== 'b' && x
>= 5 && m
.end
.y
== 2)
139 m
.appear
[0].p
= V
.ROOK
;
140 let m2
= JSON
.parse(JSON
.stringify(m
));
141 m2
.appear
[0].p
= V
.BISHOP
;
145 Array
.prototype.push
.apply(moves
, extraMoves
);
149 const toRight
= (x
== 0 && [0, 1, 5].includes(y
)) || (x
== 1 && y
== 1);
150 const toLeft
= (x
== 6 && [1, 5, 6].includes(y
)) || (x
== 5 && y
== 5);
151 const toUp
= (y
== 0 && [1, 5, 6].includes(x
)) || (x
== 5 && y
== 1);
152 const toBottom
= (y
== 6 && [0, 1, 5].includes(x
)) || (x
== 1 && y
== 5);
153 if (toRight
|| toLeft
) {
154 const forward
= (toRight
? 1 : -1);
156 super.getSlideNJumpMoves(
157 [x
, y
], [[-1, forward
], [0, forward
], [1, forward
]], "oneStep")
160 const forward
= (toUp
? -1 : 1);
162 super.getSlideNJumpMoves(
163 [x
, y
], [[forward
, -1], [forward
, 0], [forward
, 1]], "oneStep")
167 getPotentialRookMoves([x
, y
]) {
170 if (x
<= 1) multiStep
.push([0, 1]);
171 else oneStep
.push([0, 1]);
172 if (y
<= 1) multiStep
.push([-1, 0]);
173 else oneStep
.push([-1, 0]);
174 if (x
>= 5) multiStep
.push([0, -1]);
175 else oneStep
.push([0, -1]);
176 if (y
>= 5) multiStep
.push([1, 0]);
177 else oneStep
.push([1, 0]);
179 let moves
= super.getSlideNJumpMoves([x
, y
], oneStep
, "oneStep");
180 for (let step
of multiStep
) {
181 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
182 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
183 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
187 if (V
.OnBoard(i
, j
)) {
188 if (this.getColor(i
, j
) != c
)
189 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
194 // Potential rebound if away from initial square
195 if (i
!= x
|| j
!= y
) {
198 if (i
== 0 && j
== 0) nextStep
= [0, 1];
199 else if (i
== 0 && j
== 6) nextStep
= [1, 0];
200 else if (i
== 6 && j
== 6) nextStep
= [0, -1];
201 else if (i
== 6 && j
== 0) nextStep
= [-1, 0];
205 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
206 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
210 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) != c
)
211 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
219 static get DictBishopSteps() {
228 getPotentialBishopMoves([x
, y
]) {
231 multiStep
["-1_1"] = [-1, 1];
232 multiStep
["1_1"] = [1, 1];
235 multiStep
["-1_-1"] = [-1, -1];
236 if (!multiStep
["-1_1"]) multiStep
["-1_1"] = [-1, 1];
239 multiStep
["1_-1"] = [1, -1];
240 if (!multiStep
["-1_-1"]) multiStep
["-1_-1"] = [-1, -1];
243 if (!multiStep
["1_-1"]) multiStep
["1_-1"] = [1, -1];
244 if (!multiStep
["1_1"]) multiStep
["1_1"] = [1, 1];
247 Object
.keys(V
.DictBishopSteps
).forEach(str
=> {
248 if (!multiStep
[str
]) oneStep
.push(V
.DictBishopSteps
[str
]);
251 let moves
= super.getSlideNJumpMoves([x
, y
], oneStep
, "oneStep");
252 for (let step
of Object
.values(multiStep
)) {
253 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
254 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
255 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
259 if (V
.OnBoard(i
, j
)) {
260 if (this.getColor(i
, j
) != c
)
261 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
266 // Rebound, if we moved away from initial square
267 if (i
!= x
|| j
!= y
) {
269 if (step
[0] == -1 && step
[1] == -1) {
270 if (j
== 0) nextStep
= [-1, 1];
271 else nextStep
= [1, -1];
273 else if (step
[0] == -1 && step
[1] == 1) {
274 if (i
== 0) nextStep
= [1, 1];
275 else nextStep
= [-1, -1];
277 else if (step
[0] == 1 && step
[1] == -1) {
278 if (i
== 6) nextStep
= [-1, -1];
279 else nextStep
= [1, 1];
283 if (j
== 6) nextStep
= [1, -1];
284 else nextStep
= [-1, 1];
288 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
289 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
293 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) != c
)
294 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
301 isAttacked(sq
, color
) {
303 super.isAttackedByKing(sq
, color
) ||
304 this.isAttackedByRook(sq
, color
) ||
305 this.isAttackedByBishop(sq
, color
) ||
306 this.isAttackedByPawn(sq
, color
)
310 isAttackedByPawn([x
, y
], color
) {
311 // Determine zone, shifted according to pawn movement
315 ([1, 2, 3, 4].includes(x
) && y
<= 1) ||
318 attackDir
= "vertical";
322 ([2, 3, 4, 5].includes(x
) && [5, 6].includes(y
)) ||
325 attackDir
= "vertical";
329 (x
<= 1 && [2, 3, 4, 5].includes(y
)) ||
332 attackDir
= "horizontal";
336 (x
>= 5 && [1, 2, 3, 4].includes(y
)) ||
339 attackDir
= "horizontal";
344 attackDir
== "vertical"
345 ? [ [forward
, -1], [forward
, 0], [forward
, 1] ]
346 : [ [-1, forward
], [0, forward
], [1, forward
] ];
348 super.isAttackedBySlideNJump([x
, y
], color
, V
.PAWN
, steps
, "oneStep")
351 // In a corner: can be attacked by one square only
354 if (y
== 0) step
= [1, 0];
358 if (y
== 0) step
= [0, 1];
362 super.isAttackedBySlideNJump([x
, y
], color
, V
.PAWN
, [step
], "oneStep")
366 isAttackedByRook([x
, y
], color
) {
367 // "Reversing" the code of getPotentialRookMoves()
370 if (x
<= 1) multiStep
.push([0, -1]);
371 else oneStep
.push([0, -1]);
372 if (y
<= 1) multiStep
.push([1, 0]);
373 else oneStep
.push([1, 0]);
374 if (x
>= 5) multiStep
.push([0, 1]);
375 else oneStep
.push([0, 1]);
376 if (y
>= 5) multiStep
.push([-1, 0]);
377 else oneStep
.push([-1, 0]);
379 super.isAttackedBySlideNJump([x
, y
], color
, V
.ROOK
, oneStep
, "oneStep")
383 for (let step
of multiStep
) {
384 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
385 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
389 if (V
.OnBoard(i
, j
)) {
390 if (this.getColor(i
, j
) == color
&& this.getPiece(i
, j
) == V
.ROOK
)
396 if (i
!= x
|| j
!= y
) {
398 if (i
== 0 && j
== 0) nextStep
= [1, 0];
399 else if (i
== 0 && j
== 6) nextStep
= [0, -1];
400 else if (i
== 6 && j
== 6) nextStep
= [-1, 0];
401 else if (i
== 6 && j
== 0) nextStep
= [0, 1];
405 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
411 this.getColor(i
, j
) == color
&&
412 this.getPiece(i
, j
) == V
.ROOK
423 isAttackedByBishop([x
, y
], color
) {
424 // "Reversing" the code of getPotentiaBishopMoves()
427 multiStep
["1_-1"] = [1, -1];
428 multiStep
["-1_-1"] = [-1, -1];
431 multiStep
["1_1"] = [1, 1];
432 if (!multiStep
["1_-1"]) multiStep
["1_-1"] = [1, -1];
435 multiStep
["-1_1"] = [-1, 1];
436 if (!multiStep
["1_1"]) multiStep
["1_1"] = [1, 1];
439 if (!multiStep
["-1_-1"]) multiStep
["-1_-1"] = [-1, -1];
440 if (!multiStep
["-1_1"]) multiStep
["-1_1"] = [-1, 1];
443 Object
.keys(V
.DictBishopSteps
).forEach(str
=> {
444 if (!multiStep
[str
]) oneStep
.push(V
.DictBishopSteps
[str
]);
447 super.isAttackedBySlideNJump([x
, y
], color
, V
.BISHOP
, oneStep
, "oneStep")
451 for (let step
of Object
.values(multiStep
)) {
452 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
453 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
457 if (V
.OnBoard(i
, j
)) {
458 if (this.getColor(i
, j
) == color
&& this.getPiece(i
, j
) == V
.BISHOP
)
464 if (i
!= x
|| j
!= y
) {
466 if (step
[0] == -1 && step
[1] == -1) {
467 if (j
== 0) nextStep
= [-1, 1];
468 else nextStep
= [1, -1];
470 else if (step
[0] == -1 && step
[1] == 1) {
471 if (i
== 0) nextStep
= [1, 1];
472 else nextStep
= [-1, -1];
474 else if (step
[0] == 1 && step
[1] == -1) {
475 if (i
== 6) nextStep
= [-1, -1];
476 else nextStep
= [1, 1];
480 if (j
== 6) nextStep
= [1, -1];
481 else nextStep
= [-1, 1];
485 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
491 this.getColor(i
, j
) == color
&&
492 this.getPiece(i
, j
) == V
.BISHOP
502 // The board is divided in areas determined by "distance to target"
503 // A zone n+1 must be reached from a zone n.
504 getKingZone([x
, y
], color
) {
506 if (y
>= 4) return -1; //"out of zone"
507 if (y
== 3 && [5, 6].includes(x
)) return 0;
508 if (x
== 6) return 1;
509 if (x
== 5) return 2;
510 if (x
== 4) return 3;
511 if (x
== 3 || y
== 0) return 4;
512 if (y
== 1) return 5;
513 if (x
== 0 || y
== 2) return 6;
514 return 7; //x == 1 && y == 3
517 if (y
<= 2) return -1; //"out of zone"
518 if (y
== 3 && [0, 1].includes(x
)) return 0;
519 if (x
== 0) return 1;
520 if (x
== 1) return 2;
521 if (x
== 2) return 3;
522 if (x
== 3 || y
== 6) return 4;
523 if (y
== 5) return 5;
524 if (x
== 6 || y
== 4) return 6;
525 return 7; //x == 5 && y == 3
529 super.postPlay(move);
530 if (move.vanish
[0].p
== V
.KING
) {
531 const c
= move.vanish
[0].c
;
532 const z1
= this.getKingZone([move.vanish
[0].x
, move.vanish
[0].y
], c
),
533 z2
= this.getKingZone([move.appear
[0].x
, move.appear
[0].y
], c
);
535 z1
>= 0 && z2
>= 0 && z1
< z2
&&
536 // There exist "zone jumps" (0 to 2 for example),
537 // so the following test "flag >= z1" is required.
538 this.kingFlags
[c
] >= z1
&& this.kingFlags
[c
] < z2
540 this.kingFlags
[c
] = z2
;
546 const oppCol
= V
.GetOppCol(this.turn
);
547 if (this.kingFlags
[oppCol
] == 7) return (oppCol
== 'w' ? "1-0" : "0-1");
548 return super.getCurrentScore();
551 static get SEARCH_DEPTH() {
557 for (let i
= 0; i
< V
.size
.x
; i
++) {
558 for (let j
= 0; j
< V
.size
.y
; j
++) {
559 if (this.board
[i
][j
] != V
.EMPTY
) {
560 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
561 const piece
= this.getPiece(i
, j
);
562 if (piece
!= 'x') evaluation
+= sign
* V
.VALUES
[piece
];
566 // Taking flags into account in a rather naive way
567 return evaluation
+ this.kingFlags
['w'] - this.kingFlags
['b'];