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