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