1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class DynamoRules
extends ChessRules
{
4 // TODO: later, allow to push out pawns on a and h files
5 static get HasEnpassant() {
9 canIplay(side
, [x
, y
]) {
10 // Sometimes opponent's pieces can be moved directly
14 setOtherVariables(fen
) {
15 super.setOtherVariables(fen
);
17 // Local stack of "action moves"
19 const amove
= V
.ParseFen(fen
).amove
;
21 const amoveParts
= amove
.split("/");
23 // No need for start & end
28 amoveParts
[i
].split(".").forEach(av
=> {
30 const xy
= V
.SquareToCoords(av
.substr(2));
31 move[i
== 0 ? "appear" : "vanish"].push(
41 this.amoves
.push(move);
43 // Stack "first moves" (on subTurn 1) to merge and check opposite moves
47 static ParseFen(fen
) {
49 ChessRules
.ParseFen(fen
),
50 { amove: fen
.split(" ")[4] }
54 static IsGoodFen(fen
) {
55 if (!ChessRules
.IsGoodFen(fen
)) return false;
56 const fenParts
= fen
.split(" ");
57 if (fenParts
.length
!= 6) return false;
58 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
64 return super.getFen() + " " + this.getAmoveFen();
68 return super.getFenForRepeat() + "_" + this.getAmoveFen();
72 const L
= this.amoves
.length
;
74 ["appear","vanish"].map(
77 this.amoves
[L
-1][mpart
].map(
79 const square
= V
.CoordsToSquare({ x: av
.x
, y: av
.y
});
80 return av
.c
+ av
.p
+ square
;
90 // Captures don't occur (only pulls & pushes)
94 // "pa": piece (as a square) doing this push/pull action
95 getActionMoves([sx
, sy
], [ex
, ey
], pa
) {
96 const color
= this.getColor(sx
, sy
);
97 const lastRank
= (color
== 'w' ? 0 : 7);
98 const piece
= this.getPiece(sx
, sy
);
100 if (ex
== lastRank
&& piece
== V
.PAWN
) {
101 // Promotion by push or pull
102 V
.PawnSpecs
.promotions
.forEach(p
=> {
103 let move = super.getBasicMove([sx
, sy
], [ex
, ey
], { c: color
, p: p
});
106 } else moves
.push(super.getBasicMove([sx
, sy
], [ex
, ey
]));
110 // Actions on piece on square "sq", by color "color"
111 // NOTE: to push a piece out of the board, make it slide until our piece
112 // (doing the action, moving or not)
113 getPactions(sq
, color
) {
117 const oppCol
= V
.GetOppCol(color
);
118 // Look in all directions for a "color" piece
119 for (let step
of V
.steps
[V
.KNIGHT
]) {
120 const xx
= x
+ step
[0],
124 this.getPiece(xx
, yy
) == V
.KNIGHT
&&
125 this.getColor(xx
, yy
) == color
127 const px
= x
- step
[0],
129 if (V
.OnBoard(px
, py
)) {
130 if (this.board
[px
][py
] == V
.EMPTY
) {
131 const hash
= "s" + px
+ py
;
132 if (!squares
[hash
]) {
133 squares
[hash
] = true;
134 Array
.prototype.push
.apply(
136 this.getActionMoves([x
, y
], [px
, py
], [xx
, yy
])
139 else { //add piece doing action
143 const hash
= "s" + xx
+ yy
;
144 if (!squares
[hash
]) {
145 squares
[hash
] = true;
148 start: { x: x
, y: y
},
149 end: { x: xx
, y: yy
},
155 p: this.getPiece(x
, y
),
165 for (let step
in V
.steps
[V
.ROOK
]) {
166 // (+ if color is ours, pawn pushes) king, rook and queen
167 // --> pawns special case can push from a little distance if on 2nd rank (or 1st rank)
169 for (let step
in V
.steps
[V
.BISHOP
]) {
170 // King, bishop, queen, and possibly pawns attacks (if color is enemy)
175 // NOTE: for pushes, play the pushed piece first.
176 // for pulls: play the piece doing the action first
177 // If castle, then no options available next (just re-click)
178 getPotentialMovesFrom([x
, y
]) {
179 const color
= this.turn
;
180 if (this.subTurn
== 1) {
181 // Free to play any move or action:
183 super.getPotentialMovesFrom([x
, y
])
184 .concat(this.getPactions([x
, y
], color
))
187 // If subTurn == 2 then we should have a first move,
188 // which restrict what we can play now.
189 // Case 1: an opponent's piece moved: we can only move the piece which
190 // did the action, in the moving direction.
191 // Case 2: one of our pieces moved: either by action or by itself.
192 // Just check if it could be a normal move. If yes, allow both.
193 const L
= this.firstMove
.length
;
194 const fm
= this.firstMove
[L
-1];
195 if (fm
.vanish
[0].c
!= color
) {
200 // Use fm.start.x, fm.start.y, fm.end.x, fm.end.y, fm.vanish[0].c
201 // Search for the piece doing the action "pa": the action type
202 // is deduced from pa relative positon then.
206 // Does m2 un-do m1 ? (to disallow undoing actions)
207 oppositeMoves(m1
, m2
) {
208 const isEqual
= (av1
, av2
) => {
209 // Precondition: av1 and av2 length = 2
210 for (let av
of av1
) {
211 const avInAv2
= av2
.find(elt
=> {
219 if (!avInAv2
) return false;
224 m1
.appear
.length
== 2 &&
225 m2
.appear
.length
== 2 &&
226 m1
.vanish
.length
== 2 &&
227 m2
.vanish
.length
== 2 &&
228 isEqual(m1
.appear
, m2
.vanish
) &&
229 isEqual(m1
.vanish
, m2
.appear
)
233 getAmove(move1
, move2
) {
234 // Just merge (one is action one is move, one may be empty)
236 appear: move1
.appear
.concat(move2
.appear
),
237 vanish: move1
.vanish
.concat(move2
.vanish
)
242 const color
= this.turn
;
243 if (this.subTurn
== 1) {
244 return moves
.filter(m
=> {
245 // A move is valid either if it doesn't result in a check,
246 // or if a second move is possible to counter the check
247 // (not undoing a potential move + action of the opponent)
249 let res
= this.underCheck(color
);
251 const moves2
= this.getAllPotentialMoves();
254 const res2
= this.underCheck(color
);
266 const Lf
= this.firstMove
.length
;
267 const La
= this.amoves
.length
;
268 if (La
== 0) return super.filterValid(moves
);
272 // Move shouldn't undo another:
273 const amove
= this.getAmove(this.firstMove
[Lf
-1], m
);
274 return !this.oppositeMoves(this.amoves
[La
-1], amove
);
280 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
281 for (let step
of steps
) {
282 let rx
= x
+ step
[0],
284 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
290 this.getPiece(rx
, ry
) == piece
&&
291 this.getColor(rx
, ry
) == color
293 // Now step in the other direction: if end of the world, then attacked
298 this.board
[rx
][ry
] == V
.EMPTY
&&
304 if (!V
.OnBoard(rx
, ry
)) return true;
310 isAttackedByPawn([x
, y
], color
) {
311 const lastRank
= (color
== 'w' ? 0 : 7);
313 // The king can be pushed out by a pawn only on last rank
315 const pawnShift
= (color
== "w" ? 1 : -1);
316 for (let i
of [-1, 1]) {
320 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
321 this.getColor(x
+ pawnShift
, y
+ i
) == color
330 if (this.subTurn
== 2)
333 return super.getCurrentScore();
337 // If subTurn == 2 && square is the final square of last move,
338 // then return an empty move
339 const L
= this.firstMove
.length
;
342 square
.x
== this.firstMove
[L
-1].end
.x
&&
343 square
.y
== this.firstMove
[L
-1].end
.y
354 move.flags
= JSON
.stringify(this.aggregateFlags());
355 V
.PlayOnBoard(this.board
, move);
356 if (this.subTurn
== 2) {
357 this.turn
= V
.GetOppCol(this.turn
);
360 else this.firstMove
.push(move);
361 this.subTurn
= 3 - this.subTurn
;
365 updateCastleFlags(move, piece
) {
366 const c
= V
.GetOppCol(this.turn
);
367 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
368 // Update castling flags
369 if (piece
== V
.KING
) this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
370 for (let v
of move.vanish
) {
371 if (v
.x
== firstRank
&& this.castleFlags
[c
].includes(v
.y
)) {
372 const flagIdx
= (v
.y
== this.castleFlags
[c
][0] ? 0 : 1);
373 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
379 this.disaggregateFlags(JSON
.parse(move.flags
));
380 V
.UndoOnBoard(this.board
, move);
381 if (this.subTurn
== 1) {
382 this.turn
= V
.GetOppCol(this.turn
);
385 else this.firstMove
.pop();
386 this.subTurn
= 3 - this.subTurn
;