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
;
20 if (amove
== "-") this.amoves
.push(null);
22 const amoveParts
= amove
.split("/");
24 // No need for start & end
29 amoveParts
[0].split(".").forEach(av
=> {
31 const xy
= V
.SquareToCoords(av
.substr(2));
32 move[i
== 0 ? "appear" : "vanish"].push(
42 this.amoves
.push(move);
46 static ParseFen(fen
) {
48 ChessRules
.ParseFen(fen
),
49 { cmove: fen
.split(" ")[4] }
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}$/))
62 // TODO: local stack of "last moves" to know move1
63 getAmove(move1
, move2
) {
64 // TODO: merge (one is action one is move)
65 if (move.appear
.length
== 2 && move.vanish
.length
== 2)
66 return { appear: move.appear
, vanish: move.vanish
};
71 // If subTurn == 2 && square is the final square of last move,
72 // then return an empty move
73 const L
= this.lastMoves
.length
;
76 square
.x
== this.lastMoves
[L
-1].end
.x
&&
77 square
.y
== this.lastMoves
[L
-1].end
.y
88 // Captures don't occur (only pulls & pushes)
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.
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
);
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
});
108 } else moves
.push(super.getBasicMove([sx
, sy
], [ex
, ey
]));
111 Math
.abs(pa
[0] - sx
) < Math
.abs(pa
[0] - ex
) ||
112 Math
.abs(pa
[1] - sy
) < Math
.abs(pa
[1] - ey
)
116 moves
.forEach(m
=> m
.action
= [{ by: pa
, type: actionType
}]);
120 // TODO: if type is given, consider only actions of this type
121 getPactions(sq
, color
, type
) {
126 const oppCol
= V
.GetOppCol(color
);
127 // Look in all directions for a "color" piece
128 for (let step
of V
.steps
[V
.KNIGHT
]) {
129 const xx
= x
+ step
[0],
133 this.getPiece(xx
, yy
) == V
.KNIGHT
&&
134 this.getColor(xx
, yy
) == color
136 const px
= x
- step
[0],
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(
145 this.getActionMoves([x
, y
], [px
, py
], [xx
, yy
])
148 else { //add piece doing action
152 const hash
= "s" + xx
+ yy
;
153 if (!squares
[hash
]) {
154 squares
[hash
] = true;
157 start: { x: x
, y: y
},
158 end: { x: xx
, y: yy
},
164 p: this.getPiece(x
, y
),
174 for (let step
in V
.steps
[V
.ROOK
]) {
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)
178 for (let step
in V
.steps
[V
.BISHOP
]) {
179 // King, bishop, queen, and possibly pawns attacks (if color is enemy)
185 // NOTE: to push a piece out of the board, make it slide until our piece
186 // (doing the action, moving or not)
187 // TODO: for pushes, play the pushed piece first.
188 // for pulls: play the piece doing the action first
189 // If castle, then no options available next (just re-click)
191 getPotentialMovesFrom([x
, y
]) {
192 const color
= this.turn
;
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
);
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) {
201 this.moveOnSubturn1
.isAnAction
202 ? super.getPotentialMovesFrom([x
, y
])
203 : this.getPactions([x
, y
], color
, TODO_arg
)
206 // Both options are possible at subTurn1: normal move, or push
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.
214 const res
= this.filterMoves(this.getPotentialMoves(/* TODO: args? */)).length
> 0;
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);
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
=> {
240 if (!avInAv2
) return false;
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
)
256 // Si on se met en échec au coup 1, peut-on le contrer au coup 2 ? (cf. take n make)
258 if (this.subTurn
== 1)
259 // Validity of subTurn 1 should be checked in getPotentialMoves...
261 return super.filterMoves(moves
);
264 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
265 for (let step
of steps
) {
266 let rx
= x
+ step
[0],
268 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
274 this.getPiece(rx
, ry
) == piece
&&
275 this.getColor(rx
, ry
) == color
277 // Now step in the other direction: if end of the world, then attacked
282 this.board
[rx
][ry
] == V
.EMPTY
&&
288 if (!V
.OnBoard(rx
, ry
)) return true;
294 isAttackedByPawn([x
, y
], color
) {
295 const lastRank
= (color
== 'w' ? 0 : 7);
297 // The king can be pushed out by a pawn only on last rank
299 const pawnShift
= (color
== "w" ? 1 : -1);
300 for (let i
of [-1, 1]) {
304 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
305 this.getColor(x
+ pawnShift
, y
+ i
) == color
314 if (this.subTurn
== 2)
317 return super.getCurrentScore();
321 move.flags
= JSON
.stringify(this.aggregateFlags());
322 V
.PlayOnBoard(this.board
, move);
323 if (this.subTurn
== 2) {
324 this.turn
= V
.GetOppCol(this.turn
);
327 this.subTurn
= 3 - this.subTurn
;
331 updateCastleFlags(move, piece
) {
332 const c
= V
.GetOppCol(this.turn
);
333 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
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
;
345 this.disaggregateFlags(JSON
.parse(move.flags
));
346 V
.UndoOnBoard(this.board
, move);
347 if (this.subTurn
== 1) {
348 this.turn
= V
.GetOppCol(this.turn
);
351 this.subTurn
= 3 - this.subTurn
;