1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class Doublemove2Rules
extends ChessRules
{
5 static IsGoodEnpassant(enpassant
) {
6 const squares
= enpassant
.split(",");
7 if (squares
.length
> 2) return false;
8 for (let sq
of squares
) {
10 const ep
= V
.SquareToCoords(sq
);
11 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
17 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
19 return this.epSquares
[this.epSquares
.length
- 1].map(
20 epsq
=> epsq
=== undefined
22 : V
.CoordsToSquare(epsq
)
26 setOtherVariables(fen
) {
27 const parsedFen
= V
.ParseFen(fen
);
28 this.setFlags(parsedFen
.flags
);
29 this.epSquares
= [parsedFen
.enpassant
.split(",").map(sq
=> {
30 if (sq
!= "-") return V
.SquareToCoords(sq
);
34 // Extract subTurn from turn indicator: "w" (first move), or
35 // "w1" or "w2" white subturn 1 or 2, and same for black
36 this.turn
= parsedFen
.turn
;
40 getEnpassantCaptures([x
, y
], shiftX
) {
42 // En passant: always OK if subturn 1,
43 // OK on subturn 2 only if enPassant was played at subturn 1
44 // (and if there are two e.p. squares available).
45 const Lep
= this.epSquares
.length
;
46 const epSquares
= this.epSquares
[Lep
- 1]; //always at least one element
48 epSquares
.forEach(sq
=> {
49 if (sq
) epSqs
.push(sq
);
51 if (epSqs
.length
== 0) return moves
;
52 const oppCol
= V
.GetOppCol(this.getColor(x
, y
));
53 for (let sq
of epSqs
) {
57 // Was this en-passant capture already played at subturn 1 ?
58 // (Or maybe the opponent filled the en-passant square with a piece)
59 this.board
[epSqs
[0].x
][epSqs
[0].y
] != V
.EMPTY
)
63 Math
.abs(sq
.y
- y
) == 1 &&
64 // Add condition "enemy pawn must be present"
65 this.getPiece(x
, sq
.y
) == V
.PAWN
&&
66 this.getColor(x
, sq
.y
) == oppCol
68 let epMove
= this.getBasicMove([x
, y
], [sq
.x
, sq
.y
]);
82 isAttacked(sq
, color
, castling
) {
83 const singleMoveAttack
= super.isAttacked(sq
, color
);
84 if (singleMoveAttack
) return true;
86 if (this.subTurn
== 1)
87 // Castling at move 1 could be done into check
89 return singleMoveAttack
;
91 // Double-move allowed:
92 const curTurn
= this.turn
;
94 const moves1
= super.getAllPotentialMoves();
96 for (let move of moves1
) {
98 const res
= super.isAttacked(sq
, color
);
106 if (this.subTurn
== 1) {
107 return moves
.filter(m1
=> {
109 // NOTE: no recursion because next call will see subTurn == 2
110 const res
= super.atLeastOneMove();
115 return super.filterValid(moves
);
119 move.flags
= JSON
.stringify(this.aggregateFlags());
120 V
.PlayOnBoard(this.board
, move);
121 const epSq
= this.getEpSquare(move);
122 if (this.subTurn
== 2) {
123 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
125 this.turn
= V
.GetOppCol(this.turn
);
128 this.epSquares
.push([epSq
]);
130 if (this.movesCount
== 1) this.turn
= "b";
132 if (this.movesCount
> 1) this.subTurn
= 3 - this.subTurn
;
137 const c
= move.vanish
[0].c
;
138 const piece
= move.vanish
[0].p
;
139 const firstRank
= c
== "w" ? V
.size
.x
- 1 : 0;
141 if (piece
== V
.KING
&& move.appear
.length
> 0) {
142 this.kingPos
[c
][0] = move.appear
[0].x
;
143 this.kingPos
[c
][1] = move.appear
[0].y
;
144 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
147 const oppCol
= V
.GetOppCol(c
);
148 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
150 move.start
.x
== firstRank
&& //our rook moves?
151 this.castleFlags
[c
].includes(move.start
.y
)
153 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
154 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
156 move.end
.x
== oppFirstRank
&& //we took opponent rook?
157 this.castleFlags
[oppCol
].includes(move.end
.y
)
159 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
160 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
165 this.disaggregateFlags(JSON
.parse(move.flags
));
166 V
.UndoOnBoard(this.board
, move);
167 if (this.subTurn
== 2 || this.movesCount
== 1) {
168 this.epSquares
.pop();
170 if (this.movesCount
== 0) this.turn
= "w";
173 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
175 this.turn
= V
.GetOppCol(this.turn
);
177 if (this.movesCount
> 0) this.subTurn
= 3 - this.subTurn
;
178 super.postUndo(move);
181 static get VALUES() {
187 q: 7, //slightly less than in orthodox game
192 // No alpha-beta here, just adapted min-max at depth 1(+1)
194 const color
= this.turn
;
195 const moves11
= this.getAllValidMoves();
196 if (this.movesCount
== 0)
197 // First white move at random:
198 return moves11
[randInt(moves11
.length
)];
200 let doubleMoves
= [];
201 // Rank moves using a min-max at depth 2
202 for (let i
= 0; i
< moves11
.length
; i
++) {
203 this.play(moves11
[i
]);
204 const moves12
= this.getAllValidMoves();
205 for (let j
= 0; j
< moves12
.length
; j
++) {
206 this.play(moves12
[j
]);
208 moves: [moves11
[i
], moves12
[j
]],
209 eval: this.evalPosition()
211 this.undo(moves12
[j
]);
213 this.undo(moves11
[i
]);
216 doubleMoves
.sort((a
, b
) => {
217 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
219 let candidates
= [0]; //indices of candidates moves
222 i
< doubleMoves
.length
&& doubleMoves
[i
].eval
== doubleMoves
[0].eval
;
227 return doubleMoves
[randInt(candidates
.length
)].moves
;