Attempt to clarify installation instructions a little
[vchess.git] / client / src / variants / Doublemove2.js
1 import { ChessRules } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class Doublemove2Rules extends ChessRules {
5
6 static IsGoodEnpassant(enpassant) {
7 const squares = enpassant.split(",");
8 if (squares.length > 2) return false;
9 for (let sq of squares) {
10 if (sq != "-") {
11 const ep = V.SquareToCoords(sq);
12 if (isNaN(ep.x) || !V.OnBoard(ep)) return false;
13 }
14 }
15 return true;
16 }
17
18 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
19 getEnpassantFen() {
20 return this.epSquares[this.epSquares.length - 1].map(
21 epsq => epsq === undefined
22 ? "-" //no en-passant
23 : V.CoordsToSquare(epsq)
24 ).join(",");
25 }
26
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);
32 return undefined;
33 })];
34 this.scanKings(fen);
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;
38 this.subTurn = 1;
39 }
40
41 getEnpassantCaptures([x, y], shiftX) {
42 let moves = [];
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
48 let epSqs = [];
49 epSquares.forEach(sq => {
50 if (sq) epSqs.push(sq);
51 });
52 if (epSqs.length == 0) return moves;
53 const oppCol = V.GetOppCol(this.getColor(x, y));
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 &&
65 // Add condition "enemy pawn must be present"
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]);
70 epMove.vanish.push({
71 x: x,
72 y: sq.y,
73 p: "p",
74 c: oppCol
75 });
76 moves.push(epMove);
77 }
78 }
79 }
80 return moves;
81 }
82
83 isAttacked() {
84 // Goal is king capture => no checks
85 return false;
86 }
87
88 filterValid(moves) {
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 "*";
100 }
101
102 play(move) {
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];
108 lastEpsq.push(epSq);
109 this.turn = V.GetOppCol(this.turn);
110 }
111 else {
112 this.epSquares.push([epSq]);
113 this.movesCount++;
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 }
121 }
122 if (this.movesCount > 1) this.subTurn = 3 - this.subTurn;
123 this.postPlay(move);
124 }
125
126 postPlay(move) {
127 const c = move.vanish[0].c;
128 const piece = move.vanish[0].p;
129 const firstRank = c == "w" ? V.size.x - 1 : 0;
130
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];
134 return;
135 }
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
141 }
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;
149 }
150 if (
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 }
157 }
158
159 undo(move) {
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();
164 this.movesCount--;
165 if (this.movesCount == 0) this.turn = "w";
166 }
167 else {
168 let lastEpsq = this.epSquares[this.epSquares.length - 1];
169 lastEpsq.pop();
170 this.turn = V.GetOppCol(this.turn);
171 }
172 if (this.movesCount > 0) this.subTurn = 3 - this.subTurn;
173 this.postUndo(move);
174 }
175
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);
181 }
182
183 // No alpha-beta here, just adapted min-max at depth 2(+1)
184 getComputerMove() {
185 const maxeval = V.INFINITY;
186 const color = this.turn;
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
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
224 if (
225 (color == 'w' && evalM > bestEval) ||
226 (color == 'b' && evalM < bestEval)
227 ) {
228 doubleMove = [moves11[i], moves12[j]];
229 bestEval = evalM;
230 }
231 this.undo(moves12[j]);
232 }
233 this.undo(moves11[i]);
234 }
235 // TODO: not always the best move played (why ???)
236 return doubleMove;
237 }
238
239 };