Implemented a very basic DarkBot + a few fixes + advance on rules
[vchess.git] / public / javascripts / 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 // Initialize with pieces positions (which are seen)
20 for (let i=0; i<V.size.x; i++)
21 {
22 for (let j=0; j<V.size.y; j++)
23 {
24 this.enlightened["w"][i][j] = false;
25 this.enlightened["b"][i][j] = false;
26 if (this.board[i][j] != V.EMPTY)
27 this.enlightened[this.getColor(i,j)][i][j] = true;
28 }
29 }
30 const currentTurn = this.turn;
31 this.turn = "w";
32 const movesWhite = this.getAllValidMoves();
33 this.turn = "b";
34 const movesBlack = this.getAllValidMoves();
35 this.turn = currentTurn;
36 for (let move of movesWhite)
37 this.enlightened["w"][move.end.x][move.end.y] = true;
38 for (let move of movesBlack)
39 this.enlightened["b"][move.end.x][move.end.y] = true;
375ecdd1
BA
40 }
41
f6dbe8e3
BA
42 // Has to be redefined to avoid an infinite loop
43 getAllValidMoves()
44 {
45 const color = this.turn;
46 const oppCol = this.getOppCol(color);
47 let potentialMoves = [];
48 for (let i=0; i<V.size.x; i++)
49 {
50 for (let j=0; j<V.size.y; j++)
51 {
52 if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
53 Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
54 }
55 }
56 return potentialMoves; //because there are no checks
57 }
58
375ecdd1
BA
59 atLeastOneMove()
60 {
61 if (this.kingPos[this.turn][0] < 0)
62 return false;
63 return true; //TODO: is it right?
64 }
65
f6dbe8e3 66 underCheck(color)
375ecdd1
BA
67 {
68 return false; //there is no check
69 }
70
f6dbe8e3 71 getCheckSquares(color)
375ecdd1 72 {
f6dbe8e3 73 return [];
375ecdd1
BA
74 }
75
388e4c40
BA
76 updateVariables(move)
77 {
78 // Update kings positions
79 const piece = move.vanish[0].p;
80 const c = move.vanish[0].c;
81 if (piece == V.KING && move.appear.length > 0)
82 {
83 this.kingPos[c][0] = move.appear[0].x;
84 this.kingPos[c][1] = move.appear[0].y;
85 }
86 if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
87 {
88 // We took opponent king !
89 const oppCol = this.getOppCol(c);
90 this.kingPos[oppCol] = [-1,-1];
91 }
92
93 // Update moves for both colors:
94 this.updateEnlightened();
95 }
96
97 unupdateVariables(move)
98 {
99 super.unupdateVariables(move);
100 const c = move.vanish[0].c;
101 const oppCol = this.getOppCol(c);
102 if (this.kingPos[oppCol][0] < 0)
103 {
104 // Last move took opponent's king
105 for (let psq of move.vanish)
106 {
107 if (psq.p == 'k')
108 {
109 this.kingPos[oppCol] = [psq.x, psq.y];
110 break;
111 }
112 }
113 }
114
115 // Update moves for both colors:
116 this.updateEnlightened();
117 }
375ecdd1
BA
118
119 checkGameEnd()
120 {
121 // No valid move: our king disappeared
122 return this.turn == "w" ? "0-1" : "1-0";
123 }
124
125 static get THRESHOLD_MATE()
126 {
127 return 500; //checkmates evals may be slightly below 1000
128 }
5915f720
BA
129
130 // In this special situation, we just look 1 half move ahead
131 getComputerMove()
132 {
133 const maxeval = V.INFINITY;
134 const color = this.turn;
135 const oppCol = this.getOppCol(color);
136 const pawnShift = (color == "w" ? -1 : 1);
137 const kp = this.kingPos[color];
138
139 // Do not cheat: the current enlightment is all we can see
140 const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
141
142 // Can a slider on (i,j) apparently take my king?
143 // NOTE: inaccurate because assume yes if some squares are shadowed
144 const sliderTake = ([i,j], piece) => {
145 let step = undefined;
146 if (piece == V.BISHOP)
147 {
148 if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j))
149 {
150 step =
151 [
152 (i-kp[0]) / Math.abs(i-kp[0]),
153 (j-kp[1]) / Math.abs(j-kp[1])
154 ];
155 }
156 }
157 else if (piece == V.ROOK)
158 {
159 if (kp[0] == i)
160 step = [0, (j-kp[1]) / Math.abs(j-kp[1])];
161 else if (kp[1] == j)
162 step = [(i-kp[0]) / Math.abs(i-kp[0]), 0];
163 }
164 if (!step)
165 return false;
166 // Check for obstacles
167 let obstacle = false;
168 for (
169 let x=kp[0]+step[0], y=kp[1]+step[1];
170 x != i && y != j;
171 x += step[0], y+= step[1])
172 {
173 if (myLight[x][y] && this.board[x][y] != V.EMPTY)
174 {
175 obstacle = true;
176 break;
177 }
178 }
179 if (!obstacle)
180 return true;
181 return false;
182 };
183
184 // Do I see something which can take my king ?
185 const kingThreats = () => {
186 for (let i=0; i<V.size.x; i++)
187 {
188 for (let j=0; j<V.size.y; j++)
189 {
190 if (myLight[i][j] && this.board[i][j] != V.EMPTY
191 && this.getColor(i,j) != color)
192 {
193 switch (this.getPiece(i,j))
194 {
195 case V.PAWN:
196 if (kp[0] + pawnShift == i && Math.abs(kp[1]-j) == 1)
197 return true;
198 break;
199 case V.KNIGHT:
200 if ((Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
201 (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2))
202 {
203 return true;
204 }
205 break;
206 case V.KING:
207 if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
208 return true;
209 break;
210 case V.BISHOP:
211 if (sliderTake([i,j], V.BISHOP))
212 return true;
213 break;
214 case V.ROOK:
215 if (sliderTake([i,j], V.ROOK))
216 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 {
232 this.play(move);
233 if (this.kingPos[oppCol][0] >= 0 && kingThreats())
234 {
235 // We didn't take opponent king, and our king will be captured: bad
236 move.eval = -maxeval;
237 }
238 this.undo(move);
239 if (!!move.eval)
240 continue;
241
242 move.eval = 0; //a priori...
243
244 // Can I take something ? If yes, do it if it seems good...
245 if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle
246 {
247 const myPieceVal = V.VALUES[move.appear[0].p];
248 const hisPieceVal = V.VALUES[move.vanish[1].p];
249 if (myPieceVal <= hisPieceVal)
250 move.eval = hisPieceVal - myPieceVal + 2; //favor captures
251 else
252 {
253 // Taking a pawn with minor piece,
254 // or minor piece or pawn with a rook,
255 // or anything but a queen with a queen,
256 // or anything with a king.
257 // ==> Do it at random, although
258 // this is clearly inferior to what a human can deduce...
259 move.eval = (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[_.sample(candidates, 1)];
272 }
375ecdd1
BA
273}
274
275const VariantRules = DarkRules;