Commit | Line | Data |
---|---|---|
10cceb25 | 1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
0c3fe8a6 | 2 | |
10cceb25 BA |
3 | export class AtomicRules extends ChessRules { |
4 | ||
5 | static get Options() { | |
6 | return { | |
7 | check: [ | |
8 | { | |
9 | label: "Balanced", | |
10 | defaut: false, | |
11 | variable: "balanced" | |
12 | } | |
13 | ], | |
14 | select: ChessRules.Options.select | |
15 | }; | |
16 | } | |
17 | ||
18 | static AbbreviateOptions(opts) { | |
19 | return opts["balanced"] ? 'B' : ''; | |
20 | } | |
21 | ||
22 | static GenRandInitFen(options) { | |
23 | return ChessRules.GenRandInitFen(options) + (options.balanced ? " B" : ""); | |
24 | } | |
25 | ||
26 | setOtherVariables(fen) { | |
27 | super.setOtherVariables(fen); | |
28 | this.balanced = !!V.ParseFen(fen).balanced; | |
29 | } | |
30 | ||
31 | static ParseFen(fen) { | |
32 | return Object.assign( | |
33 | { balanced: fen.split(" ")[5] }, | |
34 | ChessRules.ParseFen(fen) | |
35 | ); | |
36 | } | |
37 | ||
38 | static IsGoodFen(fen) { | |
39 | if (!ChessRules.IsGoodFen(fen)) return false; | |
40 | const balanced = V.ParseFen(fen).balanced; | |
41 | return (!balanced || balanced == 'B'); | |
42 | } | |
43 | ||
44 | getFen() { | |
45 | return super.getFen() + (this.balanced ? " B" : ""); | |
46 | } | |
47 | ||
48 | hoverHighlight([x, y]) { | |
49 | return this.balanced && this.movesCount == 0 && [1, 6].includes(x); | |
50 | } | |
51 | ||
52 | canIplay(side, [x, y]) { | |
53 | if (this.balanced && this.movesCount == 0) | |
54 | return (this.turn == side && this.getPiece(x, y) == V.PAWN); | |
55 | return super.canIplay(side, [x, y]); | |
56 | } | |
57 | ||
58 | doClick(square) { | |
59 | if (!this.balanced || this.movesCount >= 1) return null; | |
60 | const [x, y] = [square[0], square[1]]; | |
61 | if (![1, 6].includes(x)) return null; | |
62 | return new Move({ | |
63 | appear: [], | |
64 | vanish: [ | |
65 | new PiPo({ | |
66 | x: x, | |
67 | y: y, | |
68 | c: this.getColor(x, y), | |
69 | p: V.PAWN | |
70 | }) | |
71 | ], | |
72 | start: { x: x, y: y }, | |
73 | end: { x: x, y: y } | |
74 | }); | |
75 | } | |
7e8a7ea1 | 76 | |
6808d7a1 | 77 | getPotentialMovesFrom([x, y]) { |
10cceb25 BA |
78 | if (this.balanced && this.movesCount == 0) { |
79 | if ([1, 6].includes(x)) { | |
80 | const c = this.getColor(x, y); | |
81 | return [ | |
82 | new Move({ | |
83 | appear: [], | |
84 | vanish: [ | |
85 | new PiPo({ x: x, y: y, p: V.PAWN, c: c }) | |
86 | ], | |
87 | start: { x: x, y: y }, | |
88 | end: { x: x, y: y } | |
89 | }) | |
90 | ]; | |
91 | } | |
92 | return []; | |
93 | } | |
0c3fe8a6 | 94 | |
10cceb25 | 95 | let moves = super.getPotentialMovesFrom([x, y]); |
ded43c88 BA |
96 | if (this.getPiece(x, y) == V.PAWN) { |
97 | // Promotions by captures can be reduced to only one deterministic | |
98 | // move (because of the explosion). | |
99 | moves = moves.filter(m => { | |
100 | return ( | |
101 | m.vanish.length == 1 || | |
102 | [V.PAWN, V.QUEEN].includes(m.appear[0].p) | |
103 | ); | |
104 | }); | |
105 | } | |
dac39588 BA |
106 | // Handle explosions |
107 | moves.forEach(m => { | |
8b405c81 | 108 | // NOTE: if vanish.length==2 and appear.length==2, this is castle |
6808d7a1 | 109 | if (m.vanish.length > 1 && m.appear.length <= 1) { |
8b405c81 | 110 | // Explosion! (TODO?: drop moves which explode our king here) |
6808d7a1 BA |
111 | let steps = [ |
112 | [-1, -1], | |
113 | [-1, 0], | |
114 | [-1, 1], | |
115 | [0, -1], | |
116 | [0, 1], | |
117 | [1, -1], | |
118 | [1, 0], | |
119 | [1, 1] | |
120 | ]; | |
121 | for (let step of steps) { | |
dac39588 BA |
122 | let x = m.end.x + step[0]; |
123 | let y = m.end.y + step[1]; | |
6808d7a1 BA |
124 | if ( |
125 | V.OnBoard(x, y) && | |
126 | this.board[x][y] != V.EMPTY && | |
127 | this.getPiece(x, y) != V.PAWN | |
128 | ) { | |
dac39588 | 129 | m.vanish.push( |
6808d7a1 BA |
130 | new PiPo({ |
131 | p: this.getPiece(x, y), | |
132 | c: this.getColor(x, y), | |
133 | x: x, | |
134 | y: y | |
135 | }) | |
136 | ); | |
dac39588 BA |
137 | } |
138 | } | |
6808d7a1 | 139 | m.end = { x: m.appear[0].x, y: m.appear[0].y }; |
dac39588 BA |
140 | m.appear.pop(); //Nothin appears in this case |
141 | } | |
142 | }); | |
dac39588 BA |
143 | return moves; |
144 | } | |
0c3fe8a6 | 145 | |
6808d7a1 | 146 | getPotentialKingMoves([x, y]) { |
dac39588 BA |
147 | // King cannot capture: |
148 | let moves = []; | |
149 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
6808d7a1 | 150 | for (let step of steps) { |
dac39588 BA |
151 | const i = x + step[0]; |
152 | const j = y + step[1]; | |
6808d7a1 BA |
153 | if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) |
154 | moves.push(this.getBasicMove([x, y], [i, j])); | |
dac39588 | 155 | } |
6808d7a1 | 156 | return moves.concat(this.getCastleMoves([x, y])); |
dac39588 | 157 | } |
0c3fe8a6 | 158 | |
68e19a44 | 159 | isAttacked(sq, color) { |
6808d7a1 BA |
160 | if ( |
161 | this.getPiece(sq[0], sq[1]) == V.KING && | |
68e19a44 BA |
162 | this.isAttackedByKing(sq, color) |
163 | ) { | |
164 | // A king next to the enemy king is immune to attacks | |
165 | return false; | |
166 | } | |
6808d7a1 | 167 | return ( |
68e19a44 BA |
168 | this.isAttackedByPawn(sq, color) || |
169 | this.isAttackedByRook(sq, color) || | |
170 | this.isAttackedByKnight(sq, color) || | |
171 | this.isAttackedByBishop(sq, color) || | |
172 | this.isAttackedByQueen(sq, color) | |
173 | // No "attackedByKing": it cannot take | |
6808d7a1 | 174 | ); |
dac39588 | 175 | } |
0c3fe8a6 | 176 | |
3a2a7b5f BA |
177 | postPlay(move) { |
178 | super.postPlay(move); | |
0fb43db7 BA |
179 | // NOTE: (harmless) condition on movesCount for Atomic2 |
180 | if (move.appear.length == 0 && this.movesCount >= 2) { | |
e9b736ee | 181 | // Capture |
6808d7a1 BA |
182 | const firstRank = { w: 7, b: 0 }; |
183 | for (let c of ["w", "b"]) { | |
dac39588 | 184 | // Did we explode king of color c ? (TODO: remove move earlier) |
6808d7a1 BA |
185 | if ( |
186 | Math.abs(this.kingPos[c][0] - move.end.x) <= 1 && | |
187 | Math.abs(this.kingPos[c][1] - move.end.y) <= 1 | |
188 | ) { | |
189 | this.kingPos[c] = [-1, -1]; | |
3a2a7b5f | 190 | this.castleFlags[c] = [8, 8]; |
cb1165b4 BA |
191 | } |
192 | else { | |
dac39588 | 193 | // Now check if init rook(s) exploded |
6808d7a1 | 194 | if (Math.abs(move.end.x - firstRank[c]) <= 1) { |
3a2a7b5f BA |
195 | if (Math.abs(move.end.y - this.castleFlags[c][0]) <= 1) |
196 | this.castleFlags[c][0] = 8; | |
197 | if (Math.abs(move.end.y - this.castleFlags[c][1]) <= 1) | |
198 | this.castleFlags[c][1] = 8; | |
dac39588 BA |
199 | } |
200 | } | |
201 | } | |
202 | } | |
203 | } | |
0c3fe8a6 | 204 | |
3a2a7b5f BA |
205 | postUndo(move) { |
206 | super.postUndo(move); | |
207 | const c = this.turn; | |
dac39588 | 208 | const oppCol = V.GetOppCol(c); |
10cceb25 | 209 | // NOTE: condition on movesCount for balanced setting |
0fb43db7 BA |
210 | if ( |
211 | this.movesCount >= 1 && | |
212 | [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0) | |
213 | ) { | |
dac39588 | 214 | // There is a chance that last move blowed some king away.. |
6808d7a1 BA |
215 | for (let psq of move.vanish) { |
216 | if (psq.p == "k") | |
217 | this.kingPos[psq.c == c ? c : oppCol] = [psq.x, psq.y]; | |
dac39588 BA |
218 | } |
219 | } | |
220 | } | |
0c3fe8a6 | 221 | |
6808d7a1 | 222 | underCheck(color) { |
dac39588 BA |
223 | const oppCol = V.GetOppCol(color); |
224 | let res = undefined; | |
225 | // If our king disappeared, move is not valid | |
6808d7a1 | 226 | if (this.kingPos[color][0] < 0) res = true; |
dac39588 | 227 | // If opponent king disappeared, move is valid |
6808d7a1 | 228 | else if (this.kingPos[oppCol][0] < 0) res = false; |
dac39588 | 229 | // Otherwise, if we remain under check, move is not valid |
68e19a44 | 230 | else res = this.isAttacked(this.kingPos[color], oppCol); |
dac39588 BA |
231 | return res; |
232 | } | |
0c3fe8a6 | 233 | |
af34341d BA |
234 | getCheckSquares() { |
235 | const color = this.turn; | |
6808d7a1 BA |
236 | let res = []; |
237 | if ( | |
238 | this.kingPos[color][0] >= 0 && //king might have exploded | |
68e19a44 | 239 | this.isAttacked(this.kingPos[color], V.GetOppCol(color)) |
6808d7a1 BA |
240 | ) { |
241 | res = [JSON.parse(JSON.stringify(this.kingPos[color]))]; | |
dac39588 BA |
242 | } |
243 | return res; | |
244 | } | |
0c3fe8a6 | 245 | |
6808d7a1 | 246 | getCurrentScore() { |
dac39588 BA |
247 | const color = this.turn; |
248 | const kp = this.kingPos[color]; | |
6808d7a1 | 249 | if (kp[0] < 0) |
e9b736ee | 250 | // King disappeared |
dac39588 | 251 | return color == "w" ? "0-1" : "1-0"; |
bb688df5 | 252 | if (this.atLeastOneMove()) return "*"; |
68e19a44 | 253 | if (!this.isAttacked(kp, V.GetOppCol(color))) return "1/2"; |
dac39588 BA |
254 | return color == "w" ? "0-1" : "1-0"; //checkmate |
255 | } | |
7e8a7ea1 | 256 | |
10cceb25 BA |
257 | getNotation(move) { |
258 | if (move.appear.length == 0 && move.vanish.length == 1) | |
259 | // First move in game (balanced == true) | |
260 | return V.CoordsToSquare(move.start) + "X"; | |
261 | return super.getNotation(move); | |
262 | } | |
263 | ||
6808d7a1 | 264 | }; |