1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class Doublemove2Rules
extends ChessRules
{
6 static IsGoodEnpassant(enpassant
) {
7 const squares
= enpassant
.split(",");
8 if (squares
.length
> 2) return false;
9 for (let sq
of squares
) {
11 const ep
= V
.SquareToCoords(sq
);
12 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
18 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
20 return this.epSquares
[this.epSquares
.length
- 1].map(
21 epsq
=> epsq
=== undefined
23 : V
.CoordsToSquare(epsq
)
27 setOtherVariables(fen
) {
28 const parsedFen
= V
.ParseFen(fen
);
29 this.setFlags(parsedFen
.flags
);
30 this.epSquares
= [parsedFen
.enpassant
.split(",").map(sq
=> {
31 if (sq
!= "-") return V
.SquareToCoords(sq
);
35 // Extract subTurn from turn indicator: "w" (first move), or
36 // "w1" or "w2" white subturn 1 or 2, and same for black
37 this.turn
= parsedFen
.turn
;
41 getEnpassantCaptures([x
, y
], shiftX
) {
43 // En passant: always OK if subturn 1,
44 // OK on subturn 2 only if enPassant was played at subturn 1
45 // (and if there are two e.p. squares available).
46 const Lep
= this.epSquares
.length
;
47 const epSquares
= this.epSquares
[Lep
- 1]; //always at least one element
49 epSquares
.forEach(sq
=> {
50 if (sq
) epSqs
.push(sq
);
52 if (epSqs
.length
== 0) return moves
;
53 const oppCol
= V
.GetOppCol(this.getColor(x
, y
));
54 for (let sq
of epSqs
) {
58 // Was this en-passant capture already played at subturn 1 ?
59 // (Or maybe the opponent filled the en-passant square with a piece)
60 this.board
[epSqs
[0].x
][epSqs
[0].y
] != V
.EMPTY
)
64 Math
.abs(sq
.y
- y
) == 1 &&
65 // Add condition "enemy pawn must be present"
66 this.getPiece(x
, sq
.y
) == V
.PAWN
&&
67 this.getColor(x
, sq
.y
) == oppCol
69 let epMove
= this.getBasicMove([x
, y
], [sq
.x
, sq
.y
]);
84 // Goal is king capture => no checks
97 const color
= this.turn
;
98 if (this.kingPos
[color
][0] < 0) return (color
== 'w' ? "0-1" : "1-0");
103 move.flags
= JSON
.stringify(this.aggregateFlags());
104 V
.PlayOnBoard(this.board
, move);
105 const epSq
= this.getEpSquare(move);
106 if (this.subTurn
== 2) {
107 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
109 this.turn
= V
.GetOppCol(this.turn
);
112 this.epSquares
.push([epSq
]);
115 this.movesCount
== 1 ||
116 // King is captured at subTurn 1?
117 (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
122 if (this.movesCount
> 1) this.subTurn
= 3 - this.subTurn
;
127 const c
= move.vanish
[0].c
;
128 const piece
= move.vanish
[0].p
;
129 const firstRank
= c
== "w" ? V
.size
.x
- 1 : 0;
131 if (piece
== V
.KING
) {
132 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
133 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
136 const oppCol
= V
.GetOppCol(c
);
137 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
) {
138 // Opponent's king is captured, game over
139 this.kingPos
[oppCol
] = [-1, -1];
140 move.captureKing
= true; //for undo
142 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
144 move.start
.x
== firstRank
&& //our rook moves?
145 this.castleFlags
[c
].includes(move.start
.y
)
147 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
148 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
151 move.end
.x
== oppFirstRank
&& //we took opponent rook?
152 this.castleFlags
[oppCol
].includes(move.end
.y
)
154 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
155 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
160 this.disaggregateFlags(JSON
.parse(move.flags
));
161 V
.UndoOnBoard(this.board
, move);
162 if (this.subTurn
== 2 || this.movesCount
== 1 || !!move.captureKing
) {
163 this.epSquares
.pop();
165 if (this.movesCount
== 0) this.turn
= "w";
168 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
170 this.turn
= V
.GetOppCol(this.turn
);
172 if (this.movesCount
> 0) this.subTurn
= 3 - this.subTurn
;
177 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
178 // Opponent's king was captured
179 this.kingPos
[move.vanish
[1].c
] = [move.vanish
[1].x
, move.vanish
[1].y
];
180 super.postUndo(move);
183 // No alpha-beta here, just adapted min-max at depth 2(+1)
185 const maxeval
= V
.INFINITY
;
186 const color
= this.turn
;
187 const oppCol
= V
.GetOppCol(this.turn
);
189 // Search best (half) move for opponent turn
190 const getBestMoveEval
= () => {
191 let score
= this.getCurrentScore();
192 if (score
!= "*") return maxeval
* (score
== "1-0" ? 1 : -1);
193 let moves
= this.getAllValidMoves();
194 let res
= oppCol
== "w" ? -maxeval : maxeval
;
195 for (let m
of moves
) {
197 score
= this.getCurrentScore();
201 return maxeval
* (score
== "1-0" ? 1 : -1);
203 const evalPos
= this.evalPosition();
204 res
= oppCol
== "w" ? Math
.max(res
, evalPos
) : Math
.min(res
, evalPos
);
210 const moves11
= this.getAllValidMoves();
211 if (this.movesCount
== 0)
212 // First white move at random:
213 return moves11
[randInt(moves11
.length
)];
214 let doubleMove
= null;
215 let bestEval
= Number
.POSITIVE_INFINITY
* (color
== 'w' ? -1 : 1);
216 // Rank moves using a min-max at depth 2
217 for (let i
= 0; i
< moves11
.length
; i
++) {
218 this.play(moves11
[i
]);
219 const moves12
= this.getAllValidMoves();
220 for (let j
= 0; j
< moves12
.length
; j
++) {
221 this.play(moves12
[j
]);
222 // Small fluctuations to uniformize play a little
223 const evalM
= getBestMoveEval() + 0.05 - Math
.random() / 10
225 (color
== 'w' && evalM
> bestEval
) ||
226 (color
== 'b' && evalM
< bestEval
)
228 doubleMove
= [moves11
[i
], moves12
[j
]];
231 this.undo(moves12
[j
]);
233 this.undo(moves11
[i
]);
235 // TODO: not always the best move played (why ???)