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