Commit | Line | Data |
---|---|---|
b9139251 | 1 | import { ChessRules, PiPo, Move } from "@/base_rules"; |
241bf8f2 BA |
2 | import { ArrayFun } from "@/utils/array"; |
3 | import { randInt } from "@/utils/alea"; | |
4 | ||
32f6285e | 5 | export class HiddenRules extends ChessRules { |
7e8a7ea1 | 6 | |
4313762d BA |
7 | static get Options() { |
8 | // TODO: later, option "free placement" | |
9 | return null; | |
10 | } | |
11 | ||
241bf8f2 BA |
12 | static get HasFlags() { |
13 | return false; | |
14 | } | |
15 | ||
16 | static get HasEnpassant() { | |
17 | return false; | |
18 | } | |
19 | ||
00eef1ca BA |
20 | static get SomeHiddenMoves() { |
21 | return true; | |
22 | } | |
23 | ||
241bf8f2 BA |
24 | // Analyse in Hidden mode makes no sense |
25 | static get CanAnalyze() { | |
26 | return false; | |
27 | } | |
28 | ||
57eb158f | 29 | // Moves are revealed only when game ends, but are highlighted on board |
241bf8f2 | 30 | static get ShowMoves() { |
57eb158f | 31 | return "highlight"; |
241bf8f2 BA |
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 | ||
1cd3e362 BA |
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 | ||
241bf8f2 | 62 | static get PIECES() { |
6f2f9437 | 63 | return ChessRules.PIECES.concat(Object.keys(V.HIDDEN_DECODE)); |
241bf8f2 BA |
64 | } |
65 | ||
b9139251 BA |
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 | ||
e71161fb BA |
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 | ||
241bf8f2 | 86 | // Scan board for kings positions (no castling) |
3a2a7b5f | 87 | scanKings(fen) { |
241bf8f2 BA |
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: { | |
e50a8025 | 103 | const num = parseInt(fenRows[i].charAt(j), 10); |
241bf8f2 BA |
104 | if (!isNaN(num)) k += num - 1; |
105 | } | |
106 | } | |
107 | k++; | |
108 | } | |
109 | } | |
110 | } | |
111 | ||
b9139251 | 112 | getBasicMove([sx, sy], [ex, ey], tr) { |
1cd3e362 BA |
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 | } | |
b9139251 BA |
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 | |
1cd3e362 | 150 | mv.appear[0].p = V.Decode(mv.appear[0].p); |
b9139251 | 151 | } |
1cd3e362 | 152 | |
b9139251 BA |
153 | return mv; |
154 | } | |
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(""); |
2c5d7b20 BA |
218 | upFen = upFen.substr(0,8) + "/" + |
219 | upFen.substr(8).split("").reverse().join(""); | |
b9139251 | 220 | let downFen = pieces["b"].join("").toUpperCase(); |
2c5d7b20 BA |
221 | downFen = downFen.substr(0,8) + "/" + |
222 | downFen.substr(8).split("").reverse().join(""); | |
b9139251 | 223 | return upFen + "/8/8/8/8/" + downFen + " w 0"; |
241bf8f2 BA |
224 | } |
225 | ||
226 | getCheckSquares() { | |
227 | return []; | |
228 | } | |
229 | ||
3a2a7b5f BA |
230 | postPlay(move) { |
231 | super.postPlay(move); | |
241bf8f2 BA |
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 | ||
3a2a7b5f BA |
241 | postUndo(move) { |
242 | super.postUndo(move); | |
241bf8f2 BA |
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() { | |
71ef1664 BA |
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]; | |
2c5d7b20 BA |
270 | const hisPieceVal = |
271 | Object.keys(V.HIDDEN_DECODE).includes(move.vanish[1].p) | |
272 | ? undefined | |
273 | : V.VALUES[move.vanish[1].p]; | |
71ef1664 BA |
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 | |
2c5d7b20 BA |
292 | ? Math.abs(move.end.x - move.start.x) + |
293 | Math.abs(move.end.y - move.start.y) | |
71ef1664 BA |
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)]]; | |
241bf8f2 | 306 | } |
1cd3e362 BA |
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 | } | |
7e8a7ea1 | 336 | |
241bf8f2 | 337 | }; |