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