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