Fix MarseillRules
[vchess.git] / client / src / variants / Marseille.js
CommitLineData
829f6574 1import { ChessRules } from "@/base_rules";
5fde3a01 2import { randInt } from "@/utils/alea";
829f6574
BA
3
4export const VariantRules = class MarseilleRules extends ChessRules
4f7723a1 5{
6e62b1c7
BA
6 static IsGoodEnpassant(enpassant)
7 {
8 if (enpassant != "-")
9 {
10 const squares = enpassant.split(",");
11 if (squares.length > 2)
12 return false;
13 for (let sq of squares)
14 {
15 const ep = V.SquareToCoords(sq);
16 if (isNaN(ep.x) || !V.OnBoard(ep))
17 return false;
18 }
19 }
20 return true;
21 }
22
23 getTurnFen()
24 {
6e62b1c7
BA
25 return this.turn + this.subTurn;
26 }
27
28 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
29 getEnpassantFen()
30 {
31 const L = this.epSquares.length;
32 if (this.epSquares[L-1].every(epsq => epsq === undefined))
33 return "-"; //no en-passant
34 let res = "";
35 this.epSquares[L-1].forEach(epsq => {
36 if (!!epsq)
37 res += V.CoordsToSquare(epsq) + ",";
38 });
39 return res.slice(0,-1); //remove last comma
40 }
41
42 setOtherVariables(fen)
43 {
44 const parsedFen = V.ParseFen(fen);
45 this.setFlags(parsedFen.flags);
46 if (parsedFen.enpassant == "-")
51882959 47 this.epSquares = [ [undefined] ];
6e62b1c7
BA
48 else
49 {
50 let res = [];
51 const squares = parsedFen.enpassant.split(",");
52 for (let sq of squares)
53 res.push(V.SquareToCoords(sq));
6e62b1c7
BA
54 this.epSquares = [ res ];
55 }
56 this.scanKingsRooks(fen);
57 // Extract subTurn from turn indicator: "w" (first move), or
58 // "w1" or "w2" white subturn 1 or 2, and same for black
59 const fullTurn = V.ParseFen(fen).turn;
6e62b1c7 60 this.turn = fullTurn[0];
7d9e99bc 61 this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
6e62b1c7
BA
62 }
63
64 getPotentialPawnMoves([x,y])
65 {
66 const color = this.turn;
67 let moves = [];
68 const [sizeX,sizeY] = [V.size.x,V.size.y];
69 const shiftX = (color == "w" ? -1 : 1);
70 const firstRank = (color == 'w' ? sizeX-1 : 0);
71 const startRank = (color == "w" ? sizeX-2 : 1);
72 const lastRank = (color == "w" ? 0 : sizeX-1);
69f3d801
BA
73 const finalPieces = x + shiftX == lastRank
74 ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
75 : [V.PAWN];
6e62b1c7 76
69f3d801
BA
77 // One square forward
78 if (this.board[x+shiftX][y] == V.EMPTY)
6e62b1c7 79 {
69f3d801 80 for (let piece of finalPieces)
6e62b1c7 81 {
69f3d801 82 moves.push(this.getBasicMove([x,y], [x+shiftX,y],
edcd679a 83 {c:color,p:piece}));
6e62b1c7 84 }
69f3d801
BA
85 // Next condition because pawns on 1st rank can generally jump
86 if ([startRank,firstRank].includes(x)
87 && this.board[x+2*shiftX][y] == V.EMPTY)
6e62b1c7 88 {
69f3d801
BA
89 // Two squares jump
90 moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
91 }
92 }
93 // Captures
94 for (let shiftY of [-1,1])
95 {
96 if (y + shiftY >= 0 && y + shiftY < sizeY
97 && this.board[x+shiftX][y+shiftY] != V.EMPTY
98 && this.canTake([x,y], [x+shiftX,y+shiftY]))
99 {
100 for (let piece of finalPieces)
6e62b1c7 101 {
69f3d801 102 moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
edcd679a 103 {c:color,p:piece}));
6e62b1c7
BA
104 }
105 }
106 }
107
108 // En passant: always OK if subturn 1,
109 // OK on subturn 2 only if enPassant was played at subturn 1
110 // (and if there are two e.p. squares available).
111 const Lep = this.epSquares.length;
112 const epSquares = this.epSquares[Lep-1]; //always at least one element
113 let epSqs = [];
114 epSquares.forEach(sq => {
115 if (!!sq)
116 epSqs.push(sq);
117 });
118 if (epSqs.length == 0)
119 return moves;
26b8e4f7 120 const oppCol = V.GetOppCol(color);
6e62b1c7
BA
121 for (let sq of epSqs)
122 {
123 if (this.subTurn == 1 || (epSqs.length == 2 &&
124 // Was this en-passant capture already played at subturn 1 ?
69f3d801 125 // (Or maybe the opponent filled the en-passant square with a piece)
6e62b1c7
BA
126 this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
127 {
edcd679a
BA
128 if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
129 // Add condition "enemy pawn must be present"
130 && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
6e62b1c7
BA
131 {
132 let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
133 epMove.vanish.push({
134 x: x,
135 y: sq.y,
136 p: 'p',
edcd679a 137 c: oppCol
6e62b1c7
BA
138 });
139 moves.push(epMove);
140 }
141 }
142 }
143
144 return moves;
145 }
146
7d9e99bc 147 play(move)
6e62b1c7 148 {
6e62b1c7 149 move.flags = JSON.stringify(this.aggregateFlags());
829f6574 150 move.turn = this.turn + this.subTurn;
6e62b1c7 151 V.PlayOnBoard(this.board, move);
51882959 152 const epSq = this.getEpSquare(move);
7d9e99bc 153 if (this.subTurn == 0) //first move in game
51882959 154 {
6e62b1c7 155 this.turn = "b";
829f6574 156 this.subTurn = 1;
51882959
BA
157 this.epSquares.push([epSq]);
158 }
6e62b1c7 159 // Does this move give check on subturn 1? If yes, skip subturn 2
26b8e4f7 160 else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
6e62b1c7 161 {
26b8e4f7 162 this.turn = V.GetOppCol(this.turn);
51882959 163 this.epSquares.push([epSq]);
6e62b1c7
BA
164 move.checkOnSubturn1 = true;
165 }
166 else
167 {
168 if (this.subTurn == 2)
51882959 169 {
26b8e4f7 170 this.turn = V.GetOppCol(this.turn);
51882959
BA
171 let lastEpsq = this.epSquares[this.epSquares.length-1];
172 lastEpsq.push(epSq);
173 }
174 else
175 this.epSquares.push([epSq]);
6e62b1c7
BA
176 this.subTurn = 3 - this.subTurn;
177 }
6e62b1c7 178 this.updateVariables(move);
6e62b1c7
BA
179 }
180
181 undo(move)
182 {
183 this.disaggregateFlags(JSON.parse(move.flags));
6e62b1c7 184 V.UndoOnBoard(this.board, move);
7d9e99bc 185 if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
51882959 186 this.epSquares.pop();
7d9e99bc 187 else //this.subTurn == 1
6e62b1c7 188 {
7d9e99bc
BA
189 let lastEpsq = this.epSquares[this.epSquares.length-1];
190 lastEpsq.pop();
6e62b1c7 191 }
7d9e99bc
BA
192 this.turn = move.turn[0];
193 this.subTurn = parseInt(move.turn[1]);
6e62b1c7 194 this.unupdateVariables(move);
6e62b1c7
BA
195 }
196
197 // NOTE: GenRandInitFen() is OK,
198 // since at first move turn indicator is just "w"
199
0596f5e7
BA
200 static get VALUES()
201 {
202 return {
203 'p': 1,
204 'r': 5,
205 'n': 3,
206 'b': 3,
207 'q': 7, //slightly less than in orthodox game
208 'k': 1000
209 };
210 }
211
6e62b1c7
BA
212 // No alpha-beta here, just adapted min-max at depth 2(+1)
213 getComputerMove()
214 {
78bab51e
BA
215 if (this.subTurn == 2)
216 return null; //TODO: imperfect interface setup
217
6e62b1c7
BA
218 const maxeval = V.INFINITY;
219 const color = this.turn;
26b8e4f7 220 const oppCol = V.GetOppCol(this.turn);
51882959 221
6e62b1c7
BA
222 // Search best (half) move for opponent turn
223 const getBestMoveEval = () => {
51882959 224 const turnBefore = this.turn + this.subTurn;
5fde3a01
BA
225 let score = this.getCurrentScore();
226 if (score != "*")
6e62b1c7 227 {
6e62b1c7
BA
228 if (score == "1/2")
229 return 0;
230 return maxeval * (score == "1-0" ? 1 : -1);
231 }
5fde3a01 232 let moves = this.getAllValidMoves();
6e62b1c7
BA
233 let res = (oppCol == "w" ? -maxeval : maxeval);
234 for (let m of moves)
235 {
236 this.play(m);
5fde3a01 237 score = this.getCurrentScore();
51882959
BA
238 // Now turn is oppCol,2 if m doesn't give check
239 // Otherwise it's color,1. In both cases the next test makes sense
5fde3a01 240 if (score != "*")
6e62b1c7 241 {
6e62b1c7
BA
242 if (score == "1/2")
243 res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
244 else
245 {
246 // Found a mate
6e62b1c7
BA
247 this.undo(m);
248 return maxeval * (score == "1-0" ? 1 : -1);
249 }
250 }
251 const evalPos = this.evalPosition();
252 res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos));
6e62b1c7
BA
253 this.undo(m);
254 }
255 return res;
256 };
257
258 let moves11 = this.getAllValidMoves();
259 let doubleMoves = [];
260 // Rank moves using a min-max at depth 2
261 for (let i=0; i<moves11.length; i++)
262 {
6e62b1c7
BA
263 this.play(moves11[i]);
264 if (this.turn != color)
265 {
266 // We gave check with last move: search the best opponent move
267 doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
268 }
269 else
270 {
271 let moves12 = this.getAllValidMoves();
272 for (let j=0; j<moves12.length; j++)
273 {
274 this.play(moves12[j]);
275 doubleMoves.push({
276 moves:[moves11[i],moves12[j]],
277 eval:getBestMoveEval()});
278 this.undo(moves12[j]);
279 }
280 }
281 this.undo(moves11[i]);
282 }
283
284 doubleMoves.sort( (a,b) => {
285 return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
286 let candidates = [0]; //indices of candidates moves
287 for (let i=1;
288 i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
289 i++)
290 {
291 candidates.push(i);
292 }
78bab51e 293
5fde3a01 294 const selected = doubleMoves[randInt(candidates.length)].moves;
6e62b1c7
BA
295 if (selected.length == 1)
296 return selected[0];
297 return selected;
298 }
4f7723a1 299}