A few bug fixes
[vchess.git] / client / src / variants / Doublemove2.js
CommitLineData
829f6574 1import { ChessRules } from "@/base_rules";
5fde3a01 2import { randInt } from "@/utils/alea";
829f6574 3
b406466b 4export class Doublemove2Rules extends ChessRules {
7e8a7ea1 5
6808d7a1 6 static IsGoodEnpassant(enpassant) {
e71161fb
BA
7 const squares = enpassant.split(",");
8 if (squares.length > 2) return false;
9 for (let sq of squares) {
10 if (sq != "-") {
dac39588 11 const ep = V.SquareToCoords(sq);
6808d7a1 12 if (isNaN(ep.x) || !V.OnBoard(ep)) return false;
dac39588
BA
13 }
14 }
15 return true;
16 }
6e62b1c7 17
dac39588 18 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
6808d7a1 19 getEnpassantFen() {
e71161fb
BA
20 return this.epSquares[this.epSquares.length - 1].map(
21 epsq => epsq === undefined
22 ? "-" //no en-passant
23 : V.CoordsToSquare(epsq)
24 ).join(",");
dac39588 25 }
6e62b1c7 26
6808d7a1 27 setOtherVariables(fen) {
dac39588
BA
28 const parsedFen = V.ParseFen(fen);
29 this.setFlags(parsedFen.flags);
e71161fb
BA
30 this.epSquares = [parsedFen.enpassant.split(",").map(sq => {
31 if (sq != "-") return V.SquareToCoords(sq);
32 return undefined;
33 })];
3a2a7b5f 34 this.scanKings(fen);
dac39588
BA
35 // Extract subTurn from turn indicator: "w" (first move), or
36 // "w1" or "w2" white subturn 1 or 2, and same for black
af34341d
BA
37 this.turn = parsedFen.turn;
38 this.subTurn = 1;
dac39588 39 }
6e62b1c7 40
32f6285e 41 getEnpassantCaptures([x, y], shiftX) {
dac39588 42 let moves = [];
dac39588
BA
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;
6808d7a1 47 const epSquares = this.epSquares[Lep - 1]; //always at least one element
dac39588
BA
48 let epSqs = [];
49 epSquares.forEach(sq => {
6808d7a1 50 if (sq) epSqs.push(sq);
dac39588 51 });
6808d7a1 52 if (epSqs.length == 0) return moves;
32f6285e 53 const oppCol = V.GetOppCol(this.getColor(x, y));
6808d7a1
BA
54 for (let sq of epSqs) {
55 if (
56 this.subTurn == 1 ||
57 (epSqs.length == 2 &&
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)
61 ) {
62 if (
63 sq.x == x + shiftX &&
64 Math.abs(sq.y - y) == 1 &&
dac39588 65 // Add condition "enemy pawn must be present"
6808d7a1
BA
66 this.getPiece(x, sq.y) == V.PAWN &&
67 this.getColor(x, sq.y) == oppCol
68 ) {
69 let epMove = this.getBasicMove([x, y], [sq.x, sq.y]);
dac39588
BA
70 epMove.vanish.push({
71 x: x,
72 y: sq.y,
6808d7a1 73 p: "p",
dac39588
BA
74 c: oppCol
75 });
76 moves.push(epMove);
77 }
78 }
79 }
dac39588
BA
80 return moves;
81 }
6e62b1c7 82
34bfe151 83 isAttacked() {
bc0b9205 84 // Goal is king capture => no checks
b406466b
BA
85 return false;
86 }
87
88 filterValid(moves) {
bc0b9205
BA
89 return moves;
90 }
91
92 getCheckSquares() {
93 return [];
94 }
95
96 getCurrentScore() {
97 const color = this.turn;
98 if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0");
99 return "*";
b406466b
BA
100 }
101
6808d7a1 102 play(move) {
dac39588 103 move.flags = JSON.stringify(this.aggregateFlags());
dac39588
BA
104 V.PlayOnBoard(this.board, move);
105 const epSq = this.getEpSquare(move);
b406466b
BA
106 if (this.subTurn == 2) {
107 let lastEpsq = this.epSquares[this.epSquares.length - 1];
108 lastEpsq.push(epSq);
dac39588 109 this.turn = V.GetOppCol(this.turn);
b406466b
BA
110 }
111 else {
dac39588 112 this.epSquares.push([epSq]);
9842aca2 113 this.movesCount++;
964eda04
BA
114 if (
115 this.movesCount == 1 ||
116 // King is captured at subTurn 1?
117 (move.vanish.length == 2 && move.vanish[1].p == V.KING)
118 ) {
119 this.turn = "b";
120 }
dac39588 121 }
b406466b 122 if (this.movesCount > 1) this.subTurn = 3 - this.subTurn;
3a2a7b5f
BA
123 this.postPlay(move);
124 }
125
126 postPlay(move) {
b406466b 127 const c = move.vanish[0].c;
3a2a7b5f
BA
128 const piece = move.vanish[0].p;
129 const firstRank = c == "w" ? V.size.x - 1 : 0;
130
737a5daf 131 if (piece == V.KING) {
bc0b9205 132 this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
0d5335de 133 this.castleFlags[c] = [V.size.y, V.size.y];
3a2a7b5f
BA
134 return;
135 }
136 const oppCol = V.GetOppCol(c);
964eda04 137 if (move.vanish.length == 2 && move.vanish[1].p == V.KING) {
bc0b9205
BA
138 // Opponent's king is captured, game over
139 this.kingPos[oppCol] = [-1, -1];
964eda04
BA
140 move.captureKing = true; //for undo
141 }
3a2a7b5f
BA
142 const oppFirstRank = V.size.x - 1 - firstRank;
143 if (
144 move.start.x == firstRank && //our rook moves?
145 this.castleFlags[c].includes(move.start.y)
146 ) {
147 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
148 this.castleFlags[c][flagIdx] = V.size.y;
bc0b9205 149 }
737a5daf 150 if (
3a2a7b5f
BA
151 move.end.x == oppFirstRank && //we took opponent rook?
152 this.castleFlags[oppCol].includes(move.end.y)
153 ) {
154 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
155 this.castleFlags[oppCol][flagIdx] = V.size.y;
156 }
dac39588 157 }
6e62b1c7 158
6808d7a1 159 undo(move) {
dac39588
BA
160 this.disaggregateFlags(JSON.parse(move.flags));
161 V.UndoOnBoard(this.board, move);
964eda04 162 if (this.subTurn == 2 || this.movesCount == 1 || !!move.captureKing) {
dac39588 163 this.epSquares.pop();
9842aca2 164 this.movesCount--;
b406466b
BA
165 if (this.movesCount == 0) this.turn = "w";
166 }
167 else {
6808d7a1 168 let lastEpsq = this.epSquares[this.epSquares.length - 1];
dac39588 169 lastEpsq.pop();
b406466b 170 this.turn = V.GetOppCol(this.turn);
dac39588 171 }
b406466b 172 if (this.movesCount > 0) this.subTurn = 3 - this.subTurn;
bc0b9205 173 this.postUndo(move);
dac39588 174 }
6e62b1c7 175
bc0b9205
BA
176 postUndo(move) {
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);
dac39588 181 }
0596f5e7 182
bc0b9205 183 // No alpha-beta here, just adapted min-max at depth 2(+1)
6808d7a1 184 getComputerMove() {
bc0b9205 185 const maxeval = V.INFINITY;
dac39588 186 const color = this.turn;
bc0b9205
BA
187 const oppCol = V.GetOppCol(this.turn);
188
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) {
196 this.play(m);
197 score = this.getCurrentScore();
198 if (score != "*") {
199 // King captured
200 this.undo(m);
201 return maxeval * (score == "1-0" ? 1 : -1);
202 }
203 const evalPos = this.evalPosition();
204 res = oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos);
205 this.undo(m);
206 }
207 return res;
208 };
209
5d75c82c
BA
210 const moves11 = this.getAllValidMoves();
211 if (this.movesCount == 0)
212 // First white move at random:
213 return moves11[randInt(moves11.length)];
737a5daf
BA
214 let doubleMove = null;
215 let bestEval = Number.POSITIVE_INFINITY * (color == 'w' ? -1 : 1);
5d75c82c 216 // Rank moves using a min-max at depth 2
6808d7a1 217 for (let i = 0; i < moves11.length; i++) {
dac39588 218 this.play(moves11[i]);
5d75c82c 219 const moves12 = this.getAllValidMoves();
b406466b
BA
220 for (let j = 0; j < moves12.length; j++) {
221 this.play(moves12[j]);
737a5daf
BA
222 // Small fluctuations to uniformize play a little
223 const evalM = getBestMoveEval() + 0.05 - Math.random() / 10
224 if (
225 (color == 'w' && evalM > bestEval) ||
226 (color == 'b' && evalM < bestEval)
227 ) {
228 doubleMove = [moves11[i], moves12[j]];
229 bestEval = evalM;
230 }
b406466b 231 this.undo(moves12[j]);
dac39588
BA
232 }
233 this.undo(moves11[i]);
234 }
737a5daf
BA
235 // TODO: not always the best move played (why ???)
236 return doubleMove;
dac39588 237 }
7e8a7ea1 238
6808d7a1 239};