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