57de5683b4b22d01402c77832207f6fe48612832
1 import { ChessRules
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
4 // NOTE: alternative implementation, probably cleaner = use only 1 board
5 // TODO? atLeastOneMove() would be more efficient if rewritten here
6 // (less sideBoard computations)
7 export class AliceRules
extends ChessRules
{
8 static get ALICE_PIECES() {
18 static get ALICE_CODES() {
30 return ChessRules
.PIECES
.concat(Object
.keys(V
.ALICE_PIECES
));
34 return (Object
.keys(V
.ALICE_PIECES
).includes(b
[1]) ? "Alice/" : "") + b
;
37 getEpSquare(moveOrSquare
) {
38 if (!moveOrSquare
) return undefined;
39 if (typeof moveOrSquare
=== "string") {
40 const square
= moveOrSquare
;
41 if (square
== "-") return undefined;
42 return V
.SquareToCoords(square
);
44 // Argument is a move:
45 const move = moveOrSquare
;
50 Math
.abs(s
.x
- e
.x
) == 2 &&
51 // Special conditions: a pawn can be on the other side
52 ['p','s'].includes(move.appear
[0].p
) &&
53 ['p','s'].includes(move.vanish
[0].p
)
60 return undefined; //default
63 setOtherVariables(fen
) {
64 super.setOtherVariables(fen
);
65 const rows
= V
.ParseFen(fen
).position
.split("/");
66 if (this.kingPos
["w"][0] < 0 || this.kingPos
["b"][0] < 0) {
67 // INIT_COL_XXX won't be required if Alice kings are found
68 // (it means 'king moved')
69 for (let i
= 0; i
< rows
.length
; i
++) {
70 let k
= 0; //column index on board
71 for (let j
= 0; j
< rows
[i
].length
; j
++) {
72 switch (rows
[i
].charAt(j
)) {
74 this.kingPos
["b"] = [i
, k
];
77 this.kingPos
["w"] = [i
, k
];
80 const num
= parseInt(rows
[i
].charAt(j
));
81 if (!isNaN(num
)) k
+= num
- 1;
90 // Return the (standard) color+piece notation at a square for a board
91 getSquareOccupation(i
, j
, mirrorSide
) {
92 const piece
= this.getPiece(i
, j
);
93 if (mirrorSide
== 1 && Object
.keys(V
.ALICE_CODES
).includes(piece
))
94 return this.board
[i
][j
];
95 if (mirrorSide
== 2 && Object
.keys(V
.ALICE_PIECES
).includes(piece
))
96 return this.getColor(i
, j
) + V
.ALICE_PIECES
[piece
];
100 // Build board of the given (mirror)side
101 getSideBoard(mirrorSide
) {
102 // Build corresponding board from complete board
103 let sideBoard
= ArrayFun
.init(V
.size
.x
, V
.size
.y
, "");
104 for (let i
= 0; i
< V
.size
.x
; i
++) {
105 for (let j
= 0; j
< V
.size
.y
; j
++)
106 sideBoard
[i
][j
] = this.getSquareOccupation(i
, j
, mirrorSide
);
111 // NOTE: castle & enPassant
112 // https://www.chessvariants.com/other.dir/alice.html
113 getPotentialMovesFrom([x
, y
], sideBoard
) {
114 const pieces
= Object
.keys(V
.ALICE_CODES
);
115 const codes
= Object
.keys(V
.ALICE_PIECES
);
116 const mirrorSide
= pieces
.includes(this.getPiece(x
, y
)) ? 1 : 2;
117 if (!sideBoard
) sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
118 const color
= this.getColor(x
, y
);
120 // Search valid moves on sideBoard
121 const saveBoard
= this.board
;
122 this.board
= sideBoard
[mirrorSide
- 1];
123 const moves
= super.getPotentialMovesFrom([x
, y
]).filter(m
=> {
124 // Filter out king moves which result in under-check position on
125 // current board (before mirror traversing)
126 let aprioriValid
= true;
127 if (m
.appear
[0].p
== V
.KING
) {
129 if (this.underCheck(color
, sideBoard
)) aprioriValid
= false;
134 this.board
= saveBoard
;
136 // Finally filter impossible moves
137 const res
= moves
.filter(m
=> {
138 if (m
.appear
.length
== 2) {
139 // Castle: appear[i] must be an empty square on the other board
140 for (let psq
of m
.appear
) {
142 this.getSquareOccupation(psq
.x
, psq
.y
, 3 - mirrorSide
) != V
.EMPTY
147 } else if (this.board
[m
.end
.x
][m
.end
.y
] != V
.EMPTY
) {
148 // Attempt to capture
149 const piece
= this.getPiece(m
.end
.x
, m
.end
.y
);
151 (mirrorSide
== 1 && codes
.includes(piece
)) ||
152 (mirrorSide
== 2 && pieces
.includes(piece
))
157 // If the move is computed on board1, m.appear change for Alice pieces.
158 if (mirrorSide
== 1) {
159 m
.appear
.forEach(psq
=> {
160 // forEach: castling taken into account
161 psq
.p
= V
.ALICE_CODES
[psq
.p
]; //goto board2
165 // Move on board2: mark vanishing pieces as Alice
166 m
.vanish
.forEach(psq
=> {
167 psq
.p
= V
.ALICE_CODES
[psq
.p
];
170 // Fix en-passant captures
172 m
.vanish
[0].p
== V
.PAWN
&&
173 m
.vanish
.length
== 2 &&
174 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
176 m
.vanish
[1].c
= V
.GetOppCol(this.getColor(x
, y
));
177 // In the special case of en-passant, if
178 // - board1 takes board2 : vanish[1] --> Alice
179 // - board2 takes board1 : vanish[1] --> normal
180 let van
= m
.vanish
[1];
181 if (mirrorSide
== 1 && codes
.includes(this.getPiece(van
.x
, van
.y
)))
182 van
.p
= V
.ALICE_CODES
[van
.p
];
185 pieces
.includes(this.getPiece(van
.x
, van
.y
))
187 van
.p
= V
.ALICE_PIECES
[van
.p
];
194 getEnpassantCaptures([x
, y
], shiftX
) {
195 const Lep
= this.epSquares
.length
;
196 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
197 let enpassantMove
= null;
200 epSquare
.x
== x
+ shiftX
&&
201 Math
.abs(epSquare
.y
- y
) == 1
203 enpassantMove
= this.getBasicMove([x
, y
], [epSquare
.x
, epSquare
.y
]);
204 // May capture in same world or different:
205 const capturedPiece
=
206 this.board
[x
][epSquare
.y
] != V
.EMPTY
207 ? this.getPiece(x
, epSquare
.y
)
208 : ['p','s'][1 - "ps".indexOf(this.getPiece(x
, y
))];
209 enpassantMove
.vanish
.push({
213 c: V
.GetOppCol(this.turn
)
216 return !!enpassantMove
? [enpassantMove
] : [];
219 filterValid(moves
, sideBoard
) {
220 if (moves
.length
== 0) return [];
221 if (!sideBoard
) sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
222 const color
= this.turn
;
223 return moves
.filter(m
=> {
224 this.playSide(m
, sideBoard
); //no need to track flags
225 const res
= !this.underCheck(color
, sideBoard
);
226 this.undoSide(m
, sideBoard
);
232 const color
= this.turn
;
233 let potentialMoves
= [];
234 const sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
235 for (var i
= 0; i
< V
.size
.x
; i
++) {
236 for (var j
= 0; j
< V
.size
.y
; j
++) {
237 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
238 Array
.prototype.push
.apply(
240 this.getPotentialMovesFrom([i
, j
], sideBoard
)
245 return this.filterValid(potentialMoves
, sideBoard
);
248 // Play on sideboards [TODO: only one sideBoard required]
249 playSide(move, sideBoard
) {
250 const pieces
= Object
.keys(V
.ALICE_CODES
);
251 move.vanish
.forEach(psq
=> {
252 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
253 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = V
.EMPTY
;
255 move.appear
.forEach(psq
=> {
256 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
257 const piece
= mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
];
258 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
259 if (piece
== V
.KING
) this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
263 // Undo on sideboards
264 undoSide(move, sideBoard
) {
265 const pieces
= Object
.keys(V
.ALICE_CODES
);
266 move.appear
.forEach(psq
=> {
267 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
268 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = V
.EMPTY
;
270 move.vanish
.forEach(psq
=> {
271 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
272 const piece
= mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
];
273 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
274 if (piece
== V
.KING
) this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
278 // sideBoard: arg containing both boards (see getAllValidMoves())
279 underCheck(color
, sideBoard
) {
280 const kp
= this.kingPos
[color
];
281 const mirrorSide
= sideBoard
[0][kp
[0]][kp
[1]] != V
.EMPTY
? 1 : 2;
282 let saveBoard
= this.board
;
283 this.board
= sideBoard
[mirrorSide
- 1];
284 let res
= this.isAttacked(kp
, [V
.GetOppCol(color
)]);
285 this.board
= saveBoard
;
290 const color
= this.turn
;
291 const pieces
= Object
.keys(V
.ALICE_CODES
);
292 const kp
= this.kingPos
[color
];
293 const mirrorSide
= pieces
.includes(this.getPiece(kp
[0], kp
[1])) ? 1 : 2;
294 let sideBoard
= this.getSideBoard(mirrorSide
);
295 let saveBoard
= this.board
;
296 this.board
= sideBoard
;
297 let res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)])
298 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
300 this.board
= saveBoard
;
305 super.postPlay(move); //standard king
306 const piece
= move.vanish
[0].p
;
307 const c
= move.vanish
[0].c
;
310 this.kingPos
[c
][0] = move.appear
[0].x
;
311 this.kingPos
[c
][1] = move.appear
[0].y
;
312 this.castleFlags
[c
] = [8, 8];
317 super.postUndo(move);
318 const c
= move.vanish
[0].c
;
319 if (move.vanish
[0].p
== "l")
320 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
324 if (this.atLeastOneMove()) return "*";
325 const pieces
= Object
.keys(V
.ALICE_CODES
);
326 const color
= this.turn
;
327 const kp
= this.kingPos
[color
];
328 const mirrorSide
= pieces
.includes(this.getPiece(kp
[0], kp
[1])) ? 1 : 2;
329 let sideBoard
= this.getSideBoard(mirrorSide
);
330 let saveBoard
= this.board
;
331 this.board
= sideBoard
;
333 if (!this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)]))
335 else res
= color
== "w" ? "0-1" : "1-0";
336 this.board
= saveBoard
;
340 static get VALUES() {
341 return Object
.assign(
354 static get SEARCH_DEPTH() {
359 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
) {
360 if (move.end
.y
< move.start
.y
) return "0-0-0";
364 const finalSquare
= V
.CoordsToSquare(move.end
);
365 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
367 const captureMark
= move.vanish
.length
> move.appear
.length
? "x" : "";
369 if (["p", "s"].includes(piece
) && captureMark
.length
== 1)
370 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
372 // Piece or pawn movement
373 let notation
= piece
.toUpperCase() + pawnMark
+ captureMark
+ finalSquare
;
374 if (["s", "p"].includes(piece
) && !["s", "p"].includes(move.appear
[0].p
)) {
376 notation
+= "=" + move.appear
[0].p
.toUpperCase();