Fix Koopa promotions with captures, and Balakhlava: pawns move forward
[vchess.git] / client / src / variants / Dark.js
CommitLineData
0c3fe8a6 1import { ChessRules } from "@/base_rules";
6808d7a1 2import { ArrayFun } from "@/utils/array";
0c3fe8a6
BA
3import { randInt } from "@/utils/alea";
4
32f6285e 5export class DarkRules extends ChessRules {
20620465 6 // Analyse in Dark mode makes no sense
8477e53d 7 static get CanAnalyze() {
20620465
BA
8 return false;
9 }
10
11 // Moves are revealed only when game ends
12 static get ShowMoves() {
13 return "none";
14 }
15
00eef1ca
BA
16 static get SomeHiddenMoves() {
17 return true;
18 }
19
6808d7a1 20 setOtherVariables(fen) {
dac39588 21 super.setOtherVariables(fen);
6808d7a1 22 const [sizeX, sizeY] = [V.size.x, V.size.y];
dac39588 23 this.enlightened = {
6808d7a1
BA
24 w: ArrayFun.init(sizeX, sizeY),
25 b: ArrayFun.init(sizeX, sizeY)
dac39588
BA
26 };
27 // Setup enlightened: squares reachable by each side
28 // (TODO: one side would be enough ?)
29 this.updateEnlightened();
30 }
375ecdd1 31
6808d7a1
BA
32 updateEnlightened() {
33 for (let i = 0; i < V.size.x; i++) {
34 for (let j = 0; j < V.size.y; j++) {
dac39588
BA
35 this.enlightened["w"][i][j] = false;
36 this.enlightened["b"][i][j] = false;
37 }
38 }
6808d7a1 39 const pawnShift = { w: -1, b: 1 };
dac39588 40 // Initialize with pieces positions (which are seen)
6808d7a1
BA
41 for (let i = 0; i < V.size.x; i++) {
42 for (let j = 0; j < V.size.y; j++) {
43 if (this.board[i][j] != V.EMPTY) {
2c5d7b20
BA
44 const c = this.getColor(i, j);
45 this.enlightened[c][i][j] = true;
dac39588 46 // Add potential squares visible by "impossible pawn capture"
6808d7a1
BA
47 if (this.getPiece(i, j) == V.PAWN) {
48 for (let shiftY of [-1, 1]) {
49 if (
2c5d7b20
BA
50 V.OnBoard(i + pawnShift[c], j + shiftY) &&
51 this.board[i + pawnShift[c]][j + shiftY] == V.EMPTY
6808d7a1 52 ) {
2c5d7b20 53 this.enlightened[c][i + pawnShift[c]][j + shiftY] = true;
dac39588
BA
54 }
55 }
56 }
57 }
58 }
59 }
60 const currentTurn = this.turn;
61 this.turn = "w";
62 const movesWhite = this.getAllValidMoves();
63 this.turn = "b";
64 const movesBlack = this.getAllValidMoves();
65 this.turn = currentTurn;
66 for (let move of movesWhite)
67 this.enlightened["w"][move.end.x][move.end.y] = true;
68 for (let move of movesBlack)
69 this.enlightened["b"][move.end.x][move.end.y] = true;
d048c4c9
BA
70 // Include en-passant capturing square if any:
71 let moves = currentTurn == "w" ? movesWhite : movesBlack;
72 for (let m of moves) {
73 if (
74 m.appear[0].p == V.PAWN &&
75 m.vanish.length == 2 &&
76 m.vanish[1].x != m.end.x
77 ) {
78 const psq = m.vanish[1];
79 this.enlightened[currentTurn][psq.x][psq.y] = true;
80 break;
81 }
82 }
dac39588 83 }
375ecdd1 84
c3d16e78
BA
85 filterValid(moves) {
86 // Used in the interface
87 return moves;
88 }
89
dac39588 90 // Has to be redefined to avoid an infinite loop
6808d7a1 91 getAllValidMoves() {
dac39588 92 const color = this.turn;
dac39588 93 let potentialMoves = [];
6808d7a1
BA
94 for (let i = 0; i < V.size.x; i++) {
95 for (let j = 0; j < V.size.y; j++) {
96 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color)
97 Array.prototype.push.apply(
98 potentialMoves,
99 this.getPotentialMovesFrom([i, j])
100 );
dac39588
BA
101 }
102 }
103 return potentialMoves; //because there are no checks
104 }
f6dbe8e3 105
6808d7a1 106 getCheckSquares() {
dac39588
BA
107 return [];
108 }
375ecdd1 109
3a2a7b5f
BA
110 postPlay(move) {
111 super.postPlay(move);
241bf8f2
BA
112 if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
113 // We took opponent king (because if castle vanish[1] is a rook)
6808d7a1 114 this.kingPos[this.turn] = [-1, -1];
388e4c40 115
dac39588
BA
116 // Update lights for both colors:
117 this.updateEnlightened();
118 }
388e4c40 119
3a2a7b5f
BA
120 postUndo(move) {
121 super.postUndo(move);
dac39588
BA
122 const c = move.vanish[0].c;
123 const oppCol = V.GetOppCol(c);
241bf8f2
BA
124 if (this.kingPos[oppCol][0] < 0)
125 // Last move took opponent's king:
126 this.kingPos[oppCol] = [move.vanish[1].x, move.vanish[1].y];
388e4c40 127
dac39588
BA
128 // Update lights for both colors:
129 this.updateEnlightened();
130 }
375ecdd1 131
6808d7a1 132 getCurrentScore() {
dac39588
BA
133 const color = this.turn;
134 const kp = this.kingPos[color];
6808d7a1 135 if (kp[0] < 0)
241bf8f2 136 // King disappeared
6808d7a1 137 return color == "w" ? "0-1" : "1-0";
241bf8f2
BA
138 // Assume that stalemate is impossible (I think so. Would need proof...)
139 return "*";
dac39588 140 }
375ecdd1 141
6808d7a1 142 static get THRESHOLD_MATE() {
dac39588
BA
143 return 500; //checkmates evals may be slightly below 1000
144 }
5915f720 145
dac39588 146 // In this special situation, we just look 1 half move ahead
6808d7a1 147 getComputerMove() {
dac39588
BA
148 const maxeval = V.INFINITY;
149 const color = this.turn;
150 const oppCol = V.GetOppCol(color);
6808d7a1 151 const pawnShift = color == "w" ? -1 : 1;
5915f720 152
dac39588
BA
153 // Do not cheat: the current enlightment is all we can see
154 const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
5915f720 155
dac39588
BA
156 // Can a slider on (i,j) apparently take my king?
157 // NOTE: inaccurate because assume yes if some squares are shadowed
6808d7a1 158 const sliderTake = ([i, j], piece) => {
dac39588
BA
159 const kp = this.kingPos[color];
160 let step = undefined;
6808d7a1
BA
161 if (piece == V.BISHOP) {
162 if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j)) {
163 step = [
164 (i - kp[0]) / Math.abs(i - kp[0]),
165 (j - kp[1]) / Math.abs(j - kp[1])
dac39588
BA
166 ];
167 }
6808d7a1
BA
168 } else if (piece == V.ROOK) {
169 if (kp[0] == i) step = [0, (j - kp[1]) / Math.abs(j - kp[1])];
170 else if (kp[1] == j) step = [(i - kp[0]) / Math.abs(i - kp[0]), 0];
dac39588 171 }
6808d7a1 172 if (!step) return false;
dac39588
BA
173 // Check for obstacles
174 let obstacle = false;
175 for (
6808d7a1 176 let x = kp[0] + step[0], y = kp[1] + step[1];
dac39588 177 x != i && y != j;
6808d7a1
BA
178 x += step[0], y += step[1]
179 ) {
180 if (myLight[x][y] && this.board[x][y] != V.EMPTY) {
dac39588
BA
181 obstacle = true;
182 break;
183 }
184 }
6808d7a1 185 if (!obstacle) return true;
dac39588
BA
186 return false;
187 };
5915f720 188
dac39588
BA
189 // Do I see something which can take my king ?
190 const kingThreats = () => {
191 const kp = this.kingPos[color];
6808d7a1
BA
192 for (let i = 0; i < V.size.x; i++) {
193 for (let j = 0; j < V.size.y; j++) {
194 if (
195 myLight[i][j] &&
196 this.board[i][j] != V.EMPTY &&
197 this.getColor(i, j) != color
198 ) {
199 switch (this.getPiece(i, j)) {
dac39588 200 case V.PAWN:
6808d7a1 201 if (kp[0] + pawnShift == i && Math.abs(kp[1] - j) == 1)
dac39588
BA
202 return true;
203 break;
204 case V.KNIGHT:
6808d7a1
BA
205 if (
206 (Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
207 (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2)
208 ) {
dac39588
BA
209 return true;
210 }
211 break;
212 case V.KING:
213 if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
214 return true;
215 break;
216 case V.BISHOP:
6808d7a1 217 if (sliderTake([i, j], V.BISHOP)) return true;
dac39588
BA
218 break;
219 case V.ROOK:
6808d7a1 220 if (sliderTake([i, j], V.ROOK)) return true;
dac39588
BA
221 break;
222 case V.QUEEN:
6808d7a1 223 if (sliderTake([i, j], V.BISHOP) || sliderTake([i, j], V.ROOK))
dac39588
BA
224 return true;
225 break;
226 }
227 }
228 }
229 }
230 return false;
231 };
5915f720 232
dac39588 233 let moves = this.getAllValidMoves();
6808d7a1 234 for (let move of moves) {
dac39588 235 this.play(move);
6808d7a1 236 if (this.kingPos[oppCol][0] >= 0 && kingThreats()) {
dac39588
BA
237 // We didn't take opponent king, and our king will be captured: bad
238 move.eval = -maxeval;
239 }
240 this.undo(move);
4f518610 241
6808d7a1 242 if (move.eval) continue;
5915f720 243
dac39588 244 move.eval = 0; //a priori...
5915f720 245
dac39588 246 // Can I take something ? If yes, do it if it seems good...
6808d7a1 247 if (move.vanish.length == 2 && move.vanish[1].c != color) {
71ef1664 248 // OK this isn't a castling move
dac39588
BA
249 const myPieceVal = V.VALUES[move.appear[0].p];
250 const hisPieceVal = V.VALUES[move.vanish[1].p];
71ef1664 251 // Favor captures
2c5d7b20
BA
252 if (myPieceVal <= hisPieceVal)
253 move.eval = hisPieceVal - myPieceVal + 1;
6808d7a1 254 else {
dac39588
BA
255 // Taking a pawn with minor piece,
256 // or minor piece or pawn with a rook,
257 // or anything but a queen with a queen,
258 // or anything with a king.
71ef1664
BA
259 move.eval = hisPieceVal - myPieceVal;
260 //Math.random() < 0.5 ? 1 : -1;
dac39588
BA
261 }
262 }
263 }
5915f720 264
dac39588
BA
265 // TODO: also need to implement the case when an opponent piece (in light)
266 // is threatening something - maybe not the king, but e.g. pawn takes rook.
5915f720 267
6808d7a1 268 moves.sort((a, b) => b.eval - a.eval);
dac39588 269 let candidates = [0];
6808d7a1 270 for (let j = 1; j < moves.length && moves[j].eval == moves[0].eval; j++)
dac39588
BA
271 candidates.push(j);
272 return moves[candidates[randInt(candidates.length)]];
273 }
6808d7a1 274};