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