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
8 static get ALICE_PIECES()
19 static get ALICE_CODES()
33 return (Object
.keys(this.ALICE_PIECES
).includes(b
[1]) ? "Alice/" : "") + b
;
38 return ChessRules
.PIECES
.concat(Object
.keys(V
.ALICE_PIECES
));
41 setOtherVariables(fen
)
43 super.setOtherVariables(fen
);
44 const rows
= V
.ParseFen(fen
).position
.split("/");
45 if (this.kingPos
["w"][0] < 0 || this.kingPos
["b"][0] < 0)
47 // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
48 for (let i
=0; i
<rows
.length
; i
++)
50 let k
= 0; //column index on board
51 for (let j
=0; j
<rows
[i
].length
; j
++)
53 switch (rows
[i
].charAt(j
))
56 this.kingPos
['b'] = [i
,k
];
59 this.kingPos
['w'] = [i
,k
];
62 const num
= parseInt(rows
[i
].charAt(j
));
72 // Return the (standard) color+piece notation at a square for a board
73 getSquareOccupation(i
, j
, mirrorSide
)
75 const piece
= this.getPiece(i
,j
);
76 if (mirrorSide
==1 && Object
.keys(V
.ALICE_CODES
).includes(piece
))
77 return this.board
[i
][j
];
78 else if (mirrorSide
==2 && Object
.keys(V
.ALICE_PIECES
).includes(piece
))
79 return this.getColor(i
,j
) + V
.ALICE_PIECES
[piece
];
83 // Build board of the given (mirror)side
84 getSideBoard(mirrorSide
)
86 // Build corresponding board from complete board
87 let sideBoard
= ArrayFun
.init(V
.size
.x
, V
.size
.y
, "");
88 for (let i
=0; i
<V
.size
.x
; i
++)
90 for (let j
=0; j
<V
.size
.y
; j
++)
91 sideBoard
[i
][j
] = this.getSquareOccupation(i
, j
, mirrorSide
);
96 // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
97 getPotentialMovesFrom([x
,y
], sideBoard
)
99 const pieces
= Object
.keys(V
.ALICE_CODES
);
100 const codes
= Object
.keys(V
.ALICE_PIECES
);
101 const mirrorSide
= (pieces
.includes(this.getPiece(x
,y
)) ? 1 : 2);
103 sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
104 const color
= this.getColor(x
,y
);
106 // Search valid moves on sideBoard
107 const saveBoard
= this.board
;
108 this.board
= sideBoard
[mirrorSide
-1];
109 const moves
= super.getPotentialMovesFrom([x
,y
])
111 // Filter out king moves which result in under-check position on
112 // current board (before mirror traversing)
113 let aprioriValid
= true;
114 if (m
.appear
[0].p
== V
.KING
)
117 if (this.underCheck(color
, sideBoard
))
118 aprioriValid
= false;
123 this.board
= saveBoard
;
125 // Finally filter impossible moves
126 const res
= moves
.filter(m
=> {
127 if (m
.appear
.length
== 2) //castle
129 // appear[i] must be an empty square on the other board
130 for (let psq
of m
.appear
)
132 if (this.getSquareOccupation(psq
.x
,psq
.y
,3-mirrorSide
) != V
.EMPTY
)
136 else if (this.board
[m
.end
.x
][m
.end
.y
] != V
.EMPTY
)
138 // Attempt to capture
139 const piece
= this.getPiece(m
.end
.x
,m
.end
.y
);
140 if ((mirrorSide
==1 && codes
.includes(piece
))
141 || (mirrorSide
==2 && pieces
.includes(piece
)))
146 // If the move is computed on board1, m.appear change for Alice pieces.
149 m
.appear
.forEach(psq
=> { //forEach: castling taken into account
150 psq
.p
= V
.ALICE_CODES
[psq
.p
]; //goto board2
153 else //move on board2: mark vanishing pieces as Alice
155 m
.vanish
.forEach(psq
=> {
156 psq
.p
= V
.ALICE_CODES
[psq
.p
];
159 // Fix en-passant captures
160 if (m
.vanish
[0].p
== V
.PAWN
&& m
.vanish
.length
== 2
161 && this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
)
163 m
.vanish
[1].c
= V
.GetOppCol(this.getColor(x
,y
));
164 // In the special case of en-passant, if
165 // - board1 takes board2 : vanish[1] --> Alice
166 // - board2 takes board1 : vanish[1] --> normal
167 let van
= m
.vanish
[1];
168 if (mirrorSide
==1 && codes
.includes(this.getPiece(van
.x
,van
.y
)))
169 van
.p
= V
.ALICE_CODES
[van
.p
];
170 else if (mirrorSide
==2 && pieces
.includes(this.getPiece(van
.x
,van
.y
)))
171 van
.p
= V
.ALICE_PIECES
[van
.p
];
178 filterValid(moves
, sideBoard
)
180 if (moves
.length
== 0)
183 sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
184 const color
= this.turn
;
185 return moves
.filter(m
=> {
186 this.playSide(m
, sideBoard
); //no need to track flags
187 const res
= !this.underCheck(color
, sideBoard
);
188 this.undoSide(m
, sideBoard
);
195 const color
= this.turn
;
196 const oppCol
= V
.GetOppCol(color
);
197 let potentialMoves
= [];
198 const sideBoard
= [this.getSideBoard(1), this.getSideBoard(2)];
199 for (var i
=0; i
<V
.size
.x
; i
++)
201 for (var j
=0; j
<V
.size
.y
; j
++)
203 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == color
)
205 Array
.prototype.push
.apply(potentialMoves
,
206 this.getPotentialMovesFrom([i
,j
], sideBoard
));
210 return this.filterValid(potentialMoves
, sideBoard
);
213 // Play on sideboards [TODO: only one sideBoard required]
214 playSide(move, sideBoard
)
216 const pieces
= Object
.keys(V
.ALICE_CODES
);
217 move.vanish
.forEach(psq
=> {
218 const mirrorSide
= (pieces
.includes(psq
.p
) ? 1 : 2);
219 sideBoard
[mirrorSide
-1][psq
.x
][psq
.y
] = V
.EMPTY
;
221 move.appear
.forEach(psq
=> {
222 const mirrorSide
= (pieces
.includes(psq
.p
) ? 1 : 2);
223 const piece
= (mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
]);
224 sideBoard
[mirrorSide
-1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
226 this.kingPos
[psq
.c
] = [psq
.x
,psq
.y
];
230 // Undo on sideboards
231 undoSide(move, sideBoard
)
233 const pieces
= Object
.keys(V
.ALICE_CODES
);
234 move.appear
.forEach(psq
=> {
235 const mirrorSide
= (pieces
.includes(psq
.p
) ? 1 : 2);
236 sideBoard
[mirrorSide
-1][psq
.x
][psq
.y
] = V
.EMPTY
;
238 move.vanish
.forEach(psq
=> {
239 const mirrorSide
= (pieces
.includes(psq
.p
) ? 1 : 2);
240 const piece
= (mirrorSide
== 1 ? psq
.p : V
.ALICE_PIECES
[psq
.p
]);
241 sideBoard
[mirrorSide
-1][psq
.x
][psq
.y
] = psq
.c
+ piece
;
243 this.kingPos
[psq
.c
] = [psq
.x
,psq
.y
];
247 // sideBoard: arg containing both boards (see getAllValidMoves())
248 underCheck(color
, sideBoard
)
250 const kp
= this.kingPos
[color
];
251 const mirrorSide
= (sideBoard
[0][kp
[0]][kp
[1]] != V
.EMPTY
? 1 : 2);
252 let saveBoard
= this.board
;
253 this.board
= sideBoard
[mirrorSide
-1];
254 let res
= this.isAttacked(kp
, [V
.GetOppCol(color
)]);
255 this.board
= saveBoard
;
259 getCheckSquares(color
)
261 const pieces
= Object
.keys(V
.ALICE_CODES
);
262 const kp
= this.kingPos
[color
];
263 const mirrorSide
= (pieces
.includes(this.getPiece(kp
[0],kp
[1])) ? 1 : 2);
264 let sideBoard
= this.getSideBoard(mirrorSide
);
265 let saveBoard
= this.board
;
266 this.board
= sideBoard
;
267 let res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)])
268 ? [ JSON
.parse(JSON
.stringify(this.kingPos
[color
])) ]
270 this.board
= saveBoard
;
274 updateVariables(move)
276 super.updateVariables(move); //standard king
277 const piece
= move.vanish
[0].p
;
278 const c
= move.vanish
[0].c
;
282 this.kingPos
[c
][0] = move.appear
[0].x
;
283 this.kingPos
[c
][1] = move.appear
[0].y
;
284 this.castleFlags
[c
] = [false,false];
288 unupdateVariables(move)
290 super.unupdateVariables(move);
291 const c
= move.vanish
[0].c
;
292 if (move.vanish
[0].p
== "l")
293 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
298 if (this.atLeastOneMove()) // game not over
301 const pieces
= Object
.keys(V
.ALICE_CODES
);
302 const color
= this.turn
;
303 const kp
= this.kingPos
[color
];
304 const mirrorSide
= (pieces
.includes(this.getPiece(kp
[0],kp
[1])) ? 1 : 2);
305 let sideBoard
= this.getSideBoard(mirrorSide
);
306 let saveBoard
= this.board
;
307 this.board
= sideBoard
;
309 if (!this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
)]))
312 res
= (color
== "w" ? "0-1" : "1-0");
313 this.board
= saveBoard
;
319 return Object
.assign(
334 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
336 if (move.end
.y
< move.start
.y
)
342 const finalSquare
= V
.CoordsToSquare(move.end
);
343 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
345 const captureMark
= (move.vanish
.length
> move.appear
.length
? "x" : "");
347 if (["p","s"].includes(piece
) && captureMark
.length
== 1)
348 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
350 // Piece or pawn movement
351 let notation
= piece
.toUpperCase() + pawnMark
+ captureMark
+ finalSquare
;
352 if (['s','p'].includes(piece
) && !['s','p'].includes(move.appear
[0].p
))
355 notation
+= "=" + move.appear
[0].p
.toUpperCase();