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