fcf63b4ed92d732835d2ba50a93a831a64ff15c8
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])) {
64 const tryMove
= (init
, shift
) => {
66 shift
[0] / Math
.abs(shift
[0]) || 0,
67 shift
[1] / Math
.abs(shift
[1]) || 0,
69 const nbSteps
= Math
.max(Math
.abs(shift
[0]), Math
.abs(shift
[1]));
70 let stepsAchieved
= 0;
71 let sq
= [init
[0] + step
[0], init
[1] + step
[1]];
72 while (V
.OnBoard(sq
[0],sq
[1])) {
73 if (this.board
[sq
[0]][sq
[1]] != V
.HOLE
)
75 if (stepsAchieved
< nbSteps
) {
81 if (stepsAchieved
< nbSteps
)
82 // The move is impossible
86 // First, apply shift1
87 let dest
= tryMove(square
, shift1
);
89 // A knight: apply second shift
90 dest
= tryMove(dest
, shift2
);
94 // NOTE (TODO?): some extra work done in some function because informations
95 // on one step should ease the computation for a step in the same direction.
108 // Decompose knight movements into one step orthogonal + one diagonal
142 getJumpMoves([x
, y
], steps
) {
144 for (let step
of steps
) {
145 const sq
= this.getSquareAfter([x
,y
], step
);
148 this.board
[sq
[0]][sq
[1]] == V
.EMPTY
||
149 this.canTake([x
, y
], sq
)
152 moves
.push(this.getBasicMove([x
, y
], sq
));
158 // What are the pawn moves from square x,y ?
159 getPotentialPawnMoves([x
, y
]) {
160 const color
= this.turn
;
162 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
163 const shiftX
= color
== "w" ? -1 : 1;
164 const startRank
= color
== "w" ? sizeX
- 2 : 1;
165 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
167 const sq1
= this.getSquareAfter([x
,y
], [shiftX
,0]);
168 if (sq1
&& this.board
[sq1
[0]][y
] == V
.EMPTY
) {
169 // One square forward (cannot be a promotion)
170 moves
.push(this.getBasicMove([x
, y
], [sq1
[0], y
]));
171 if (x
== startRank
) {
172 // If two squares after is available, then move is possible
173 const sq2
= this.getSquareAfter([x
,y
], [2*shiftX
,0]);
174 if (sq2
&& this.board
[sq2
[0]][y
] == V
.EMPTY
)
176 moves
.push(this.getBasicMove([x
, y
], [sq2
[0], y
]));
180 for (let shiftY
of [-1, 1]) {
181 const sq
= this.getSquareAfter([x
,y
], [shiftX
,shiftY
]);
184 this.board
[sq
[0]][sq
[1]] != V
.EMPTY
&&
185 this.canTake([x
, y
], [sq
[0], sq
[1]])
187 const finalPieces
= sq
[0] == lastRank
188 ? [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]
190 for (let piece
of finalPieces
) {
192 this.getBasicMove([x
, y
], [sq
[0], sq
[1]], {
204 getPotentialRookMoves(sq
) {
205 return this.getJumpMoves(sq
, V
.steps
[V
.ROOK
]);
208 getPotentialKnightMoves(sq
) {
209 return this.getJumpMoves(sq
, V
.steps
[V
.KNIGHT
]);
212 getPotentialBishopMoves(sq
) {
213 return this.getJumpMoves(sq
, V
.steps
[V
.BISHOP
]);
216 getPotentialQueenMoves(sq
) {
217 return this.getJumpMoves(
219 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
223 getPotentialKingMoves(sq
) {
224 return this.getJumpMoves(sq
, V
.steps
[V
.KING
]);
227 isAttackedByJump([x
, y
], color
, piece
, steps
) {
228 for (let step
of steps
) {
229 const sq
= this.getSquareAfter([x
,y
], step
);
232 this.getPiece(sq
[0], sq
[1]) == piece
&&
233 this.getColor(sq
[0], sq
[1]) == color
241 isAttackedByPawn([x
, y
], color
) {
242 const pawnShift
= (color
== "w" ? 1 : -1);
243 for (let i
of [-1, 1]) {
244 const sq
= this.getSquareAfter([x
,y
], [pawnShift
,i
]);
247 this.getPiece(sq
[0], sq
[1]) == V
.PAWN
&&
248 this.getColor(sq
[0], sq
[1]) == color
256 isAttackedByRook(sq
, color
) {
257 return this.isAttackedByJump(sq
, color
, V
.ROOK
, V
.steps
[V
.ROOK
]);
260 isAttackedByKnight(sq
, color
) {
261 // NOTE: knight attack is not symmetric in this variant:
262 // steps order need to be reversed.
263 return this.isAttackedByJump(
267 V
.steps
[V
.KNIGHT
].map(s
=> s
.reverse())
271 isAttackedByBishop(sq
, color
) {
272 return this.isAttackedByJump(sq
, color
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
275 isAttackedByQueen(sq
, color
) {
276 return this.isAttackedByJump(
280 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
284 isAttackedByKing(sq
, color
) {
285 return this.isAttackedByJump(sq
, color
, V
.KING
, V
.steps
[V
.KING
]);
288 // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical.
289 // This is a side-effect on board generated by the move.
290 static PlayOnBoard(board
, move) {
291 board
[move.vanish
[0].x
][move.vanish
[0].y
] = V
.HOLE
;
292 for (let psq
of move.appear
) board
[psq
.x
][psq
.y
] = psq
.c
+ psq
.p
;
296 if (this.atLeastOneMove()) return "*";
297 // No valid move: I lose
298 return this.turn
== "w" ? "0-1" : "1-0";
301 static get SEARCH_DEPTH() {
307 for (let i
= 0; i
< V
.size
.x
; i
++) {
308 for (let j
= 0; j
< V
.size
.y
; j
++) {
309 if (![V
.EMPTY
,V
.HOLE
].includes(this.board
[i
][j
])) {
310 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
311 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
319 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
320 // Indicate start square + dest square, because holes distort the board
322 (piece
!= V
.PAWN
? piece
.toUpperCase() : "") +
323 V
.CoordsToSquare(move.start
) +
324 (move.vanish
.length
> move.appear
.length
? "x" : "") +
325 V
.CoordsToSquare(move.end
);
326 if (piece
== V
.PAWN
&& move.appear
[0].p
!= V
.PAWN
)
328 notation
+= "=" + move.appear
[0].p
.toUpperCase();