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