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