73a55088f60ddd3983a4731ab00075cf978cf715
[vchess.git] / client / src / variants / Benedict.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export const VariantRules = class BenedictRules extends ChessRules {
4 static get HasEnpassant() {
5 return false;
6 }
7
8 // TODO(?): some duplicated code in 2 next functions
9 getSlideNJumpMoves([x, y], steps, oneStep) {
10 let moves = [];
11 outerLoop: for (let loop = 0; loop < steps.length; loop++) {
12 const step = steps[loop];
13 let i = x + step[0];
14 let j = y + step[1];
15 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
16 moves.push(this.getBasicMove([x, y], [i, j]));
17 if (oneStep) continue outerLoop;
18 i += step[0];
19 j += step[1];
20 }
21 // No capture check: handled elsewhere (next method)
22 }
23 return moves;
24 }
25
26 // Find possible captures from a square
27 // follow steps from x,y until something is met.
28 findCaptures([x, y]) {
29 const color = this.getColor(x, y);
30 const piece = this.getPiece(x, y);
31 let squares = [];
32 const steps =
33 piece != V.PAWN
34 ? [V.QUEEN,V.KING].includes(piece)
35 ? V.steps[V.ROOK].concat(V.steps[V.BISHOP])
36 : V.steps[piece]
37 : color == "w"
38 ? [
39 [-1, -1],
40 [-1, 1]
41 ]
42 : [
43 [1, -1],
44 [1, 1]
45 ];
46 const oneStep = [V.KNIGHT,V.PAWN,V.KING].includes(piece);
47 outerLoop: for (let loop = 0; loop < steps.length; loop++) {
48 const step = steps[loop];
49 let i = x + step[0];
50 let j = y + step[1];
51 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
52 if (oneStep) continue outerLoop;
53 i += step[0];
54 j += step[1];
55 }
56 if (
57 V.OnBoard(i, j) &&
58 this.getColor(i, j) == V.GetOppCol(color)
59 ) {
60 // eat!
61 squares.push([i, j]);
62 }
63 }
64 return squares;
65 }
66
67 getPotentialPawnMoves([x, y]) {
68 const color = this.getColor(x, y);
69 let moves = [];
70 const sizeY = V.size.y;
71 const shift = color == "w" ? -1 : 1;
72 const startRank = color == "w" ? sizeY - 2 : 1;
73 const firstRank = color == "w" ? sizeY - 1 : 0;
74 const lastRank = color == "w" ? 0 : sizeY - 1;
75
76 if (x + shift != lastRank) {
77 // Normal moves
78 if (this.board[x + shift][y] == V.EMPTY) {
79 moves.push(this.getBasicMove([x, y], [x + shift, y]));
80 if (
81 [startRank, firstRank].includes(x) &&
82 this.board[x + 2 * shift][y] == V.EMPTY
83 ) {
84 // Two squares jump
85 moves.push(this.getBasicMove([x, y], [x + 2 * shift, y]));
86 }
87 }
88 }
89 else {
90 // Promotion
91 let promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
92 promotionPieces.forEach(p => {
93 // Normal move
94 if (this.board[x + shift][y] == V.EMPTY)
95 moves.push(
96 this.getBasicMove([x, y], [x + shift, y], { c: color, p: p })
97 );
98 });
99 }
100
101 // No en passant here
102
103 return moves;
104 }
105
106 getPotentialRookMoves(sq) {
107 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
108 }
109
110 getPotentialKnightMoves(sq) {
111 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
112 }
113
114 getPotentialBishopMoves(sq) {
115 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
116 }
117
118 getPotentialQueenMoves(sq) {
119 return this.getSlideNJumpMoves(
120 sq,
121 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
122 );
123 }
124
125 getPotentialKingMoves(sq) {
126 // Initialize with normal (non-capturing) moves
127 let noCaptures = this.getSlideNJumpMoves(
128 sq,
129 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
130 "oneStep"
131 );
132 return noCaptures.concat(this.getCastleMoves(sq));
133 }
134
135 // No "under check" verifications:
136 getCastleMoves([x, y]) {
137 const c = this.getColor(x, y);
138 if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
139 return []; //x isn't first rank, or king has moved (shortcut)
140
141 // Castling ?
142 const oppCol = V.GetOppCol(c);
143 let moves = [];
144 let i = 0;
145 // King, then rook:
146 const finalSquares = [
147 [2, 3],
148 [V.size.y - 2, V.size.y - 3]
149 ];
150 castlingCheck: for (
151 let castleSide = 0;
152 castleSide < 2;
153 castleSide++ //large, then small
154 ) {
155 if (this.castleFlags[c][castleSide] >= 8) continue;
156 // If this code is reached, rooks and king are on initial position
157
158 const rookPos = this.castleFlags[c][castleSide];
159 if (this.getColor(x, rookPos) != c)
160 // Rook is here but changed color
161 continue;
162
163 // Nothing on the path of the king ?
164 const finDist = finalSquares[castleSide][0] - y;
165 let step = finDist / Math.max(1, Math.abs(finDist));
166 for (let i = y; i != finalSquares[castleSide][0]; i += step) {
167 if (
168 this.board[x][i] != V.EMPTY &&
169 // NOTE: next check is enough, because of chessboard constraints
170 (this.getColor(x, i) != c ||
171 ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
172 ) {
173 continue castlingCheck;
174 }
175 }
176
177 // Nothing on the path to the rook?
178 step = castleSide == 0 ? -1 : 1;
179 for (i = y + step; i != rookPos; i += step) {
180 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
181 }
182
183 // Nothing on final squares, except maybe king and castling rook?
184 for (i = 0; i < 2; i++) {
185 if (
186 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
187 this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
188 finalSquares[castleSide][i] != rookPos
189 ) {
190 continue castlingCheck;
191 }
192 }
193
194 // If this code is reached, castle is valid
195 moves.push(
196 new Move({
197 appear: [
198 new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
199 new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
200 ],
201 vanish: [
202 new PiPo({ x: x, y: y, p: V.KING, c: c }),
203 new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
204 ],
205 end:
206 Math.abs(y - rookPos) <= 2
207 ? { x: x, y: rookPos }
208 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
209 })
210 );
211 }
212
213 return moves;
214 }
215
216 // TODO: appear/vanish description of a move is too verbose for Benedict.
217 // => Would need a new "flipped" array, to be passed in Game.vue...
218 getPotentialMovesFrom([x, y]) {
219 const color = this.turn;
220 const oppCol = V.GetOppCol(color);
221 // Get all moves from x,y without captures:
222 let moves = super.getPotentialMovesFrom([x, y]);
223 // Add flips:
224 moves.forEach(m => {
225 let newAppear = [];
226 let newVanish = [];
227 V.PlayOnBoard(this.board, m);
228 // If castling, m.appear has 2 elements.
229 // In this case, consider the attacks of moving units only.
230 // (Sometimes the king or rook doesn't move).
231 for (let i = 0; i < m.appear.length; i++) {
232 const a = m.appear[i];
233 if (m.vanish[i].x != a.x || m.vanish[i].y != a.y) {
234 const flipped = this.findCaptures([a.x, a.y]);
235 flipped.forEach(sq => {
236 const piece = this.getPiece(sq[0],sq[1]);
237 const pipoA = new PiPo({
238 x:sq[0],
239 y:sq[1],
240 c:color,
241 p:piece
242 });
243 const pipoV = new PiPo({
244 x:sq[0],
245 y:sq[1],
246 c:oppCol,
247 p:piece
248 });
249 newAppear.push(pipoA);
250 newVanish.push(pipoV);
251 });
252 }
253 }
254 Array.prototype.push.apply(m.appear, newAppear);
255 Array.prototype.push.apply(m.vanish, newVanish);
256 V.UndoOnBoard(this.board, m);
257 });
258 return moves;
259 }
260
261 // Moves cannot flip our king's color, so all are valid
262 filterValid(moves) {
263 return moves;
264 }
265
266 // No notion of check here:
267 getCheckSquares() {
268 return [];
269 }
270
271 // Stop at the first move found
272 atLeastOneMove() {
273 const color = this.turn;
274 const oppCol = V.GetOppCol(color);
275 for (let i = 0; i < V.size.x; i++) {
276 for (let j = 0; j < V.size.y; j++) {
277 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
278 const moves = this.getPotentialMovesFrom([i, j]);
279 if (moves.length > 0)
280 return true;
281 }
282 }
283 }
284 return false;
285 }
286
287 getCurrentScore() {
288 const color = this.turn;
289 // Did a king change color?
290 const kp = this.kingPos[color];
291 if (this.getColor(kp[0], kp[1]) != color)
292 return color == "w" ? "0-1" : "1-0";
293 if (this.atLeastOneMove())
294 return "*";
295 // Stalemate:
296 return "1/2";
297 }
298
299 getNotation(move) {
300 // Just remove flips:
301 const basicMove = {
302 appear: [move.appear[0]],
303 vanish: [move.vanish[0]],
304 start: move.start,
305 end: move.end
306 };
307 return super.getNotation(basicMove);
308 }
309 };