Commit | Line | Data |
---|---|---|
0c3fe8a6 BA |
1 | import { ChessRules } from "@/base_rules"; |
2 | import { ArrayFun} from "@/utils/array"; | |
3 | import { randInt } from "@/utils/alea"; | |
4 | ||
5 | export 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 | } |