1 import { ChessRules
} from "@/base_rules";
3 export class WormholeRules
extends ChessRules
{
5 static get HasFlags() {
9 static get HasEnpassant() {
18 if (b
[0] == 'x') return 'x';
19 return ChessRules
.board2fen(b
);
23 if (f
== 'x') return V
.HOLE
;
24 return ChessRules
.fen2board(f
);
28 if (b
[0] == 'x') return "Wormhole/hole";
32 static IsGoodPosition(position
) {
33 if (position
.length
== 0) return false;
34 const rows
= position
.split("/");
35 if (rows
.length
!= V
.size
.x
) return false;
36 let kings
= { "k": 0, "K": 0 };
37 for (let row
of rows
) {
39 for (let i
= 0; i
< row
.length
; i
++) {
40 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
41 if (['x'].concat(V
.PIECES
).includes(row
[i
].toLowerCase())) sumElts
++;
43 const num
= parseInt(row
[i
], 10);
44 if (isNaN(num
)) return false;
48 if (sumElts
!= V
.size
.y
) return false;
50 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
54 getSquareAfter(square
, movement
) {
56 if (Array
.isArray(movement
[0])) {
65 const tryMove
= (init
, shift
) => {
67 shift
[0] / Math
.abs(shift
[0]) || 0,
68 shift
[1] / Math
.abs(shift
[1]) || 0,
70 const nbSteps
= Math
.max(Math
.abs(shift
[0]), Math
.abs(shift
[1]));
71 let stepsAchieved
= 0;
72 let sq
= [init
[0] + step
[0], init
[1] + step
[1]];
73 while (V
.OnBoard(sq
[0],sq
[1])) {
74 if (this.board
[sq
[0]][sq
[1]] != V
.HOLE
)
76 if (stepsAchieved
< nbSteps
) {
82 if (stepsAchieved
< nbSteps
)
83 // The move is impossible
87 // First, apply shift1
88 let dest
= tryMove(square
, shift1
);
90 // A knight: apply second shift
91 dest
= tryMove(dest
, shift2
);
95 // NOTE (TODO?): some extra work done in some function because informations
96 // on one step should ease the computation for a step in the same direction.
109 // Decompose knight movements into one step orthogonal + one diagonal
143 getJumpMoves([x
, y
], steps
) {
145 for (let step
of steps
) {
146 const sq
= this.getSquareAfter([x
,y
], step
);
149 this.board
[sq
[0]][sq
[1]] == V
.EMPTY
||
150 this.canTake([x
, y
], sq
)
153 moves
.push(this.getBasicMove([x
, y
], sq
));
159 // What are the pawn moves from square x,y ?
160 getPotentialPawnMoves([x
, y
]) {
161 const color
= this.turn
;
163 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
164 const shiftX
= color
== "w" ? -1 : 1;
165 const startRank
= color
== "w" ? sizeX
- 2 : 1;
166 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
168 const sq1
= this.getSquareAfter([x
,y
], [shiftX
,0]);
169 if (sq1
&& this.board
[sq1
[0]][y
] == V
.EMPTY
) {
170 // One square forward (cannot be a promotion)
171 moves
.push(this.getBasicMove([x
, y
], [sq1
[0], y
]));
172 if (x
== startRank
) {
173 // If two squares after is available, then move is possible
174 const sq2
= this.getSquareAfter([x
,y
], [2*shiftX
,0]);
175 if (sq2
&& this.board
[sq2
[0]][y
] == V
.EMPTY
)
177 moves
.push(this.getBasicMove([x
, y
], [sq2
[0], y
]));
181 for (let shiftY
of [-1, 1]) {
182 const sq
= this.getSquareAfter([x
,y
], [shiftX
,shiftY
]);
185 this.board
[sq
[0]][sq
[1]] != V
.EMPTY
&&
186 this.canTake([x
, y
], [sq
[0], sq
[1]])
188 const finalPieces
= sq
[0] == lastRank
189 ? [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]
191 for (let piece
of finalPieces
) {
193 this.getBasicMove([x
, y
], [sq
[0], sq
[1]], {
205 getPotentialRookMoves(sq
) {
206 return this.getJumpMoves(sq
, V
.steps
[V
.ROOK
]);
209 getPotentialKnightMoves(sq
) {
210 return this.getJumpMoves(sq
, V
.steps
[V
.KNIGHT
]);
213 getPotentialBishopMoves(sq
) {
214 return this.getJumpMoves(sq
, V
.steps
[V
.BISHOP
]);
217 getPotentialQueenMoves(sq
) {
218 return this.getJumpMoves(
220 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
224 getPotentialKingMoves(sq
) {
225 return this.getJumpMoves(sq
, V
.steps
[V
.KING
]);
228 isAttackedByJump([x
, y
], color
, piece
, steps
) {
229 for (let step
of steps
) {
230 const sq
= this.getSquareAfter([x
,y
], step
);
233 this.getPiece(sq
[0], sq
[1]) == piece
&&
234 this.getColor(sq
[0], sq
[1]) == color
242 isAttackedByPawn([x
, y
], color
) {
243 const pawnShift
= (color
== "w" ? 1 : -1);
244 for (let i
of [-1, 1]) {
245 const sq
= this.getSquareAfter([x
,y
], [pawnShift
,i
]);
248 this.getPiece(sq
[0], sq
[1]) == V
.PAWN
&&
249 this.getColor(sq
[0], sq
[1]) == color
257 isAttackedByRook(sq
, color
) {
258 return this.isAttackedByJump(sq
, color
, V
.ROOK
, V
.steps
[V
.ROOK
]);
261 isAttackedByKnight(sq
, color
) {
262 // NOTE: knight attack is not symmetric in this variant:
263 // steps order need to be reversed.
264 return this.isAttackedByJump(
268 V
.steps
[V
.KNIGHT
].map(s
=> s
.reverse())
272 isAttackedByBishop(sq
, color
) {
273 return this.isAttackedByJump(sq
, color
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
276 isAttackedByQueen(sq
, color
) {
277 return this.isAttackedByJump(
281 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
285 isAttackedByKing(sq
, color
) {
286 return this.isAttackedByJump(sq
, color
, V
.KING
, V
.steps
[V
.KING
]);
289 // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical.
290 // This is a side-effect on board generated by the move.
291 static PlayOnBoard(board
, move) {
292 board
[move.vanish
[0].x
][move.vanish
[0].y
] = V
.HOLE
;
293 for (let psq
of move.appear
) board
[psq
.x
][psq
.y
] = psq
.c
+ psq
.p
;
297 if (this.atLeastOneMove()) return "*";
298 // No valid move: I lose
299 return this.turn
== "w" ? "0-1" : "1-0";
302 static get SEARCH_DEPTH() {
308 for (let i
= 0; i
< V
.size
.x
; i
++) {
309 for (let j
= 0; j
< V
.size
.y
; j
++) {
310 if (![V
.EMPTY
,V
.HOLE
].includes(this.board
[i
][j
])) {
311 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
312 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
320 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
321 // Indicate start square + dest square, because holes distort the board
323 (piece
!= V
.PAWN
? piece
.toUpperCase() : "") +
324 V
.CoordsToSquare(move.start
) +
325 (move.vanish
.length
> move.appear
.length
? "x" : "") +
326 V
.CoordsToSquare(move.end
);
327 if (piece
== V
.PAWN
&& move.appear
[0].p
!= V
.PAWN
)
329 notation
+= "=" + move.appear
[0].p
.toUpperCase();