Update TODO
[vchess.git] / client / src / variants / Hidden.js
CommitLineData
b9139251 1import { ChessRules, PiPo, Move } from "@/base_rules";
241bf8f2
BA
2import { ArrayFun } from "@/utils/array";
3import { randInt } from "@/utils/alea";
4
32f6285e 5export class HiddenRules extends ChessRules {
241bf8f2
BA
6 static get HasFlags() {
7 return false;
8 }
9
10 static get HasEnpassant() {
11 return false;
12 }
13
00eef1ca
BA
14 static get SomeHiddenMoves() {
15 return true;
16 }
17
241bf8f2
BA
18 // Analyse in Hidden mode makes no sense
19 static get CanAnalyze() {
20 return false;
21 }
22
57eb158f 23 // Moves are revealed only when game ends, but are highlighted on board
241bf8f2 24 static get ShowMoves() {
57eb158f 25 return "highlight";
241bf8f2
BA
26 }
27
28 static get HIDDEN_DECODE() {
29 return {
30 s: "p",
31 t: "q",
32 u: "r",
33 c: "b",
34 o: "n",
35 l: "k"
36 };
37 }
38 static get HIDDEN_CODE() {
39 return {
40 p: "s",
41 q: "t",
42 r: "u",
43 b: "c",
44 n: "o",
45 k: "l"
46 };
47 }
48
1cd3e362
BA
49 // Turn a hidden piece or revealed piece into revealed piece:
50 static Decode(p) {
51 if (Object.keys(V.HIDDEN_DECODE).includes(p))
52 return V.HIDDEN_DECODE[p];
53 return p;
54 }
55
241bf8f2 56 static get PIECES() {
6f2f9437 57 return ChessRules.PIECES.concat(Object.keys(V.HIDDEN_DECODE));
241bf8f2
BA
58 }
59
b9139251
BA
60 // Pieces can be hidden :)
61 getPiece(i, j) {
62 const piece = this.board[i][j].charAt(1);
63 if (Object.keys(V.HIDDEN_DECODE).includes(piece))
64 return V.HIDDEN_DECODE[piece];
65 return piece;
66 }
67
e71161fb
BA
68 getPpath(b, color, score) {
69 if (Object.keys(V.HIDDEN_DECODE).includes(b[1])) {
70 // Supposed to be hidden.
71 if (score == "*" && (!color || color != b[0]))
72 return "Hidden/" + b[0] + "p";
73 // Else: condition OK to show the piece
74 return b[0] + V.HIDDEN_DECODE[b[1]];
75 }
76 // The piece is already not supposed to be hidden:
77 return b;
78 }
79
241bf8f2 80 // Scan board for kings positions (no castling)
3a2a7b5f 81 scanKings(fen) {
241bf8f2
BA
82 this.kingPos = { w: [-1, -1], b: [-1, -1] };
83 const fenRows = V.ParseFen(fen).position.split("/");
84 for (let i = 0; i < fenRows.length; i++) {
85 let k = 0; //column index on board
86 for (let j = 0; j < fenRows[i].length; j++) {
87 switch (fenRows[i].charAt(j)) {
88 case "k":
89 case "l":
90 this.kingPos["b"] = [i, k];
91 break;
92 case "K":
93 case "L":
94 this.kingPos["w"] = [i, k];
95 break;
96 default: {
e50a8025 97 const num = parseInt(fenRows[i].charAt(j), 10);
241bf8f2
BA
98 if (!isNaN(num)) k += num - 1;
99 }
100 }
101 k++;
102 }
103 }
104 }
105
b9139251 106 getBasicMove([sx, sy], [ex, ey], tr) {
1cd3e362
BA
107 if (
108 tr &&
109 Object.keys(V.HIDDEN_DECODE).includes(this.board[sx][sy].charAt(1))
110 ) {
111 // The transformed piece is a priori hidden
112 tr.p = V.HIDDEN_CODE[tr.p];
113 }
b9139251
BA
114 let mv = new Move({
115 appear: [
116 new PiPo({
117 x: ex,
118 y: ey,
119 c: tr ? tr.c : this.getColor(sx, sy),
120 p: tr ? tr.p : this.board[sx][sy].charAt(1)
121 })
122 ],
123 vanish: [
124 new PiPo({
125 x: sx,
126 y: sy,
127 c: this.getColor(sx, sy),
128 p: this.board[sx][sy].charAt(1)
129 })
130 ]
131 });
132
133 // The opponent piece disappears if we take it
134 if (this.board[ex][ey] != V.EMPTY) {
135 mv.vanish.push(
136 new PiPo({
137 x: ex,
138 y: ey,
139 c: this.getColor(ex, ey),
140 p: this.board[ex][ey].charAt(1)
141 })
142 );
143 // Pieces are revealed when they capture
1cd3e362 144 mv.appear[0].p = V.Decode(mv.appear[0].p);
b9139251 145 }
1cd3e362 146
b9139251
BA
147 return mv;
148 }
149
1cd3e362
BA
150 filterValid(moves) {
151 return moves;
152 }
153
7ba4a5bc 154 // Ignore randomness here: placement is always random asymmetric
241bf8f2
BA
155 static GenRandInitFen() {
156 let pieces = { w: new Array(8), b: new Array(8) };
157 // Shuffle pieces + pawns on two first ranks
158 for (let c of ["w", "b"]) {
159 let positions = ArrayFun.range(16);
160
161 // Get random squares for bishops
162 let randIndex = 2 * randInt(8);
163 const bishop1Pos = positions[randIndex];
164 // The second bishop must be on a square of different color
165 let randIndex_tmp = 2 * randInt(8) + 1;
166 const bishop2Pos = positions[randIndex_tmp];
167 // Remove chosen squares
168 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
169 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
170
171 // Get random squares for knights
172 randIndex = randInt(14);
173 const knight1Pos = positions[randIndex];
174 positions.splice(randIndex, 1);
175 randIndex = randInt(13);
176 const knight2Pos = positions[randIndex];
177 positions.splice(randIndex, 1);
178
b9139251 179 // Get random squares for rooks
241bf8f2 180 randIndex = randInt(12);
b9139251
BA
181 const rook1Pos = positions[randIndex];
182 positions.splice(randIndex, 1);
183 randIndex = randInt(11);
184 const rook2Pos = positions[randIndex];
185 positions.splice(randIndex, 1);
186
187 // Get random square for queen
188 randIndex = randInt(10);
241bf8f2
BA
189 const queenPos = positions[randIndex];
190 positions.splice(randIndex, 1);
191
1cd3e362 192 // Get random square for king
b9139251
BA
193 randIndex = randInt(9);
194 const kingPos = positions[randIndex];
195 positions.splice(randIndex, 1);
241bf8f2 196
b9139251
BA
197 // Pawns position are all remaining slots:
198 for (let p of positions)
199 pieces[c][p] = "s";
241bf8f2
BA
200
201 // Finally put the shuffled pieces in the board array
b9139251
BA
202 pieces[c][rook1Pos] = "u";
203 pieces[c][knight1Pos] = "o";
204 pieces[c][bishop1Pos] = "c";
205 pieces[c][queenPos] = "t";
206 pieces[c][kingPos] = "l";
207 pieces[c][bishop2Pos] = "c";
208 pieces[c][knight2Pos] = "o";
209 pieces[c][rook2Pos] = "u";
241bf8f2 210 }
b9139251 211 let upFen = pieces["b"].join("");
2c5d7b20
BA
212 upFen = upFen.substr(0,8) + "/" +
213 upFen.substr(8).split("").reverse().join("");
b9139251 214 let downFen = pieces["b"].join("").toUpperCase();
2c5d7b20
BA
215 downFen = downFen.substr(0,8) + "/" +
216 downFen.substr(8).split("").reverse().join("");
b9139251 217 return upFen + "/8/8/8/8/" + downFen + " w 0";
241bf8f2
BA
218 }
219
220 getCheckSquares() {
221 return [];
222 }
223
3a2a7b5f
BA
224 postPlay(move) {
225 super.postPlay(move);
241bf8f2
BA
226 if (
227 move.vanish.length >= 2 &&
228 [V.KING,V.HIDDEN_CODE[V.KING]].includes(move.vanish[1].p)
229 ) {
230 // We took opponent king
231 this.kingPos[this.turn] = [-1, -1];
232 }
233 }
234
3a2a7b5f
BA
235 postUndo(move) {
236 super.postUndo(move);
241bf8f2
BA
237 const c = move.vanish[0].c;
238 const oppCol = V.GetOppCol(c);
239 if (this.kingPos[oppCol][0] < 0)
240 // Last move took opponent's king:
241 this.kingPos[oppCol] = [move.vanish[1].x, move.vanish[1].y];
242 }
243
244 getCurrentScore() {
245 const color = this.turn;
246 const kp = this.kingPos[color];
247 if (kp[0] < 0)
248 // King disappeared
249 return color == "w" ? "0-1" : "1-0";
250 // Assume that stalemate is impossible:
251 return "*";
252 }
253
254 getComputerMove() {
71ef1664
BA
255 const color = this.turn;
256 let moves = this.getAllValidMoves();
257 for (let move of moves) {
258 move.eval = 0; //a priori...
259
260 // Can I take something ? If yes, do it with some probability
261 if (move.vanish.length == 2 && move.vanish[1].c != color) {
262 // OK this isn't a castling move
263 const myPieceVal = V.VALUES[move.appear[0].p];
2c5d7b20
BA
264 const hisPieceVal =
265 Object.keys(V.HIDDEN_DECODE).includes(move.vanish[1].p)
266 ? undefined
267 : V.VALUES[move.vanish[1].p];
71ef1664
BA
268 if (!hisPieceVal) {
269 // Opponent's piece is unknown: do not take too much risk
270 move.eval = -myPieceVal + 1.5; //so that pawns always take
271 }
272 // Favor captures
273 else if (myPieceVal <= hisPieceVal)
274 move.eval = hisPieceVal - myPieceVal + 1;
275 else {
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 move.eval = hisPieceVal - myPieceVal;
281 }
282 } else {
283 // If no capture, favor small step moves,
284 // but sometimes move the knight anyway
285 const penalty = V.Decode(move.vanish[0].p) != V.KNIGHT
2c5d7b20
BA
286 ? Math.abs(move.end.x - move.start.x) +
287 Math.abs(move.end.y - move.start.y)
71ef1664
BA
288 : (Math.random() < 0.5 ? 3 : 1);
289 move.eval -= penalty / (V.size.x + V.size.y - 1);
290 }
291
292 // TODO: also favor movements toward the center?
293 }
294
295 moves.sort((a, b) => b.eval - a.eval);
296 let candidates = [0];
297 for (let j = 1; j < moves.length && moves[j].eval == moves[0].eval; j++)
298 candidates.push(j);
299 return moves[candidates[randInt(candidates.length)]];
241bf8f2 300 }
1cd3e362
BA
301
302 getNotation(move) {
303 // Translate final square
304 const finalSquare = V.CoordsToSquare(move.end);
305
306 const piece = this.getPiece(move.start.x, move.start.y);
307 if (piece == V.PAWN) {
308 // Pawn move
309 let notation = "";
310 if (move.vanish.length > move.appear.length) {
311 // Capture
312 const startColumn = V.CoordToColumn(move.start.y);
313 notation = startColumn + "x" + finalSquare;
314 }
315 else notation = finalSquare;
316 if (move.appear.length > 0 && !["p","s"].includes(move.appear[0].p)) {
317 // Promotion
318 const appearPiece = V.Decode(move.appear[0].p);
319 notation += "=" + appearPiece.toUpperCase();
320 }
321 return notation;
322 }
323 // Piece movement
324 return (
325 piece.toUpperCase() +
326 (move.vanish.length > move.appear.length ? "x" : "") +
327 finalSquare
328 );
329 }
241bf8f2 330};