Add Antiking v1
[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 // No "under check" verifications:
107 getCastleMoves([x, y]) {
108 const c = this.getColor(x, y);
109 if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
110 return []; //x isn't first rank, or king has moved (shortcut)
111
112 // Castling ?
113 const oppCol = V.GetOppCol(c);
114 let moves = [];
115 let i = 0;
116 // King, then rook:
117 const finalSquares = [
118 [2, 3],
119 [V.size.y - 2, V.size.y - 3]
120 ];
121 castlingCheck: for (
122 let castleSide = 0;
123 castleSide < 2;
124 castleSide++ //large, then small
125 ) {
126 if (this.castleFlags[c][castleSide] >= 8) continue;
127 // If this code is reached, rooks and king are on initial position
128
129 const rookPos = this.castleFlags[c][castleSide];
130 if (this.getColor(x, rookPos) != c)
131 // Rook is here but changed color
132 continue;
133
134 // Nothing on the path of the king ?
135 const finDist = finalSquares[castleSide][0] - y;
136 let step = finDist / Math.max(1, Math.abs(finDist));
137 for (let i = y; i != finalSquares[castleSide][0]; i += step) {
138 if (
139 this.board[x][i] != V.EMPTY &&
140 // NOTE: next check is enough, because of chessboard constraints
141 (this.getColor(x, i) != c ||
142 ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
143 ) {
144 continue castlingCheck;
145 }
146 }
147
148 // Nothing on the path to the rook?
149 step = castleSide == 0 ? -1 : 1;
150 for (i = y + step; i != rookPos; i += step) {
151 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
152 }
153
154 // Nothing on final squares, except maybe king and castling rook?
155 for (i = 0; i < 2; i++) {
156 if (
157 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
158 this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
159 finalSquares[castleSide][i] != rookPos
160 ) {
161 continue castlingCheck;
162 }
163 }
164
165 // If this code is reached, castle is valid
166 moves.push(
167 new Move({
168 appear: [
169 new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
170 new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
171 ],
172 vanish: [
173 new PiPo({ x: x, y: y, p: V.KING, c: c }),
174 new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
175 ],
176 end:
177 Math.abs(y - rookPos) <= 2
178 ? { x: x, y: rookPos }
179 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
180 })
181 );
182 }
183
184 return moves;
185 }
186
187 // TODO: appear/vanish description of a move is too verbose for Benedict.
188 // => Would need a new "flipped" array, to be passed in Game.vue...
189 getPotentialMovesFrom([x, y]) {
190 const color = this.turn;
191 const oppCol = V.GetOppCol(color);
192 // Get all moves from x,y without captures:
193 let moves = super.getPotentialMovesFrom([x, y]);
194 // Add flips:
195 moves.forEach(m => {
196 let newAppear = [];
197 let newVanish = [];
198 V.PlayOnBoard(this.board, m);
199 // If castling, m.appear has 2 elements.
200 // In this case, consider the attacks of moving units only.
201 // (Sometimes the king or rook doesn't move).
202 for (let i = 0; i < m.appear.length; i++) {
203 const a = m.appear[i];
204 if (m.vanish[i].x != a.x || m.vanish[i].y != a.y) {
205 const flipped = this.findCaptures([a.x, a.y]);
206 flipped.forEach(sq => {
207 const piece = this.getPiece(sq[0],sq[1]);
208 const pipoA = new PiPo({
209 x:sq[0],
210 y:sq[1],
211 c:color,
212 p:piece
213 });
214 const pipoV = new PiPo({
215 x:sq[0],
216 y:sq[1],
217 c:oppCol,
218 p:piece
219 });
220 newAppear.push(pipoA);
221 newVanish.push(pipoV);
222 });
223 }
224 }
225 Array.prototype.push.apply(m.appear, newAppear);
226 Array.prototype.push.apply(m.vanish, newVanish);
227 V.UndoOnBoard(this.board, m);
228 });
229 return moves;
230 }
231
232 // Moves cannot flip our king's color, so all are valid
233 filterValid(moves) {
234 return moves;
235 }
236
237 // No notion of check here:
238 getCheckSquares() {
239 return [];
240 }
241
242 // Stop at the first move found
243 atLeastOneMove() {
244 const color = this.turn;
245 const oppCol = V.GetOppCol(color);
246 for (let i = 0; i < V.size.x; i++) {
247 for (let j = 0; j < V.size.y; j++) {
248 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
249 const moves = this.getPotentialMovesFrom([i, j]);
250 if (moves.length > 0)
251 return true;
252 }
253 }
254 }
255 return false;
256 }
257
258 getCurrentScore() {
259 const color = this.turn;
260 // Did a king change color?
261 const kp = this.kingPos[color];
262 if (this.getColor(kp[0], kp[1]) != color)
263 return color == "w" ? "0-1" : "1-0";
264 if (this.atLeastOneMove())
265 return "*";
266 // Stalemate:
267 return "1/2";
268 }
269
270 getNotation(move) {
271 // Just remove flips:
272 const basicMove = {
273 appear: [move.appear[0]],
274 vanish: [move.vanish[0]],
275 start: move.start,
276 end: move.end
277 };
278 return super.getNotation(basicMove);
279 }
280 };