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