Fix Benedict rules
[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 // TODO: appear/vanish description of a move is too verbose for Benedict.
136 // => Would need a new "flipped" array, to be passed in Game.vue...
137 getPotentialMovesFrom([x, y]) {
138 const color = this.turn;
139 const oppCol = V.GetOppCol(color);
140 // Get all moves from x,y without captures:
141 let moves = super.getPotentialMovesFrom([x, y]);
142 // Add flips:
143 moves.forEach(m => {
144 let newAppear = [];
145 let newVanish = [];
146 V.PlayOnBoard(this.board, m);
147 // If castling, m.appear has 2 elements:
148 m.appear.forEach(a => {
149 const flipped = this.findCaptures([a.x, a.y]);
150 flipped.forEach(sq => {
151 const piece = this.getPiece(sq[0],sq[1]);
152 const pipoA = new PiPo({
153 x:sq[0],
154 y:sq[1],
155 c:color,
156 p:piece
157 });
158 const pipoV = new PiPo({
159 x:sq[0],
160 y:sq[1],
161 c:oppCol,
162 p:piece
163 });
164 newAppear.push(pipoA);
165 newVanish.push(pipoV);
166 });
167 });
168 Array.prototype.push.apply(m.appear, newAppear);
169 Array.prototype.push.apply(m.vanish, newVanish);
170 V.UndoOnBoard(this.board, m);
171 });
172 return moves;
173 }
174
175 // Moves cannot flip our king's color, so all are valid
176 filterValid(moves) {
177 return moves;
178 }
179
180 // No notion of check here:
181 getCheckSquares() {
182 return [];
183 }
184
185 // Stop at the first move found
186 atLeastOneMove() {
187 const color = this.turn;
188 const oppCol = V.GetOppCol(color);
189 for (let i = 0; i < V.size.x; i++) {
190 for (let j = 0; j < V.size.y; j++) {
191 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
192 const moves = this.getPotentialMovesFrom([i, j]);
193 if (moves.length > 0)
194 return true;
195 }
196 }
197 }
198 return false;
199 }
200
201 getCurrentScore() {
202 const color = this.turn;
203 // Did a king change color?
204 const kp = this.kingPos[color];
205 if (this.getColor(kp[0], kp[1]) != color)
206 return color == "w" ? "0-1" : "1-0";
207 if (this.atLeastOneMove())
208 return "*";
209 // Stalemate:
210 return "1/2";
211 }
212 };