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