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