1 import { ChessRules
} from "@/base_rules";
3 export 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 static IsGoodPosition(position
) {
32 if (position
.length
== 0) return false;
33 const rows
= position
.split("/");
34 if (rows
.length
!= V
.size
.x
) return false;
35 let kings
= { "k": 0, "K": 0 };
36 for (let row
of rows
) {
38 for (let i
= 0; i
< row
.length
; i
++) {
39 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
40 if (['x'].concat(V
.PIECES
).includes(row
[i
].toLowerCase())) sumElts
++;
42 const num
= parseInt(row
[i
]);
43 if (isNaN(num
)) return false;
47 if (sumElts
!= V
.size
.y
) return false;
49 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
53 getSquareAfter(square
, movement
) {
55 if (Array
.isArray(movement
[0])) {
63 const tryMove
= (init
, shift
) => {
65 shift
[0] / Math
.abs(shift
[0]) || 0,
66 shift
[1] / Math
.abs(shift
[1]) || 0,
68 const nbSteps
= Math
.max(Math
.abs(shift
[0]), Math
.abs(shift
[1]));
69 let stepsAchieved
= 0;
70 let sq
= [init
[0] + step
[0], init
[1] + step
[1]];
71 while (V
.OnBoard(sq
[0],sq
[1])) {
72 if (this.board
[sq
[0]][sq
[1]] != V
.HOLE
)
74 if (stepsAchieved
< nbSteps
) {
80 if (stepsAchieved
< nbSteps
)
81 // The move is impossible
85 // First, apply shift1
86 let dest
= tryMove(square
, shift1
);
88 // A knight: apply second shift
89 dest
= tryMove(dest
, shift2
);
93 // NOTE (TODO?): some extra work done in some function because informations
94 // on one step should ease the computation for a step in the same direction.
107 // Decompose knight movements into one step orthogonal + one diagonal
141 getJumpMoves([x
, y
], steps
) {
143 for (let step
of steps
) {
144 const sq
= this.getSquareAfter([x
,y
], step
);
147 this.board
[sq
[0]][sq
[1]] == V
.EMPTY
||
148 this.canTake([x
, y
], sq
)
151 moves
.push(this.getBasicMove([x
, y
], sq
));
157 // What are the pawn moves from square x,y ?
158 getPotentialPawnMoves([x
, y
]) {
159 const color
= this.turn
;
161 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
162 const shiftX
= color
== "w" ? -1 : 1;
163 const startRank
= color
== "w" ? sizeX
- 2 : 1;
164 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
166 const sq1
= this.getSquareAfter([x
,y
], [shiftX
,0]);
167 if (sq1
&& this.board
[sq1
[0]][y
] == V
.EMPTY
) {
168 // One square forward (cannot be a promotion)
169 moves
.push(this.getBasicMove([x
, y
], [sq1
[0], y
]));
170 if (x
== startRank
) {
171 // If two squares after is available, then move is possible
172 const sq2
= this.getSquareAfter([x
,y
], [2*shiftX
,0]);
173 if (sq2
&& this.board
[sq2
[0]][y
] == V
.EMPTY
)
175 moves
.push(this.getBasicMove([x
, y
], [sq2
[0], y
]));
179 for (let shiftY
of [-1, 1]) {
180 const sq
= this.getSquareAfter([x
,y
], [shiftX
,shiftY
]);
183 this.board
[sq
[0]][sq
[1]] != V
.EMPTY
&&
184 this.canTake([x
, y
], [sq
[0], sq
[1]])
186 const finalPieces
= sq
[0] == lastRank
187 ? [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]
189 for (let piece
of finalPieces
) {
191 this.getBasicMove([x
, y
], [sq
[0], sq
[1]], {
203 getPotentialRookMoves(sq
) {
204 return this.getJumpMoves(sq
, V
.steps
[V
.ROOK
]);
207 getPotentialKnightMoves(sq
) {
208 return this.getJumpMoves(sq
, V
.steps
[V
.KNIGHT
]);
211 getPotentialBishopMoves(sq
) {
212 return this.getJumpMoves(sq
, V
.steps
[V
.BISHOP
]);
215 getPotentialQueenMoves(sq
) {
216 return this.getJumpMoves(
218 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
222 getPotentialKingMoves(sq
) {
223 return this.getJumpMoves(sq
, V
.steps
[V
.KING
]);
226 isAttackedByJump([x
, y
], color
, piece
, steps
) {
227 for (let step
of steps
) {
228 const sq
= this.getSquareAfter([x
,y
], step
);
231 this.getPiece(sq
[0], sq
[1]) == piece
&&
232 this.getColor(sq
[0], sq
[1]) == color
240 isAttackedByPawn([x
, y
], color
) {
241 const pawnShift
= (color
== "w" ? 1 : -1);
242 for (let i
of [-1, 1]) {
243 const sq
= this.getSquareAfter([x
,y
], [pawnShift
,i
]);
246 this.getPiece(sq
[0], sq
[1]) == V
.PAWN
&&
247 this.getColor(sq
[0], sq
[1]) == color
255 isAttackedByRook(sq
, color
) {
256 return this.isAttackedByJump(sq
, color
, V
.ROOK
, V
.steps
[V
.ROOK
]);
259 isAttackedByKnight(sq
, color
) {
260 // NOTE: knight attack is not symmetric in this variant:
261 // steps order need to be reversed.
262 return this.isAttackedByJump(
266 V
.steps
[V
.KNIGHT
].map(s
=> s
.reverse())
270 isAttackedByBishop(sq
, color
) {
271 return this.isAttackedByJump(sq
, color
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
274 isAttackedByQueen(sq
, color
) {
275 return this.isAttackedByJump(
279 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
283 isAttackedByKing(sq
, color
) {
284 return this.isAttackedByJump(sq
, color
, V
.KING
, V
.steps
[V
.KING
]);
287 // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical.
288 // This is a side-effect on board generated by the move.
289 static PlayOnBoard(board
, move) {
290 board
[move.vanish
[0].x
][move.vanish
[0].y
] = V
.HOLE
;
291 for (let psq
of move.appear
) board
[psq
.x
][psq
.y
] = psq
.c
+ psq
.p
;
295 if (this.atLeastOneMove()) return "*";
296 // No valid move: I lose
297 return this.turn
== "w" ? "0-1" : "1-0";
300 static get SEARCH_DEPTH() {
306 for (let i
= 0; i
< V
.size
.x
; i
++) {
307 for (let j
= 0; j
< V
.size
.y
; j
++) {
308 if (![V
.EMPTY
,V
.HOLE
].includes(this.board
[i
][j
])) {
309 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
310 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
318 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
319 // Indicate start square + dest square, because holes distort the board
321 (piece
!= V
.PAWN
? piece
.toUpperCase() : "") +
322 V
.CoordsToSquare(move.start
) +
323 (move.vanish
.length
> move.appear
.length
? "x" : "") +
324 V
.CoordsToSquare(move.end
);
325 if (piece
== V
.PAWN
&& move.appear
[0].p
!= V
.PAWN
)
327 notation
+= "=" + move.appear
[0].p
.toUpperCase();