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 (less sideBoard computations)
6 export const VariantRules
= class AliceRules
extends ChessRules
{
7 static get ALICE_PIECES() {
17 static get ALICE_CODES() {
29 return ChessRules
.PIECES
.concat(Object
.keys(V
.ALICE_PIECES
));
33 return (Object
.keys(V
.ALICE_PIECES
).includes(b
[1]) ? "Alice/" : "") + b
;
36 setOtherVariables(fen
) {
37 super.setOtherVariables(fen
);
38 const rows
= V
.ParseFen(fen
).position
.split("/");
39 if (this.kingPos
["w"][0] < 0 || this.kingPos
["b"][0] < 0) {
40 // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
41 for (let i
= 0; i
< rows
.length
; i
++) {
42 let k
= 0; //column index on board
43 for (let j
= 0; j
< rows
[i
].length
; j
++) {
44 switch (rows
[i
].charAt(j
)) {
46 this.kingPos
["b"] = [i
, k
];
49 this.kingPos
["w"] = [i
, k
];
52 const num
= parseInt(rows
[i
].charAt(j
));
53 if (!isNaN(num
)) k
+= num
- 1;
62 // Return the (standard) color+piece notation at a square for a board
63 getSquareOccupation(i
, j
, mirrorSide
) {
64 const piece
= this.getPiece(i
, j
);
65 if (mirrorSide
== 1 && Object
.keys(V
.ALICE_CODES
).includes(piece
))
66 return this.board
[i
][j
];
67 if (mirrorSide
== 2 && Object
.keys(V
.ALICE_PIECES
).includes(piece
))
68 return this.getColor(i
, j
) + V
.ALICE_PIECES
[piece
];
72 // Build board of the given (mirror)side
73 getSideBoard(mirrorSide
) {
74 // Build corresponding board from complete board
75 let sideBoard
= ArrayFun
.init(V
.size
.x
, V
.size
.y
, "");
76 for (let i
= 0; i
< V
.size
.x
; i
++) {
77 for (let j
= 0; j
< V
.size
.y
; j
++)
78 sideBoard
[i
][j
] = this.getSquareOccupation(i
, j
, mirrorSide
);
83 // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
84 getPotentialMovesFrom([x
, y
], sideBoard
) {
85 const pieces
= Object
.keys(V
.ALICE_CODES
);
86 const codes
= Object
.keys(V
.ALICE_PIECES
);
87 const mirrorSide
= pieces
.includes(this.getPiece(x
, y
)) ? 1 : 2;
88 if (!sideBoard
) sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
89 const color
= this.getColor(x
, y
);
91 // Search valid moves on sideBoard
92 const saveBoard
= this.board
;
93 this.board
= sideBoard
[mirrorSide
- 1];
94 const moves
= super.getPotentialMovesFrom([x
, y
]).filter(m
=> {
95 // Filter out king moves which result in under-check position on
96 // current board (before mirror traversing)
97 let aprioriValid
= true;
98 if (m
.appear
[0].p
== V
.KING
) {
100 if (this.underCheck(color
, sideBoard
)) aprioriValid
= false;
105 this.board
= saveBoard
;
107 // Finally filter impossible moves
108 const res
= moves
.filter(m
=> {
109 if (m
.appear
.length
== 2) {
111 // appear[i] must be an empty square on the other board
112 for (let psq
of m
.appear
) {
113 if (this.getSquareOccupation(psq
.x
, psq
.y
, 3 - mirrorSide
) != V
.EMPTY
)
116 } else if (this.board
[m
.end
.x
][m
.end
.y
] != V
.EMPTY
) {
117 // Attempt to capture
118 const piece
= this.getPiece(m
.end
.x
, m
.end
.y
);
120 (mirrorSide
== 1 && codes
.includes(piece
)) ||
121 (mirrorSide
== 2 && pieces
.includes(piece
))
126 // If the move is computed on board1, m.appear change for Alice pieces.
127 if (mirrorSide
== 1) {
128 m
.appear
.forEach(psq
=> {
129 // forEach: castling taken into account
130 psq
.p
= V
.ALICE_CODES
[psq
.p
]; //goto board2
134 // Move on board2: mark vanishing pieces as Alice
135 m
.vanish
.forEach(psq
=> {
136 psq
.p
= V
.ALICE_CODES
[psq
.p
];
139 // Fix en-passant captures
141 m
.vanish
[0].p
== V
.PAWN
&&
142 m
.vanish
.length
== 2 &&
143 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
145 m
.vanish
[1].c
= V
.GetOppCol(this.getColor(x
, y
));
146 // In the special case of en-passant, if
147 // - board1 takes board2 : vanish[1] --> Alice
148 // - board2 takes board1 : vanish[1] --> normal
149 let van
= m
.vanish
[1];
150 if (mirrorSide
== 1 && codes
.includes(this.getPiece(van
.x
, van
.y
)))
151 van
.p
= V
.ALICE_CODES
[van
.p
];
154 pieces
.includes(this.getPiece(van
.x
, van
.y
))
156 van
.p
= V
.ALICE_PIECES
[van
.p
];
163 filterValid(moves
, sideBoard
) {
164 if (moves
.length
== 0) return [];
165 if (!sideBoard
) sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
166 const color
= this.turn
;
167 return moves
.filter(m
=> {
168 this.playSide(m
, sideBoard
); //no need to track flags
169 const res
= !this.underCheck(color
, sideBoard
);
170 this.undoSide(m
, sideBoard
);
176 const color
= this.turn
;
177 let potentialMoves
= [];
178 const sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
179 for (var i
= 0; i
< V
.size
.x
; i
++) {
180 for (var j
= 0; j
< V
.size
.y
; j
++) {
181 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
182 Array
.prototype.push
.apply(
184 this.getPotentialMovesFrom([i
, j
], sideBoard
)
189 return this.filterValid(potentialMoves
, sideBoard
);
192 // Play on sideboards [TODO: only one sideBoard required]
193 playSide(move, sideBoard
) {
194 const pieces
= Object
.keys(V
.ALICE_CODES
);
195 move.vanish
.forEach(psq
=> {
196 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
197 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = V
.EMPTY
;
199 move.appear
.forEach(psq
=> {
200 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
201 const piece
= mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
];
202 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
203 if (piece
== V
.KING
) this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
207 // Undo on sideboards
208 undoSide(move, sideBoard
) {
209 const pieces
= Object
.keys(V
.ALICE_CODES
);
210 move.appear
.forEach(psq
=> {
211 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
212 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = V
.EMPTY
;
214 move.vanish
.forEach(psq
=> {
215 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
216 const piece
= mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
];
217 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
218 if (piece
== V
.KING
) this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
222 // sideBoard: arg containing both boards (see getAllValidMoves())
223 underCheck(color
, sideBoard
) {
224 const kp
= this.kingPos
[color
];
225 const mirrorSide
= sideBoard
[0][kp
[0]][kp
[1]] != V
.EMPTY
? 1 : 2;
226 let saveBoard
= this.board
;
227 this.board
= sideBoard
[mirrorSide
- 1];
228 let res
= this.isAttacked(kp
, [V
.GetOppCol(color
)]);
229 this.board
= saveBoard
;
233 getCheckSquares(color
) {
234 const pieces
= Object
.keys(V
.ALICE_CODES
);
235 const kp
= this.kingPos
[color
];
236 const mirrorSide
= pieces
.includes(this.getPiece(kp
[0], kp
[1])) ? 1 : 2;
237 let sideBoard
= this.getSideBoard(mirrorSide
);
238 let saveBoard
= this.board
;
239 this.board
= sideBoard
;
240 let res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)])
241 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
243 this.board
= saveBoard
;
247 updateVariables(move) {
248 super.updateVariables(move); //standard king
249 const piece
= move.vanish
[0].p
;
250 const c
= move.vanish
[0].c
;
253 this.kingPos
[c
][0] = move.appear
[0].x
;
254 this.kingPos
[c
][1] = move.appear
[0].y
;
255 this.castleFlags
[c
] = [false, false];
259 unupdateVariables(move) {
260 super.unupdateVariables(move);
261 const c
= move.vanish
[0].c
;
262 if (move.vanish
[0].p
== "l") this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
266 if (this.atLeastOneMove())
270 const pieces
= Object
.keys(V
.ALICE_CODES
);
271 const color
= this.turn
;
272 const kp
= this.kingPos
[color
];
273 const mirrorSide
= pieces
.includes(this.getPiece(kp
[0], kp
[1])) ? 1 : 2;
274 let sideBoard
= this.getSideBoard(mirrorSide
);
275 let saveBoard
= this.board
;
276 this.board
= sideBoard
;
278 if (!this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)]))
280 else res
= color
== "w" ? "0-1" : "1-0";
281 this.board
= saveBoard
;
285 static get VALUES() {
286 return Object
.assign(
299 static get SEARCH_DEPTH() {
304 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
) {
305 if (move.end
.y
< move.start
.y
) return "0-0-0";
309 const finalSquare
= V
.CoordsToSquare(move.end
);
310 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
312 const captureMark
= move.vanish
.length
> move.appear
.length
? "x" : "";
314 if (["p", "s"].includes(piece
) && captureMark
.length
== 1)
315 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
317 // Piece or pawn movement
318 let notation
= piece
.toUpperCase() + pawnMark
+ captureMark
+ finalSquare
;
319 if (["s", "p"].includes(piece
) && !["s", "p"].includes(move.appear
[0].p
)) {
321 notation
+= "=" + move.appear
[0].p
.toUpperCase();