25954d57952e54cce8af1d6e5044ab63e2c26e90
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 // Step is right, just add (push/pull) moves in this direction
95 // Direction is assumed normalized.
96 getMovesInDirection([x
, y
], [dx
, dy
], nbSteps
) {
97 nbSteps
= nbSteps
|| 8; //max 8 steps anyway
98 let [i
, j
] = [x
+ dx
, y
+ dy
];
100 const color
= this.getColor(x
, y
);
101 const piece
= this.getPiece(x
, y
);
102 const lastRank
= (color
== 'w' ? 0 : 7);
103 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
104 if (i
== lastRank
&& piece
== V
.PAWN
) {
105 // Promotion by push or pull
106 V
.PawnSpecs
.promotions
.forEach(p
=> {
107 let move = super.getBasicMove([x
, y
], [i
, j
], { c: color
, p: p
});
111 else moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
113 if (!V
.OnBoard(i
, j
) && piece
!= V
.KING
) {
114 // Add special "exit" move, by "taking king"
117 start: { x: x
, y: y
},
118 end: JSON
.parse(JSON
.stringify(this.kingPos
[color
])),
120 vanish: [{ x: x
, y: y
, c: color
, p: piece
}]
127 // Normalize direction to know the step
128 getNormalizedDirection([dx
, dy
]) {
129 const absDir
= [Math
.abs(dx
), Math
.abs(dy
)];
131 if (absDir
[0] != 0 && absDir
[1] != 0 && absDir
[0] != absDir
[1])
133 divisor
= Math
.min(absDir
[0], absDir
[1]);
135 // Standard slider (or maybe a pawn or king: same)
136 divisor
= Math
.max(absDir
[0], absDir
[1]);
137 return [dx
/ divisor
, dy
/ divisor
];
140 // There is something on x2,y2, maybe our color, pushed/pulled
141 static IsAprioriValidMove([x1
, y1
], [x2
, y2
]) {
142 const color1
= this.getColor(x1
, y1
);
143 const color2
= this.getColor(x2
, y2
);
144 const pawnShift
= (color1
== 'w' ? -1 : 1);
145 const pawnStartRank
= (color1
== 'w' ? 6 : 1);
146 const deltaX
= Math
.abs(x1
- x2
);
147 const deltaY
= Math
.abs(y1
- y2
);
148 switch (this.getPiece(x1
, y1
)) {
155 x1
+ pawnShift
== x2
||
156 x1
== pawnStartRank
&& x1
+ 2 * pawnShift
== x2
167 return (x1
== x2
|| y1
== y2
);
169 return (deltaX
+ deltaY
== 3 && (deltaX
== 1 || deltaY
== 1));
172 return (deltaX
== deltaY
);
175 (deltaX
== 0 || deltaY
== 0 || deltaX
== deltaY
)
178 return (deltaX
<= 1 && deltaY
<= 1);
183 // NOTE: for pushes, play the pushed piece first.
184 // for pulls: play the piece doing the action first
185 // NOTE: to push a piece out of the board, make it slide until its king
186 getPotentialMovesFrom([x
, y
]) {
187 const color
= this.turn
;
188 if (this.subTurn
== 1) {
189 // Free to play any move:
190 const moves
= super.getPotentialMovesFrom([x
, y
])
191 // Structure to avoid adding moves twice (can be action & move)
193 moves
.forEach(m
=> { hashMoves
[getMoveHash(m
)] = true; });
194 const getMoveHash
= (m
) => {
195 return V
.CoordsToSquare(m
.start
) + V
.CoordsToSquare(m
.end
);
197 const addMoves
= (dir
, nbSteps
) => {
199 this.getMovesInDirection([x
, y
], [-dir
[0], -dir
[1]], nbSteps
)
200 .filter(m
=> !movesHash
[getMoveHash(m
)]);
201 newMoves
.forEach(m
=> { hashMoves
[getMoveHash(m
)] = true; });
202 Array
.prototype.push
.apply(moves
, newMoves
);
204 const pawnShift
= (color
== 'w' ? -1 : 1);
205 const pawnStartRank
= (color
== 'w' ? 6 : 1);
206 // [x, y] is pushed by 'color'
207 for (let step
of V
.steps
[V
.KNIGHT
]) {
208 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
209 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) {
210 // Only can move away from a knight (can pull but must move first)
211 Array
.prototype.push
.apply(
213 this.getMovesInDirection([x
, y
], [-step
[0], -step
[1]], 1)
214 .filter(m
=> !movesHash
[getMoveHash(m
)])
218 for (let step
in V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
219 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
220 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
226 this.board
[i
][j
] != V
.EMPTY
&&
227 this.getColor(i
, j
) == color
229 const deltaX
= Math
.abs(i
- x
);
230 const deltaY
= Math
.abs(j
- y
);
231 // Can a priori go both ways, except with pawns
232 switch (this.getPiece(i
, j
)) {
234 if (deltaX
<= 2 && deltaY
<= 1) {
235 const pColor
= this.getColor(x
, y
);
236 if (pColor
== color
&& deltaY
== 0) {
238 const maxSteps
= (i
== pawnStartRank
&& deltaX
== 1 ? 2 : 1);
239 addMoves(step
, maxSteps
);
241 else if (pColor
!= color
&& deltaY
== 1 && deltaX
== 1)
247 if (deltaX
== 0 || deltaY
== 0) addMoves(step
);
250 if (deltaX
+ deltaY
== 3 && (deltaX
== 1 || deltaY
== 1))
254 if (deltaX
== deltaY
) addMoves(step
);
257 if (deltaX
== 0 || deltaY
== 0 || deltaX
== deltaY
)
261 if (deltaX
<= 1 && deltaY
<= 1) addMoves(step
, 1);
268 // If subTurn == 2 then we should have a first move,
269 // which restrict what we can play now: only in the first move direction
270 // NOTE: no need for knight or pawn checks, because the move will be
271 // naturally limited in those cases.
272 const L
= this.firstMove
.length
;
273 const fm
= this.firstMove
[L
-1];
274 if (fm
.appear
.length
== 2 && fm
.vanish
.length
== 2)
275 // Castle: no real move playable then.
277 if (fm
.appear
.length
== 0) {
278 // Piece at subTurn 1 just exited the board.
279 // Can I be a piece which caused the exit?
281 const moveOk
= V
.IsAprioriValidMove([x
, y
], [fm
.start
.x
, fm
.start
.y
]);
285 const dir
= this.getNormalizedDirection(
286 [fm
.start
.x
- x
, fm
.start
.y
- y
]);
287 return this.getMovesInDirection([x
, y
], dir
);
291 const dirM
= this.getNormalizedDirection(
292 [fm
.end
.x
- fm
.start
.x
, fm
.end
.y
- fm
.start
.y
]);
293 const dir
= this.getNormalizedDirection(
294 [fm
.start
.x
- x
, fm
.start
.y
- y
]);
295 // Normalized directions should match:
296 if (dir
[0] == dirM
[0] && dir
[1] == dirM
[1])
297 return this.getMovesInDirection([x
, y
], dir
);
302 // Does m2 un-do m1 ? (to disallow undoing actions)
303 oppositeMoves(m1
, m2
) {
304 const isEqual
= (av1
, av2
) => {
305 // Precondition: av1 and av2 length = 2
306 for (let av
of av1
) {
307 const avInAv2
= av2
.find(elt
=> {
315 if (!avInAv2
) return false;
320 m1
.appear
.length
== 2 &&
321 m2
.appear
.length
== 2 &&
322 m1
.vanish
.length
== 2 &&
323 m2
.vanish
.length
== 2 &&
324 isEqual(m1
.appear
, m2
.vanish
) &&
325 isEqual(m1
.vanish
, m2
.appear
)
329 getAmove(move1
, move2
) {
330 // Just merge (one is action one is move, one may be empty)
332 appear: move1
.appear
.concat(move2
.appear
),
333 vanish: move1
.vanish
.concat(move2
.vanish
)
338 const color
= this.turn
;
339 if (this.subTurn
== 1) {
340 return moves
.filter(m
=> {
341 // A move is valid either if it doesn't result in a check,
342 // or if a second move is possible to counter the check
343 // (not undoing a potential move + action of the opponent)
345 let res
= this.underCheck(color
);
347 const moves2
= this.getAllPotentialMoves();
350 const res2
= this.underCheck(color
);
362 const Lf
= this.firstMove
.length
;
363 const La
= this.amoves
.length
;
364 if (La
== 0) return super.filterValid(moves
);
368 // Move shouldn't undo another:
369 const amove
= this.getAmove(this.firstMove
[Lf
-1], m
);
370 return !this.oppositeMoves(this.amoves
[La
-1], amove
);
376 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, oneStep
) {
377 for (let step
of steps
) {
378 let rx
= x
+ step
[0],
380 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
386 this.getPiece(rx
, ry
) == piece
&&
387 this.getColor(rx
, ry
) == color
389 // Now step in the other direction: if end of the world, then attacked
394 this.board
[rx
][ry
] == V
.EMPTY
&&
400 if (!V
.OnBoard(rx
, ry
)) return true;
406 isAttackedByPawn([x
, y
], color
) {
407 const lastRank
= (color
== 'w' ? 0 : 7);
409 // The king can be pushed out by a pawn only on last rank
411 const pawnShift
= (color
== "w" ? 1 : -1);
412 for (let i
of [-1, 1]) {
416 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
417 this.getColor(x
+ pawnShift
, y
+ i
) == color
426 if (this.subTurn
== 2)
429 return super.getCurrentScore();
433 // If subTurn == 2 && square is empty && !underCheck,
434 // then return an empty move, allowing to "pass" subTurn2
437 this.board
[square
.x
][square
.y
] == V
.EMPTY
&&
438 !this.underCheck(this.turn
)
449 move.flags
= JSON
.stringify(this.aggregateFlags());
450 V
.PlayOnBoard(this.board
, move);
451 if (this.subTurn
== 2) {
452 this.turn
= V
.GetOppCol(this.turn
);
455 else this.firstMove
.push(move);
456 this.subTurn
= 3 - this.subTurn
;
460 updateCastleFlags(move, piece
) {
461 const c
= V
.GetOppCol(this.turn
);
462 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
463 // Update castling flags
464 if (piece
== V
.KING
) this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
465 for (let v
of move.vanish
) {
466 if (v
.x
== firstRank
&& this.castleFlags
[c
].includes(v
.y
)) {
467 const flagIdx
= (v
.y
== this.castleFlags
[c
][0] ? 0 : 1);
468 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
474 this.disaggregateFlags(JSON
.parse(move.flags
));
475 V
.UndoOnBoard(this.board
, move);
476 if (this.subTurn
== 1) {
477 this.turn
= V
.GetOppCol(this.turn
);
480 else this.firstMove
.pop();
481 this.subTurn
= 3 - this.subTurn
;