Now in Dynamo, only getPotentialMoves to write
[vchess.git] / client / src / variants / Dynamo.js
CommitLineData
b866a62a
BA
1import { ChessRules, Move, PiPo } from "@/base_rules";
2
0d5335de 3export class DynamoRules extends ChessRules {
7ddfec38 4 // TODO: later, allow to push out pawns on a and h files
61656127
BA
5 static get HasEnpassant() {
6 return false;
7 }
8
c7550017
BA
9 canIplay(side, [x, y]) {
10 // Sometimes opponent's pieces can be moved directly
11 return true;
12 }
13
61656127
BA
14 setOtherVariables(fen) {
15 super.setOtherVariables(fen);
16 this.subTurn = 1;
17 // Local stack of "action moves"
18 this.amoves = [];
19 const amove = V.ParseFen(fen).amove;
7ddfec38 20 if (amove == "-") this.amoves.push(null);
c7550017 21 else {
61656127
BA
22 const amoveParts = amove.split("/");
23 let amove = {
24 // No need for start & end
25 appear: [],
26 vanish: []
27 };
28 [0, 1].map(i => {
29 amoveParts[0].split(".").forEach(av => {
30 // Format is "bpe3"
31 const xy = V.SquareToCoords(av.substr(2));
32 move[i == 0 ? "appear" : "vanish"].push(
33 new PiPo({
34 x: xy.x,
35 y: xy.y,
36 c: av[0],
37 p: av[1]
38 })
39 );
40 });
41 });
42 this.amoves.push(move);
c7550017 43 }
a443d256
BA
44 // Stack "first moves" (on subTurn 1) to merge and check opposite moves
45 this.firstMove = [];
c7550017
BA
46 }
47
61656127
BA
48 static ParseFen(fen) {
49 return Object.assign(
50 ChessRules.ParseFen(fen),
51 { cmove: fen.split(" ")[4] }
52 );
53 }
54
55 static IsGoodFen(fen) {
56 if (!ChessRules.IsGoodFen(fen)) return false;
57 const fenParts = fen.split(" ");
58 if (fenParts.length != 6) return false;
59 if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/))
60 return false;
61 return true;
62 }
63
7ddfec38 64 getAmove(move1, move2) {
a443d256
BA
65 // Just merge (one is action one is move, one may be empty)
66 return {
67 appear: move1.appear.concat(move2.appear),
68 vanish: move1.vanish.concat(move2.vanish)
69 }
61656127
BA
70 }
71
61656127
BA
72 doClick(square) {
73 // If subTurn == 2 && square is the final square of last move,
74 // then return an empty move
a443d256 75 const L = this.firstMove.length;
61656127
BA
76 if (
77 this.subTurn == 2 &&
a443d256
BA
78 square.x == this.firstMove[L-1].end.x &&
79 square.y == this.firstMove[L-1].end.y
61656127
BA
80 ) {
81 return {
82 appear: [],
83 vanish: []
84 };
85 }
86 return null;
87 }
88
89 canTake() {
90 // Captures don't occur (only pulls & pushes)
91 return false;
92 }
93
7ddfec38
BA
94 // TODO: re-think these next 3 methods:
95 // Idea = have the info about lastMove in lastMoves[L-1],
96 // In particular if moving a piece or doing an action.
97
61656127
BA
98 // "pa" : piece (as a square) doing this push/pull action
99 getActionMoves([sx, sy], [ex, ey], pa) {
100 const color = this.getColor(sx, sy);
101 const lastRank = (color == 'w' ? 0 : 7);
102 const piece = this.getPiece(sx, sy);
103 let moves = [];
104 if (ex == lastRank && piece == V.PAWN) {
105 // Promotion by push or pull
106 V.PawnSpecs.promotions.forEach(p => {
107 let move = super.getBasicMove([sx, sy], [ex, ey], { c: color, p: p });
108 moves.push(move);
109 });
110 } else moves.push(super.getBasicMove([sx, sy], [ex, ey]));
111 const actionType =
112 (
113 Math.abs(pa[0] - sx) < Math.abs(pa[0] - ex) ||
114 Math.abs(pa[1] - sy) < Math.abs(pa[1] - ey)
115 )
116 ? "push"
117 : "pull";
118 moves.forEach(m => m.action = [{ by: pa, type: actionType }]);
119 return moves;
120 }
121
122 // TODO: if type is given, consider only actions of this type
123 getPactions(sq, color, type) {
b866a62a
BA
124 const [x, y] = sq;
125 let moves = [];
126 let squares = {};
b866a62a 127 if (!by) {
61656127 128 const oppCol = V.GetOppCol(color);
b866a62a
BA
129 // Look in all directions for a "color" piece
130 for (let step of V.steps[V.KNIGHT]) {
131 const xx = x + step[0],
132 yy = y + step[1];
133 if (
134 V.OnBoard(xx, yy) &&
135 this.getPiece(xx, yy) == V.KNIGHT &&
136 this.getColor(xx, yy) == color
137 ) {
138 const px = x - step[0],
139 py = y - step[1];
61656127
BA
140 if (V.OnBoard(px, py)) {
141 if (this.board[px][py] == V.EMPTY) {
142 const hash = "s" + px + py;
143 if (!squares[hash]) {
144 squares[hash] = true;
145 Array.prototype.push.apply(
146 moves,
147 this.getActionMoves([x, y], [px, py], [xx, yy])
148 );
149 }
150 else { //add piece doing action
151 }
b866a62a
BA
152 }
153 } else {
154 const hash = "s" + xx + yy;
155 if (!squares[hash]) {
156 squares[hash] = true;
157 moves.push(
158 new Move({
159 start: { x: x, y: y },
160 end: { x: xx, y: yy },
161 appear: [],
162 vanish: [
163 new PiPo({
164 x: x,
165 y: y,
166 p: this.getPiece(x, y),
167 c: oppCol
168 })
169 ]
170 })
171 );
172 }
173 }
174 }
175 }
176 for (let step in V.steps[V.ROOK]) {
61656127
BA
177 // (+ if color is ours, pawn pushes) king, rook and queen
178 // --> pawns special case can push from a little distance if on 2nd rank (or 1st rank)
b866a62a
BA
179 }
180 for (let step in V.steps[V.BISHOP]) {
61656127 181 // King, bishop, queen, and possibly pawns attacks (if color is enemy)
b866a62a
BA
182 }
183 }
b866a62a
BA
184 return moves;
185 }
186
187 // NOTE: to push a piece out of the board, make it slide until our piece
188 // (doing the action, moving or not)
61656127
BA
189 // TODO: for pushes, play the pushed piece first.
190 // for pulls: play the piece doing the action first
7ddfec38
BA
191 // If castle, then no options available next (just re-click)
192
b866a62a
BA
193 getPotentialMovesFrom([x, y]) {
194 const color = this.turn;
61656127
BA
195 if (this.getColor(x, y) != color)
196 // The only moves possible with enemy pieces are pulls and pushes:
197 return this.getPactions([x, y], color);
f9385686
BA
198 // Playing my pieces: either on their own, or pushed by another
199 // If subTurn == 2 then we should have a first move,
200 // TODO = use it to allow some type of action
201 if (this.subTurn == 2) {
202 return (
203 this.moveOnSubturn1.isAnAction
204 ? super.getPotentialMovesFrom([x, y])
205 : this.getPactions([x, y], color, TODO_arg)
206 );
b866a62a 207 }
f9385686
BA
208 // Both options are possible at subTurn1: normal move, or push
209 return (
210 super.getPotentialMovesFrom([x, y])
211 .concat(this.getPactions([x, y], color, "push"))
212 // TODO: discard moves that let the king underCheck, and no second
213 // move can counter check. Example: pinned queen pushes pinned pawn.
214 .filter(m => {
215 this.play(m);
216 const res = this.filterMoves(this.getPotentialMoves(/* TODO: args? */)).length > 0;
217 this.undo(m);
218 return res;
219 })
220 );
61656127
BA
221 }
222
223 // Does m2 un-do m1 ? (to disallow undoing actions)
224 oppositeMoves(m1, m2) {
225 const isEqual = (av1, av2) => {
226 // Precondition: av1 and av2 length = 2
227 for (let av of av1) {
228 const avInAv2 = av2.find(elt => {
229 return (
230 elt.x == av.x &&
231 elt.y == av.y &&
232 elt.c == av.c &&
233 elt.p == av.p
234 );
235 });
236 if (!avInAv2) return false;
237 }
238 return true;
239 };
240 return (
61656127
BA
241 m1.appear.length == 2 &&
242 m2.appear.length == 2 &&
243 m1.vanish.length == 2 &&
244 m2.vanish.length == 2 &&
245 isEqual(m1.appear, m2.vanish) &&
246 isEqual(m1.vanish, m2.appear)
247 );
248 }
249
250 filterValid(moves) {
7ddfec38 251 if (this.subTurn == 1)
a443d256 252 // Validity of subTurn 1 should be checked in getPotentialMoves
7ddfec38 253 return moves;
a443d256
BA
254 const L = this.firstMove.length;
255 return (
256 super.filterMoves(
257 moves.filter(m => {
258 // Move shouldn't undo another:
259 return !this.oppositeMoves(this.firstMove[L-1], m)
260 })
261 )
262 );
b866a62a
BA
263 }
264
c7550017
BA
265 isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
266 for (let step of steps) {
267 let rx = x + step[0],
268 ry = y + step[1];
269 while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
270 rx += step[0];
271 ry += step[1];
272 }
273 if (
274 V.OnBoard(rx, ry) &&
275 this.getPiece(rx, ry) == piece &&
276 this.getColor(rx, ry) == color
277 ) {
278 // Now step in the other direction: if end of the world, then attacked
279 rx = x - step[0];
280 ry = y - step[1];
2c5d7b20
BA
281 while (
282 V.OnBoard(rx, ry) &&
283 this.board[rx][ry] == V.EMPTY &&
284 !oneStep
285 ) {
c7550017
BA
286 rx -= step[0];
287 ry -= step[1];
288 }
289 if (!V.OnBoard(rx, ry)) return true;
290 }
291 }
292 return false;
293 }
294
295 isAttackedByPawn([x, y], color) {
296 const lastRank = (color == 'w' ? 0 : 7);
297 if (y != lastRank)
298 // The king can be pushed out by a pawn only on last rank
299 return false;
300 const pawnShift = (color == "w" ? 1 : -1);
301 for (let i of [-1, 1]) {
302 if (
303 y + i >= 0 &&
304 y + i < V.size.y &&
305 this.getPiece(x + pawnShift, y + i) == V.PAWN &&
306 this.getColor(x + pawnShift, y + i) == color
307 ) {
308 return true;
309 }
310 }
311 return false;
312 }
61656127
BA
313
314 getCurrentScore() {
315 if (this.subTurn == 2)
316 // Move not over
317 return "*";
318 return super.getCurrentScore();
319 }
320
321 play(move) {
322 move.flags = JSON.stringify(this.aggregateFlags());
323 V.PlayOnBoard(this.board, move);
7ddfec38 324 if (this.subTurn == 2) {
61656127 325 this.turn = V.GetOppCol(this.turn);
7ddfec38 326 this.movesCount++;
61656127 327 }
a443d256 328 else this.firstMove.push(move);
7ddfec38 329 this.subTurn = 3 - this.subTurn;
61656127
BA
330 this.postPlay(move);
331 }
332
333 updateCastleFlags(move, piece) {
334 const c = V.GetOppCol(this.turn);
335 const firstRank = (c == "w" ? V.size.x - 1 : 0);
7ddfec38
BA
336 // Update castling flags
337 if (piece == V.KING) this.castleFlags[c] = [V.size.y, V.size.y];
338 for (let v of move.vanish) {
339 if (v.x == firstRank && this.castleFlags[c].includes(v.y)) {
340 const flagIdx = (v.y == this.castleFlags[c][0] ? 0 : 1);
341 this.castleFlags[c][flagIdx] = V.size.y;
342 }
61656127
BA
343 }
344 }
345
346 undo(move) {
347 this.disaggregateFlags(JSON.parse(move.flags));
348 V.UndoOnBoard(this.board, move);
7ddfec38 349 if (this.subTurn == 1) {
61656127 350 this.turn = V.GetOppCol(this.turn);
7ddfec38 351 this.movesCount--;
61656127 352 }
a443d256 353 else this.firstMove.pop();
7ddfec38 354 this.subTurn = 3 - this.subTurn;
61656127
BA
355 this.postUndo(move);
356 }
0d5335de 357};