Improve autoplay, debug move reception while autoplay and/or analyze is on. Add Ambig...
[vchess.git] / client / src / variants / Ambiguous.js
1 import { ChessRules } from "@/base_rules";
2
3 export class AmbiguousRules extends ChessRules {
4 static get HasFlags() {
5 return false;
6 }
7
8 setOtherVariables(fen) {
9 super.setOtherVariables(fen);
10 if (this.movesCount == 0) this.subTurn = 2;
11 else this.subTurn = 1;
12 }
13
14 // Subturn 1: play a move for the opponent on the designated square.
15 // Subturn 2: play a move for me (which just indicate a square).
16 getPotentialMovesFrom([x, y]) {
17 const color = this.turn;
18 const oppCol = V.GetOppCol(color);
19 if (this.subTurn == 2) {
20 // Just play a normal move (which in fact only indicate a square)
21 return (
22 super.getPotentialMovesFrom([x, y])
23 .map(m => {
24 if (m.vanish.length == 1) m.appear[0].p = V.GOAL;
25 else m.appear[0].p = V.TARGET_CODE[m.vanish[1].p];
26 m.appear[0].c = oppCol;
27 m.vanish.shift();
28 return m;
29 })
30 );
31 }
32 // At subTurn == 1, play a targeted move for opponent
33 // Search for target (we could also have it in a stack...)
34 let target = { x: -1, y: -1 };
35 outerLoop: for (let i = 0; i < V.size.x; i++) {
36 for (let j = 0; j < V.size.y; j++) {
37 if (this.board[i][j] != V.EMPTY) {
38 const piece = this.board[i][j][1];
39 if (
40 piece == V.GOAL ||
41 Object.keys(V.TARGET_DECODE).includes(piece)
42 ) {
43 target = { x: i, y: j};
44 break outerLoop;
45 }
46 }
47 }
48 }
49 // TODO: could be more efficient than generating all moves.
50 this.turn = oppCol;
51 const emptyTarget = (this.board[target.x][target.y][1] == V.GOAL);
52 if (emptyTarget) this.board[target.x][target.y] = V.EMPTY;
53 let moves = super.getPotentialMovesFrom([x, y]);
54 if (emptyTarget) {
55 this.board[target.x][target.y] = color + V.GOAL;
56 moves.forEach(m => {
57 m.vanish.push({
58 x: target.x,
59 y: target.y,
60 c: color,
61 p: V.GOAL
62 });
63 });
64 }
65 this.turn = color;
66 return moves.filter(m => m.end.x == target.x && m.end.y == target.y);
67 }
68
69 canIplay(side, [x, y]) {
70 const color = this.getColor(x, y);
71 return (
72 (this.subTurn == 1 && color != side) ||
73 (this.subTurn == 2 && color == side)
74 );
75 }
76
77 getPpath(b) {
78 if (b[1] == V.GOAL || Object.keys(V.TARGET_DECODE).includes(b[1]))
79 return "Ambiguous/" + b;
80 return b;
81 }
82
83 // Code for empty square target
84 static get GOAL() {
85 return 'g';
86 }
87
88 static get TARGET_DECODE() {
89 return {
90 's': 'p',
91 't': 'q',
92 'u': 'r',
93 'o': 'n',
94 'c': 'b',
95 'l': 'k'
96 };
97 }
98
99 static get TARGET_CODE() {
100 return {
101 'p': 's',
102 'q': 't',
103 'r': 'u',
104 'n': 'o',
105 'b': 'c',
106 'k': 'l'
107 };
108 }
109
110 static get PIECES() {
111 return (
112 ChessRules.PIECES.concat(Object.keys(V.TARGET_DECODE)).concat([V.GOAL])
113 );
114 }
115
116 getAllPotentialMoves() {
117 const color = this.turn;
118 let potentialMoves = [];
119 for (let i = 0; i < V.size.x; i++) {
120 for (let j = 0; j < V.size.y; j++) {
121 if (
122 this.board[i][j] != V.EMPTY &&
123 this.getColor(i, j) == color &&
124 this.board[i][j][1] != V.GOAL &&
125 !(Object.keys(V.TARGET_DECODE).includes(this.board[i][j][1]))
126 ) {
127 Array.prototype.push.apply(
128 potentialMoves,
129 this.getPotentialMovesFrom([i, j])
130 );
131 }
132 }
133 }
134 return potentialMoves;
135 }
136
137 atLeastOneMove() {
138 // Since there are no checks this seems true (same as for Magnetic...)
139 return true;
140 }
141
142 filterValid(moves) {
143 return moves;
144 }
145
146 getCheckSquares() {
147 return [];
148 }
149
150 getCurrentScore() {
151 // This function is only called at subTurn 1
152 const color = V.GetOppCol(this.turn);
153 if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0");
154 return "*";
155 }
156
157 prePlay(move) {
158 const c = V.GetOppCol(this.turn);
159 const piece = move.vanish[0].p;
160 if (piece == V.KING) {
161 // (Opp) king moves:
162 this.kingPos[c][0] = move.appear[0].x;
163 this.kingPos[c][1] = move.appear[0].y;
164 }
165 if (move.vanish.length == 2 && [V.KING, 'l'].includes(move.vanish[1].p))
166 // (My) king is captured:
167 this.kingPos[this.turn] = [-1, -1];
168 }
169
170 play(move) {
171 let kingCaptured = false;
172 if (this.subTurn == 1) {
173 this.prePlay(move);
174 this.epSquares.push(this.getEpSquare(move));
175 kingCaptured = this.kingPos[this.turn][0] < 0;
176 }
177 if (kingCaptured) move.kingCaptured = true;
178 V.PlayOnBoard(this.board, move);
179 if (this.subTurn == 2 || kingCaptured) {
180 this.turn = V.GetOppCol(this.turn);
181 this.movesCount++;
182 }
183 if (!kingCaptured) this.subTurn = 3 - this.subTurn;
184 }
185
186 undo(move) {
187 if (!move.kingCaptured) this.subTurn = 3 - this.subTurn;
188 if (this.subTurn == 2 || !!move.kingCaptured) {
189 this.turn = V.GetOppCol(this.turn);
190 this.movesCount--;
191 }
192 V.UndoOnBoard(this.board, move);
193 if (this.subTurn == 1) {
194 this.epSquares.pop();
195 this.postUndo(move);
196 }
197 }
198
199 postUndo(move) {
200 // (Potentially) Reset king(s) position
201 const c = V.GetOppCol(this.turn);
202 const piece = move.vanish[0].p;
203 if (piece == V.KING) {
204 // (Opp) king moved:
205 this.kingPos[c][0] = move.vanish[0].x;
206 this.kingPos[c][1] = move.vanish[0].y;
207 }
208 if (move.vanish.length == 2 && [V.KING, 'l'].includes(move.vanish[1].p))
209 // (My) king was captured:
210 this.kingPos[this.turn] = [move.vanish[1].x, move.vanish[1].y];
211 }
212
213 static GenRandInitFen(randomness) {
214 if (randomness == 0)
215 return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 -";
216
217 let pieces = { w: new Array(8), b: new Array(8) };
218 for (let c of ["w", "b"]) {
219 if (c == 'b' && randomness == 1) {
220 pieces['b'] = pieces['w'];
221 break;
222 }
223
224 // Get random squares for every piece, totally freely
225 let positions = shuffle(ArrayFun.range(8));
226 const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
227 const rem2 = positions[0] % 2;
228 if (rem2 == positions[1] % 2) {
229 // Fix bishops (on different colors)
230 for (let i=2; i<8; i++) {
231 if (positions[i] % 2 != rem2)
232 [positions[1], positions[i]] = [positions[i], positions[1]];
233 }
234 }
235 for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i];
236 }
237 return (
238 pieces["b"].join("") +
239 "/pppppppp/8/8/8/8/PPPPPPPP/" +
240 pieces["w"].join("").toUpperCase() +
241 // En-passant allowed, but no flags
242 " w 0 -"
243 );
244 }
245
246 getNotation(move) {
247 if (this.subTurn == 2) return "T:" + V.CoordsToSquare(move.end);
248 // Remove and re-add target to get a good notation:
249 const withTarget = move.vanish[1];
250 if (move.vanish[1].p == V.GOAL) move.vanish.pop();
251 else move.vanish[1].p = V.TARGET_DECODE[move.vanish[1].p];
252 const notation = super.getNotation(move);
253 if (move.vanish.length == 1) move.vanish.push(withTarget);
254 else move.vanish[1].p = V.TARGET_CODE[move.vanish[1].p];
255 return notation;
256 }
257 };