1 import { ChessRules
} from "@/base_rules";
3 export const VariantRules
= class WormholeRules
extends ChessRules
{
4 static get HasFlags() {
8 static get HasEnpassant() {
17 if (b
[0] == 'x') return 'x';
18 return ChessRules
.board2fen(b
);
22 if (f
== 'x') return V
.HOLE
;
23 return ChessRules
.fen2board(f
);
27 if (b
[0] == 'x') return "Wormhole/hole";
31 getSquareAfter(square
, movement
) {
33 if (Array
.isArray(movement
[0])) {
41 const tryMove
= (init
, shift
) => {
43 shift
[0] / Math
.abs(shift
[0]) || 0,
44 shift
[1] / Math
.abs(shift
[1]) || 0,
46 const nbSteps
= Math
.max(Math
.abs(shift
[0]), Math
.abs(shift
[1]));
47 let stepsAchieved
= 0;
48 let sq
= [init
[0] + step
[0], init
[1] + step
[1]];
49 while (V
.OnBoard(sq
[0],sq
[1])) {
50 if (this.board
[sq
[0]][sq
[1]] != V
.HOLE
)
52 if (stepsAchieved
< nbSteps
) {
58 if (stepsAchieved
< nbSteps
)
59 // The move is impossible
63 // First, apply shift1
64 let dest
= tryMove(square
, shift1
);
66 // A knight: apply second shift
67 dest
= tryMove(dest
, shift2
);
71 // NOTE (TODO?): some extra work done in some function because informations
72 // on one step should ease the computation for a step in the same direction.
85 // Decompose knight movements into one step orthogonal + one diagonal
119 getJumpMoves([x
, y
], steps
) {
121 for (let step
of steps
) {
122 const sq
= this.getSquareAfter([x
,y
], step
);
125 this.board
[sq
[0]][sq
[1]] == V
.EMPTY
||
126 this.canTake([x
, y
], sq
)
129 moves
.push(this.getBasicMove([x
, y
], sq
));
135 // What are the pawn moves from square x,y ?
136 getPotentialPawnMoves([x
, y
]) {
137 const color
= this.turn
;
139 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
140 const shiftX
= color
== "w" ? -1 : 1;
141 const startRank
= color
== "w" ? sizeX
- 2 : 1;
142 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
144 const sq1
= this.getSquareAfter([x
,y
], [shiftX
,0]);
145 if (sq1
&& this.board
[sq1
[0]][y
] == V
.EMPTY
) {
146 // One square forward (cannot be a promotion)
147 moves
.push(this.getBasicMove([x
, y
], [sq1
[0], y
]));
148 if (x
== startRank
) {
149 // If two squares after is available, then move is possible
150 const sq2
= this.getSquareAfter([x
,y
], [2*shiftX
,0]);
151 if (sq2
&& this.board
[sq2
[0]][y
] == V
.EMPTY
)
153 moves
.push(this.getBasicMove([x
, y
], [sq2
[0], y
]));
157 for (let shiftY
of [-1, 1]) {
158 const sq
= this.getSquareAfter([x
,y
], [shiftX
,shiftY
]);
161 this.board
[sq
[0]][sq
[1]] != V
.EMPTY
&&
162 this.canTake([x
, y
], [sq
[0], sq
[1]])
164 const finalPieces
= sq
[0] == lastRank
165 ? [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]
167 for (let piece
of finalPieces
) {
169 this.getBasicMove([x
, y
], [sq
[0], sq
[1]], {
181 getPotentialRookMoves(sq
) {
182 return this.getJumpMoves(sq
, V
.steps
[V
.ROOK
]);
185 getPotentialKnightMoves(sq
) {
186 return this.getJumpMoves(sq
, V
.steps
[V
.KNIGHT
]);
189 getPotentialBishopMoves(sq
) {
190 return this.getJumpMoves(sq
, V
.steps
[V
.BISHOP
]);
193 getPotentialQueenMoves(sq
) {
194 return this.getJumpMoves(
196 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
200 getPotentialKingMoves(sq
) {
201 return this.getJumpMoves(sq
, V
.steps
[V
.KING
]);
204 isAttackedByJump([x
, y
], colors
, piece
, steps
) {
205 for (let step
of steps
) {
206 const sq
= this.getSquareAfter([x
,y
], step
);
209 this.getPiece(sq
[0], sq
[1]) === piece
&&
210 colors
.includes(this.getColor(sq
[0], sq
[1]))
218 isAttackedByPawn([x
, y
], colors
) {
219 for (let c
of colors
) {
220 const pawnShift
= c
== "w" ? 1 : -1;
221 for (let i
of [-1, 1]) {
222 const sq
= this.getSquareAfter([x
,y
], [pawnShift
,i
]);
225 this.getPiece(sq
[0], sq
[1]) == V
.PAWN
&&
226 this.getColor(sq
[0], sq
[1]) == c
235 isAttackedByRook(sq
, colors
) {
236 return this.isAttackedByJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
239 isAttackedByKnight(sq
, colors
) {
240 // NOTE: knight attack is not symmetric in this variant:
241 // steps order need to be reversed.
242 return this.isAttackedByJump(
246 V
.steps
[V
.KNIGHT
].map(s
=> s
.reverse())
250 isAttackedByBishop(sq
, colors
) {
251 return this.isAttackedByJump(sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
254 isAttackedByQueen(sq
, colors
) {
255 return this.isAttackedByJump(
259 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
263 isAttackedByKing(sq
, colors
) {
264 return this.isAttackedByJump(sq
, colors
, V
.KING
, V
.steps
[V
.KING
]);
267 // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical.
268 // This is a side-effect on board generated by the move.
269 static PlayOnBoard(board
, move) {
270 board
[move.vanish
[0].x
][move.vanish
[0].y
] = V
.HOLE
;
271 for (let psq
of move.appear
) board
[psq
.x
][psq
.y
] = psq
.c
+ psq
.p
;
275 if (this.atLeastOneMove())
277 // No valid move: I lose
278 return this.turn
== "w" ? "0-1" : "1-0";
281 static get SEARCH_DEPTH() {
287 for (let i
= 0; i
< V
.size
.x
; i
++) {
288 for (let j
= 0; j
< V
.size
.y
; j
++) {
289 if (![V
.EMPTY
,V
.HOLE
].includes(this.board
[i
][j
])) {
290 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
291 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
299 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
300 // Indicate start square + dest square, because holes distort the board
302 (piece
!= V
.PAWN
? piece
.toUpperCase() : "") +
303 V
.CoordsToSquare(move.start
) +
304 (move.vanish
.length
> move.appear
.length
? "x" : "") +
305 V
.CoordsToSquare(move.end
);
306 if (piece
== V
.PAWN
&& move.appear
[0].p
!= V
.PAWN
)
308 notation
+= "=" + move.appear
[0].p
.toUpperCase();