Update translations and TODO
[vchess.git] / public / javascripts / 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 {
22 if (this.startAtFirstMove && this.moves.length==0)
23 return "w";
24 return this.turn + this.subTurn;
25 }
26
27 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
28 getEnpassantFen()
29 {
30 const L = this.epSquares.length;
31 if (this.epSquares[L-1].every(epsq => epsq === undefined))
32 return "-"; //no en-passant
33 let res = "";
34 this.epSquares[L-1].forEach(epsq => {
35 if (!!epsq)
36 res += V.CoordsToSquare(epsq) + ",";
37 });
38 return res.slice(0,-1); //remove last comma
39 }
40
41 setOtherVariables(fen)
42 {
43 const parsedFen = V.ParseFen(fen);
44 this.setFlags(parsedFen.flags);
45 if (parsedFen.enpassant == "-")
51882959 46 this.epSquares = [ [undefined] ];
6e62b1c7
BA
47 else
48 {
49 let res = [];
50 const squares = parsedFen.enpassant.split(",");
51 for (let sq of squares)
52 res.push(V.SquareToCoords(sq));
6e62b1c7
BA
53 this.epSquares = [ res ];
54 }
55 this.scanKingsRooks(fen);
56 // Extract subTurn from turn indicator: "w" (first move), or
57 // "w1" or "w2" white subturn 1 or 2, and same for black
58 const fullTurn = V.ParseFen(fen).turn;
59 this.startAtFirstMove = (fullTurn == "w");
60 this.turn = fullTurn[0];
61 this.subTurn = (fullTurn[1] || 1);
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);
73 const pawnColor = this.getColor(x,y); //can be different for checkered
69f3d801
BA
74 const finalPieces = x + shiftX == lastRank
75 ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
76 : [V.PAWN];
6e62b1c7 77
69f3d801
BA
78 // One square forward
79 if (this.board[x+shiftX][y] == V.EMPTY)
6e62b1c7 80 {
69f3d801 81 for (let piece of finalPieces)
6e62b1c7 82 {
69f3d801
BA
83 moves.push(this.getBasicMove([x,y], [x+shiftX,y],
84 {c:pawnColor,p:piece}));
6e62b1c7 85 }
69f3d801
BA
86 // Next condition because pawns on 1st rank can generally jump
87 if ([startRank,firstRank].includes(x)
88 && this.board[x+2*shiftX][y] == V.EMPTY)
6e62b1c7 89 {
69f3d801
BA
90 // Two squares jump
91 moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
92 }
93 }
94 // Captures
95 for (let shiftY of [-1,1])
96 {
97 if (y + shiftY >= 0 && y + shiftY < sizeY
98 && this.board[x+shiftX][y+shiftY] != V.EMPTY
99 && this.canTake([x,y], [x+shiftX,y+shiftY]))
100 {
101 for (let piece of finalPieces)
6e62b1c7 102 {
69f3d801
BA
103 moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
104 {c:pawnColor,p:piece}));
6e62b1c7
BA
105 }
106 }
107 }
108
109 // En passant: always OK if subturn 1,
110 // OK on subturn 2 only if enPassant was played at subturn 1
111 // (and if there are two e.p. squares available).
112 const Lep = this.epSquares.length;
113 const epSquares = this.epSquares[Lep-1]; //always at least one element
114 let epSqs = [];
115 epSquares.forEach(sq => {
116 if (!!sq)
117 epSqs.push(sq);
118 });
119 if (epSqs.length == 0)
120 return moves;
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 {
128 if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1)
129 {
130 let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
131 epMove.vanish.push({
132 x: x,
133 y: sq.y,
134 p: 'p',
135 c: this.getColor(x,sq.y)
136 });
137 moves.push(epMove);
138 }
139 }
140 }
141
142 return moves;
143 }
144
145 play(move, ingame)
146 {
6e62b1c7 147 if (!!ingame)
1b61a94d 148 {
6e62b1c7 149 move.notation = [this.getNotation(move), this.getLongNotation(move)];
1b61a94d
BA
150 // In this special case, we also need the "move color":
151 move.color = this.turn;
152 }
6e62b1c7 153 move.flags = JSON.stringify(this.aggregateFlags());
6e62b1c7 154 V.PlayOnBoard(this.board, move);
51882959 155 const epSq = this.getEpSquare(move);
6e62b1c7 156 if (this.startAtFirstMove && this.moves.length == 0)
51882959 157 {
6e62b1c7 158 this.turn = "b";
51882959
BA
159 this.epSquares.push([epSq]);
160 }
6e62b1c7
BA
161 // Does this move give check on subturn 1? If yes, skip subturn 2
162 else if (this.subTurn==1 && this.underCheck(this.getOppCol(this.turn)))
163 {
6e62b1c7 164 this.turn = this.getOppCol(this.turn);
51882959 165 this.epSquares.push([epSq]);
6e62b1c7
BA
166 move.checkOnSubturn1 = true;
167 }
168 else
169 {
170 if (this.subTurn == 2)
51882959 171 {
6e62b1c7 172 this.turn = this.getOppCol(this.turn);
51882959
BA
173 let lastEpsq = this.epSquares[this.epSquares.length-1];
174 lastEpsq.push(epSq);
175 }
176 else
177 this.epSquares.push([epSq]);
6e62b1c7
BA
178 this.subTurn = 3 - this.subTurn;
179 }
180 this.moves.push(move);
181 this.updateVariables(move);
182 if (!!ingame)
183 move.hash = hex_md5(this.getFen());
6e62b1c7
BA
184 }
185
186 undo(move)
187 {
188 this.disaggregateFlags(JSON.parse(move.flags));
6e62b1c7
BA
189 V.UndoOnBoard(this.board, move);
190 if (this.startAtFirstMove && this.moves.length == 1)
51882959 191 {
6e62b1c7 192 this.turn = "w";
51882959
BA
193 this.epSquares.pop();
194 }
6e62b1c7
BA
195 else if (move.checkOnSubturn1)
196 {
197 this.turn = this.getOppCol(this.turn);
198 this.subTurn = 1;
51882959 199 this.epSquares.pop();
6e62b1c7
BA
200 }
201 else
202 {
203 if (this.subTurn == 1)
51882959 204 {
6e62b1c7 205 this.turn = this.getOppCol(this.turn);
51882959
BA
206 let lastEpsq = this.epSquares[this.epSquares.length-1];
207 lastEpsq.pop();
208 }
209 else
210 this.epSquares.pop();
6e62b1c7
BA
211 this.subTurn = 3 - this.subTurn;
212 }
213 this.moves.pop();
214 this.unupdateVariables(move);
6e62b1c7
BA
215 }
216
217 // NOTE: GenRandInitFen() is OK,
218 // since at first move turn indicator is just "w"
219
0596f5e7
BA
220 static get VALUES()
221 {
222 return {
223 'p': 1,
224 'r': 5,
225 'n': 3,
226 'b': 3,
227 'q': 7, //slightly less than in orthodox game
228 'k': 1000
229 };
230 }
231
6e62b1c7
BA
232 // No alpha-beta here, just adapted min-max at depth 2(+1)
233 getComputerMove()
234 {
78bab51e
BA
235 if (this.subTurn == 2)
236 return null; //TODO: imperfect interface setup
237
6e62b1c7
BA
238 const maxeval = V.INFINITY;
239 const color = this.turn;
240 const oppCol = this.getOppCol(this.turn);
51882959 241
6e62b1c7
BA
242 // Search best (half) move for opponent turn
243 const getBestMoveEval = () => {
51882959 244 const turnBefore = this.turn + this.subTurn;
6e62b1c7
BA
245 let moves = this.getAllValidMoves();
246 if (moves.length == 0)
247 {
248 const score = this.checkGameEnd();
249 if (score == "1/2")
250 return 0;
251 return maxeval * (score == "1-0" ? 1 : -1);
252 }
253 let res = (oppCol == "w" ? -maxeval : maxeval);
254 for (let m of moves)
255 {
256 this.play(m);
51882959
BA
257 // Now turn is oppCol,2 if m doesn't give check
258 // Otherwise it's color,1. In both cases the next test makes sense
6e62b1c7
BA
259 if (!this.atLeastOneMove())
260 {
261 const score = this.checkGameEnd();
262 if (score == "1/2")
263 res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
264 else
265 {
266 // Found a mate
6e62b1c7
BA
267 this.undo(m);
268 return maxeval * (score == "1-0" ? 1 : -1);
269 }
270 }
271 const evalPos = this.evalPosition();
272 res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos));
6e62b1c7
BA
273 this.undo(m);
274 }
275 return res;
276 };
277
278 let moves11 = this.getAllValidMoves();
279 let doubleMoves = [];
280 // Rank moves using a min-max at depth 2
281 for (let i=0; i<moves11.length; i++)
282 {
6e62b1c7
BA
283 this.play(moves11[i]);
284 if (this.turn != color)
285 {
286 // We gave check with last move: search the best opponent move
287 doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
288 }
289 else
290 {
291 let moves12 = this.getAllValidMoves();
292 for (let j=0; j<moves12.length; j++)
293 {
294 this.play(moves12[j]);
295 doubleMoves.push({
296 moves:[moves11[i],moves12[j]],
297 eval:getBestMoveEval()});
298 this.undo(moves12[j]);
299 }
300 }
301 this.undo(moves11[i]);
302 }
303
304 doubleMoves.sort( (a,b) => {
305 return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
306 let candidates = [0]; //indices of candidates moves
307 for (let i=1;
308 i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
309 i++)
310 {
311 candidates.push(i);
312 }
78bab51e 313
6e62b1c7
BA
314 const selected = doubleMoves[_.sample(candidates, 1)].moves;
315 if (selected.length == 1)
316 return selected[0];
317 return selected;
318 }
1b61a94d
BA
319
320 getPGN(mycolor, score, fenStart, mode)
321 {
322 let pgn = "";
323 pgn += '[Site "vchess.club"]<br>';
324 const opponent = mode=="human" ? "Anonymous" : "Computer";
325 pgn += '[Variant "' + variant + '"]<br>';
326 pgn += '[Date "' + getDate(new Date()) + '"]<br>';
327 pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
328 pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
329 pgn += '[FenStart "' + fenStart + '"]<br>';
330 pgn += '[Fen "' + this.getFen() + '"]<br>';
331 pgn += '[Result "' + score + '"]<br><br>';
332
333 let counter = 1;
334 let i = 0;
335 while (i < this.moves.length)
336 {
337 pgn += (counter++) + ".";
338 for (let color of ["w","b"])
339 {
340 let move = "";
341 while (i < this.moves.length && this.moves[i].color == color)
342 move += this.moves[i++].notation[0] + ",";
343 move = move.slice(0,-1); //remove last comma
344 pgn += move + (i < this.moves.length-1 ? " " : "");
345 }
346 }
347 pgn += "<br><br>";
348
349 // "Complete moves" PGN (helping in ambiguous cases)
350 counter = 1;
351 i = 0;
352 while (i < this.moves.length)
353 {
354 pgn += (counter++) + ".";
355 for (let color of ["w","b"])
356 {
357 let move = "";
358 while (i < this.moves.length && this.moves[i].color == color)
359 move += this.moves[i++].notation[1] + ",";
360 move = move.slice(0,-1); //remove last comma
361 pgn += move + (i < this.moves.length-1 ? " " : "");
362 }
363 }
364
365 return pgn;
366 }
4f7723a1
BA
367}
368
369const VariantRules = MarseilleRules;