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