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