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
{
9 static get ALICE_PIECES() {
19 static get ALICE_CODES() {
31 return ChessRules
.PIECES
.concat(Object
.keys(V
.ALICE_PIECES
));
35 return (Object
.keys(V
.ALICE_PIECES
).includes(b
[1]) ? "Alice/" : "") + b
;
38 getEpSquare(moveOrSquare
) {
39 if (!moveOrSquare
) return undefined;
40 if (typeof moveOrSquare
=== "string") {
41 const square
= moveOrSquare
;
42 if (square
== "-") return undefined;
43 return V
.SquareToCoords(square
);
45 // Argument is a move:
46 const move = moveOrSquare
;
51 Math
.abs(s
.x
- e
.x
) == 2 &&
52 // Special conditions: a pawn can be on the other side
53 ['p','s'].includes(move.appear
[0].p
) &&
54 ['p','s'].includes(move.vanish
[0].p
)
61 return undefined; //default
64 // king can be l or L (on the other mirror side)
65 static IsGoodPosition(position
) {
66 if (position
.length
== 0) return false;
67 const rows
= position
.split("/");
68 if (rows
.length
!= V
.size
.x
) return false;
69 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
70 for (let row
of rows
) {
72 for (let i
= 0; i
< row
.length
; i
++) {
73 if (['K','k','L','l'].includes(row
[i
])) kings
[row
[i
]]++;
74 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
76 const num
= parseInt(row
[i
], 10);
77 if (isNaN(num
)) return false;
81 if (sumElts
!= V
.size
.y
) return false;
83 if (kings
['k'] + kings
['l'] != 1 || kings
['K'] + kings
['L'] != 1)
88 setOtherVariables(fen
) {
89 super.setOtherVariables(fen
);
90 const rows
= V
.ParseFen(fen
).position
.split("/");
91 if (this.kingPos
["w"][0] < 0 || this.kingPos
["b"][0] < 0) {
92 // INIT_COL_XXX won't be required if Alice kings are found
93 // (it means 'king moved')
94 for (let i
= 0; i
< rows
.length
; i
++) {
95 let k
= 0; //column index on board
96 for (let j
= 0; j
< rows
[i
].length
; j
++) {
97 switch (rows
[i
].charAt(j
)) {
99 this.kingPos
["b"] = [i
, k
];
102 this.kingPos
["w"] = [i
, k
];
105 const num
= parseInt(rows
[i
].charAt(j
), 10);
106 if (!isNaN(num
)) k
+= num
- 1;
115 // Return the (standard) color+piece notation at a square for a board
116 getSquareOccupation(i
, j
, mirrorSide
) {
117 const piece
= this.getPiece(i
, j
);
118 if (mirrorSide
== 1 && Object
.keys(V
.ALICE_CODES
).includes(piece
))
119 return this.board
[i
][j
];
120 if (mirrorSide
== 2 && Object
.keys(V
.ALICE_PIECES
).includes(piece
))
121 return this.getColor(i
, j
) + V
.ALICE_PIECES
[piece
];
125 // Build board of the given (mirror)side
126 getSideBoard(mirrorSide
) {
127 // Build corresponding board from complete board
128 let sideBoard
= ArrayFun
.init(V
.size
.x
, V
.size
.y
, "");
129 for (let i
= 0; i
< V
.size
.x
; i
++) {
130 for (let j
= 0; j
< V
.size
.y
; j
++)
131 sideBoard
[i
][j
] = this.getSquareOccupation(i
, j
, mirrorSide
);
136 // NOTE: castle & enPassant
137 // https://www.chessvariants.com/other.dir/alice.html
138 getPotentialMovesFrom([x
, y
], sideBoard
) {
139 const pieces
= Object
.keys(V
.ALICE_CODES
);
140 const codes
= Object
.keys(V
.ALICE_PIECES
);
141 const mirrorSide
= pieces
.includes(this.getPiece(x
, y
)) ? 1 : 2;
142 if (!sideBoard
) sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
143 const color
= this.getColor(x
, y
);
145 // Search valid moves on sideBoard
146 const saveBoard
= this.board
;
147 this.board
= sideBoard
[mirrorSide
- 1];
148 const moves
= super.getPotentialMovesFrom([x
, y
]).filter(m
=> {
149 // Filter out king moves which result in under-check position on
150 // current board (before mirror traversing)
151 let aprioriValid
= true;
152 if (m
.appear
[0].p
== V
.KING
) {
154 if (this.underCheck(color
, sideBoard
)) aprioriValid
= false;
159 this.board
= saveBoard
;
161 // Finally filter impossible moves
162 const res
= moves
.filter(m
=> {
163 if (m
.appear
.length
== 2) {
164 // Castle: appear[i] must be an empty square on the other board
165 for (let psq
of m
.appear
) {
167 this.getSquareOccupation(psq
.x
, psq
.y
, 3 - mirrorSide
) != V
.EMPTY
173 else if (this.board
[m
.end
.x
][m
.end
.y
] != V
.EMPTY
) {
174 // Attempt to capture
175 const piece
= this.getPiece(m
.end
.x
, m
.end
.y
);
177 (mirrorSide
== 1 && codes
.includes(piece
)) ||
178 (mirrorSide
== 2 && pieces
.includes(piece
))
183 // If the move is computed on board1, m.appear change for Alice pieces.
184 if (mirrorSide
== 1) {
185 m
.appear
.forEach(psq
=> {
186 // forEach: castling taken into account
187 psq
.p
= V
.ALICE_CODES
[psq
.p
]; //goto board2
191 // Move on board2: mark vanishing pieces as Alice
192 m
.vanish
.forEach(psq
=> {
193 psq
.p
= V
.ALICE_CODES
[psq
.p
];
196 // Fix en-passant captures
198 m
.vanish
[0].p
== V
.PAWN
&&
199 m
.vanish
.length
== 2 &&
200 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
202 m
.vanish
[1].c
= V
.GetOppCol(this.turn
);
203 const [epX
, epY
] = [m
.vanish
[1].x
, m
.vanish
[1].y
];
204 m
.vanish
[1].p
= this.getPiece(epX
, epY
);
211 filterValid(moves
, sideBoard
) {
212 if (moves
.length
== 0) return [];
213 if (!sideBoard
) sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
214 const color
= this.turn
;
215 return moves
.filter(m
=> {
216 this.playSide(m
, sideBoard
); //no need to track flags
217 const res
= !this.underCheck(color
, sideBoard
);
218 this.undoSide(m
, sideBoard
);
224 const color
= this.turn
;
225 let potentialMoves
= [];
226 const sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
227 for (var i
= 0; i
< V
.size
.x
; i
++) {
228 for (var j
= 0; j
< V
.size
.y
; j
++) {
229 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
230 Array
.prototype.push
.apply(
232 this.getPotentialMovesFrom([i
, j
], sideBoard
)
237 return this.filterValid(potentialMoves
, sideBoard
);
240 // Play on sideboards [TODO: only one sideBoard required]
241 playSide(move, sideBoard
) {
242 const pieces
= Object
.keys(V
.ALICE_CODES
);
243 move.vanish
.forEach(psq
=> {
244 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
245 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = V
.EMPTY
;
247 move.appear
.forEach(psq
=> {
248 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
249 const piece
= mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
];
250 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
251 if (piece
== V
.KING
) this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
255 // Undo on sideboards
256 undoSide(move, sideBoard
) {
257 const pieces
= Object
.keys(V
.ALICE_CODES
);
258 move.appear
.forEach(psq
=> {
259 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
260 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = V
.EMPTY
;
262 move.vanish
.forEach(psq
=> {
263 const mirrorSide
= pieces
.includes(psq
.p
) ? 1 : 2;
264 const piece
= mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
];
265 sideBoard
[mirrorSide
- 1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
266 if (piece
== V
.KING
) this.kingPos
[psq
.c
] = [psq
.x
, psq
.y
];
270 // sideBoard: arg containing both boards (see getAllValidMoves())
271 underCheck(color
, sideBoard
) {
272 const kp
= this.kingPos
[color
];
273 const mirrorSide
= sideBoard
[0][kp
[0]][kp
[1]] != V
.EMPTY
? 1 : 2;
274 let saveBoard
= this.board
;
275 this.board
= sideBoard
[mirrorSide
- 1];
276 let res
= this.isAttacked(kp
, [V
.GetOppCol(color
)]);
277 this.board
= saveBoard
;
282 const color
= this.turn
;
283 const pieces
= Object
.keys(V
.ALICE_CODES
);
284 const kp
= this.kingPos
[color
];
285 const mirrorSide
= pieces
.includes(this.getPiece(kp
[0], kp
[1])) ? 1 : 2;
286 let sideBoard
= this.getSideBoard(mirrorSide
);
287 let saveBoard
= this.board
;
288 this.board
= sideBoard
;
289 let res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)])
290 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
292 this.board
= saveBoard
;
297 super.postPlay(move); //standard king
298 const piece
= move.vanish
[0].p
;
299 const c
= move.vanish
[0].c
;
302 this.kingPos
[c
][0] = move.appear
[0].x
;
303 this.kingPos
[c
][1] = move.appear
[0].y
;
304 this.castleFlags
[c
] = [8, 8];
309 super.postUndo(move);
310 const c
= move.vanish
[0].c
;
311 if (move.vanish
[0].p
== "l")
312 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
316 if (this.atLeastOneMove()) return "*";
317 const pieces
= Object
.keys(V
.ALICE_CODES
);
318 const color
= this.turn
;
319 const kp
= this.kingPos
[color
];
320 const mirrorSide
= pieces
.includes(this.getPiece(kp
[0], kp
[1])) ? 1 : 2;
321 let sideBoard
= this.getSideBoard(mirrorSide
);
322 let saveBoard
= this.board
;
323 this.board
= sideBoard
;
325 if (!this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)]))
327 else res
= color
== "w" ? "0-1" : "1-0";
328 this.board
= saveBoard
;
332 static get VALUES() {
333 return Object
.assign(
346 static get SEARCH_DEPTH() {
351 if (move.appear
.length
== 2) {
352 if (move.end
.y
< move.start
.y
) return "0-0-0";
356 const finalSquare
= V
.CoordsToSquare(move.end
);
357 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
359 const captureMark
= move.vanish
.length
> move.appear
.length
? "x" : "";
361 if (["p", "s"].includes(piece
) && captureMark
.length
== 1)
362 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
364 // Piece or pawn movement
365 let notation
= piece
.toUpperCase() + pawnMark
+ captureMark
+ finalSquare
;
366 if (["s", "p"].includes(piece
) && !["s", "p"].includes(move.appear
[0].p
))
368 notation
+= "=" + move.appear
[0].p
.toUpperCase();