1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class Doublemove1Rules
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 this.turn
= parsedFen
.turn
;
38 getEnpassantCaptures([x
, y
], shiftX
) {
40 // En passant: always OK if subturn 1,
41 // OK on subturn 2 only if enPassant was played at subturn 1
42 // (and if there are two e.p. squares available).
43 const Lep
= this.epSquares
.length
;
44 const epSquares
= this.epSquares
[Lep
- 1]; //always at least one element
46 epSquares
.forEach(sq
=> {
47 if (sq
) epSqs
.push(sq
);
49 if (epSqs
.length
== 0) return moves
;
50 const oppCol
= V
.GetOppCol(this.getColor(x
, y
));
51 for (let sq
of epSqs
) {
55 // Was this en-passant capture already played at subturn 1 ?
56 // (Or maybe the opponent filled the en-passant square with a piece)
57 this.board
[epSqs
[0].x
][epSqs
[0].y
] != V
.EMPTY
)
61 Math
.abs(sq
.y
- y
) == 1 &&
62 // Add condition "enemy pawn must be present"
63 this.getPiece(x
, sq
.y
) == V
.PAWN
&&
64 this.getColor(x
, sq
.y
) == oppCol
66 let epMove
= this.getBasicMove([x
, y
], [sq
.x
, sq
.y
]);
81 move.flags
= JSON
.stringify(this.aggregateFlags());
82 move.turn
= [this.turn
, this.subTurn
];
83 V
.PlayOnBoard(this.board
, move);
84 const epSq
= this.getEpSquare(move);
85 if (this.movesCount
== 0) {
88 this.epSquares
.push([epSq
]);
91 // Does this move give check on subturn 1? If yes, skip subturn 2
92 else if (this.subTurn
== 1 && this.underCheck(V
.GetOppCol(this.turn
))) {
93 this.turn
= V
.GetOppCol(this.turn
);
94 this.epSquares
.push([epSq
]);
95 move.checkOnSubturn1
= true;
99 if (this.subTurn
== 2) {
100 this.turn
= V
.GetOppCol(this.turn
);
101 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
105 this.epSquares
.push([epSq
]);
108 this.subTurn
= 3 - this.subTurn
;
114 const c
= move.turn
[0];
115 const piece
= move.vanish
[0].p
;
116 const firstRank
= c
== "w" ? V
.size
.x
- 1 : 0;
118 if (piece
== V
.KING
&& move.appear
.length
> 0) {
119 this.kingPos
[c
][0] = move.appear
[0].x
;
120 this.kingPos
[c
][1] = move.appear
[0].y
;
121 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
124 const oppCol
= V
.GetOppCol(c
);
125 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
127 move.start
.x
== firstRank
&& //our rook moves?
128 this.castleFlags
[c
].includes(move.start
.y
)
130 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
131 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
134 move.end
.x
== oppFirstRank
&& //we took opponent rook?
135 this.castleFlags
[oppCol
].includes(move.end
.y
)
137 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
138 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
143 this.disaggregateFlags(JSON
.parse(move.flags
));
144 V
.UndoOnBoard(this.board
, move);
145 if (this.movesCount
== 1 || !!move.checkOnSubturn1
|| this.subTurn
== 2) {
146 // The move may not be full, but is fully undone:
147 this.epSquares
.pop();
148 // Moves counter was just incremented:
152 // Undo the second half of a move
153 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
156 this.turn
= move.turn
[0];
157 this.subTurn
= move.turn
[1];
158 super.postUndo(move);
161 static get VALUES() {
167 q: 7, //slightly less than in orthodox game
172 // No alpha-beta here, just adapted min-max at depth 2(+1)
174 const maxeval
= V
.INFINITY
;
175 const color
= this.turn
;
176 const oppCol
= V
.GetOppCol(this.turn
);
178 // Search best (half) move for opponent turn
179 const getBestMoveEval
= () => {
180 let score
= this.getCurrentScore();
182 if (score
== "1/2") return 0;
183 return maxeval
* (score
== "1-0" ? 1 : -1);
185 let moves
= this.getAllValidMoves();
186 let res
= oppCol
== "w" ? -maxeval : maxeval
;
187 for (let m
of moves
) {
189 score
= this.getCurrentScore();
190 // Now turn is oppCol,2 if m doesn't give check
191 // Otherwise it's color,1. In both cases the next test makes sense
194 res
= oppCol
== "w" ? Math
.max(res
, 0) : Math
.min(res
, 0);
198 return maxeval
* (score
== "1-0" ? 1 : -1);
201 const evalPos
= this.evalPosition();
202 res
= oppCol
== "w" ? Math
.max(res
, evalPos
) : Math
.min(res
, evalPos
);
208 const moves11
= this.getAllValidMoves();
209 let doubleMoves
= [];
210 // Rank moves using a min-max at depth 2
211 for (let i
= 0; i
< moves11
.length
; i
++) {
212 this.play(moves11
[i
]);
213 if (this.turn
!= color
) {
214 // We gave check with last move: search the best opponent move
215 doubleMoves
.push({ moves: [moves11
[i
]], eval: getBestMoveEval() });
218 let moves12
= this.getAllValidMoves();
219 for (let j
= 0; j
< moves12
.length
; j
++) {
220 this.play(moves12
[j
]);
222 moves: [moves11
[i
], moves12
[j
]],
223 eval: getBestMoveEval() + 0.05 - Math
.random() / 10
225 this.undo(moves12
[j
]);
228 this.undo(moves11
[i
]);
231 // TODO: array + sort + candidates logic not required when adding small
232 // fluctuations to the eval function (could also be generalized).
233 doubleMoves
.sort((a
, b
) => {
234 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
236 let candidates
= [0]; //indices of candidates moves
239 i
< doubleMoves
.length
&& doubleMoves
[i
].eval
== doubleMoves
[0].eval
;
244 const selected
= doubleMoves
[randInt(candidates
.length
)].moves
;
245 if (selected
.length
== 1) return selected
[0];