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