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