Commit | Line | Data |
---|---|---|
5b18515f | 1 | import { ChessRules } from "@/base_rules"; |
0e5fe2f0 | 2 | import { randInt, shuffle } from "@/utils/alea"; |
031b3d07 | 3 | import { ArrayFun } from "@/utils/array"; |
5b18515f BA |
4 | |
5 | export class AmbiguousRules extends ChessRules { | |
7e8a7ea1 | 6 | |
5b18515f BA |
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) | |
ff3a8d16 | 24 | let movesHash = {}; |
5b18515f BA |
25 | return ( |
26 | super.getPotentialMovesFrom([x, y]) | |
ff3a8d16 BA |
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 | }) | |
5b18515f BA |
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++) { | |
a15bd0da | 137 | const colIJ = this.getColor(i, j); |
5b18515f BA |
138 | if ( |
139 | this.board[i][j] != V.EMPTY && | |
a15bd0da BA |
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 | ) | |
5b18515f BA |
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 | ||
4313762d BA |
235 | static GenRandInitFen(options) { |
236 | if (options.randomness == 0) | |
5b18515f BA |
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"]) { | |
4313762d | 241 | if (c == 'b' && options.randomness == 1) { |
5b18515f BA |
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++) { | |
5d74fcea | 253 | if (positions[i] % 2 != rem2) { |
5b18515f | 254 | [positions[1], positions[i]] = [positions[i], positions[1]]; |
5d74fcea BA |
255 | break; |
256 | } | |
5b18515f BA |
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 | ||
a15bd0da BA |
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 | ||
5b18515f BA |
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 | } | |
7e8a7ea1 | 298 | |
5b18515f | 299 | }; |