Fix Konane and Yote computer play + a few bugs
[vchess.git] / client / src / variants / Synchrone2.js
1 import { ChessRules, Move } from "@/base_rules";
2 import { Synchrone1Rules } from "@/variants/Synchrone1";
3 import { randInt } from "@/utils/alea";
4
5 export class Synchrone2Rules extends Synchrone1Rules {
6
7 static get CanAnalyze() {
8 return false;
9 }
10
11 static get HasEnpassant() {
12 return false;
13 }
14
15 static IsGoodFen(fen) {
16 if (!Synchrone1Rules.IsGoodFen(fen)) return false;
17 const fenParsed = V.ParseFen(fen);
18 // 5) Check initFen (not really... TODO?)
19 if (!fenParsed.initFen) return false;
20 return true;
21 }
22
23 static ParseFen(fen) {
24 const fenParts = fen.split(" ");
25 return Object.assign(
26 {
27 initFen: fenParts[4],
28 whiteMove: fenParts[5]
29 },
30 ChessRules.ParseFen(fen)
31 );
32 }
33
34 getInitfenFen() {
35 const L = this.initfenStack.length;
36 return L > 0 ? this.initfenStack[L-1] : "-";
37 }
38
39 getFen() {
40 return (
41 super.getBaseFen() + " " +
42 super.getTurnFen() + " " +
43 this.movesCount + " " +
44 super.getFlagsFen() + " " +
45 this.getInitfenFen() + " " +
46 this.getWhitemoveFen()
47 );
48 }
49
50 static GenRandInitFen(randomness) {
51 const res = ChessRules.GenRandInitFen(randomness);
52 // Add initFen field:
53 return res.slice(0, -1) + res.split(' ')[0] + " -";
54 }
55
56 setOtherVariables(fen) {
57 const parsedFen = V.ParseFen(fen);
58 this.setFlags(parsedFen.flags);
59 super.scanKings(fen);
60 // Also init whiteMove
61 this.whiteMove =
62 parsedFen.whiteMove != "-"
63 ? JSON.parse(parsedFen.whiteMove)
64 : null;
65 // And initFen (could be empty)
66 this.initfenStack = [];
67 if (parsedFen.initFen != "-") this.initfenStack.push(parsedFen.initFen);
68 }
69
70 getPotentialMovesFrom([x, y]) {
71 if (this.movesCount % 4 <= 1) return super.getPotentialMovesFrom([x, y]);
72 // Diff current and old board to know which pieces have moved,
73 // and to deduce possible moves at stage 2.
74 const L = this.initfenStack.length;
75 let initBoard = V.GetBoard(this.initfenStack[L-1]);
76 let appeared = [];
77 const c = this.turn;
78 const oppCol = V.GetOppCol(c);
79 for (let i=0; i<8; i++) {
80 for (let j=0; j<8; j++) {
81 if (this.board[i][j] != initBoard[i][j]) {
82 if (this.board[i][j] != V.EMPTY) {
83 const color = this.board[i][j].charAt(0);
84 appeared.push({ c: color, x: i, y: j });
85 // Pawns capture in diagonal => the following fix.
86 // (Other way would be to redefine getPotentialPawnMoves()...)
87 if (color == oppCol) initBoard[i][j] = this.board[i][j];
88 }
89 }
90 }
91 }
92 const saveBoard = this.board;
93 this.board = initBoard;
94 const movesInit = super.getPotentialMovesFrom([x, y]);
95 this.board = saveBoard;
96 const target = appeared.find(a => a.c == oppCol) || { x: -1, y: -1 };
97 let movesNow = super.getPotentialMovesFrom([x, y]).filter(m => {
98 return (
99 m.end.x == target.x &&
100 m.end.y == target.y &&
101 movesInit.some(mi => mi.end.x == m.end.x && mi.end.y == m.end.y)
102 );
103 });
104 const passTarget =
105 (x != this.kingPos[c][0] || y != this.kingPos[c][0]) ? c : oppCol;
106 movesNow.push(
107 new Move({
108 start: { x: x, y: y },
109 end: {
110 x: this.kingPos[passTarget][0],
111 y: this.kingPos[passTarget][1]
112 },
113 appear: [],
114 vanish: []
115 })
116 );
117 return movesNow;
118 }
119
120 filterValid(moves) {
121 const nonEmptyMove = moves.find(m => m.vanish.length > 0);
122 if (!nonEmptyMove) return moves;
123 // filterValid can be called when it's "not our turn":
124 const color = nonEmptyMove.vanish[0].c;
125 return moves.filter(m => {
126 if (m.vanish.length == 0) return true;
127 const piece = m.vanish[0].p;
128 if (piece == V.KING) {
129 this.kingPos[color][0] = m.appear[0].x;
130 this.kingPos[color][1] = m.appear[0].y;
131 }
132 V.PlayOnBoard(this.board, m);
133 let res = !this.underCheck(color);
134 V.UndoOnBoard(this.board, m);
135 if (piece == V.KING) this.kingPos[color] = [m.start.x, m.start.y];
136 return res;
137 });
138 }
139
140 getPossibleMovesFrom([x, y]) {
141 return this.filterValid(this.getPotentialMovesFrom([x, y]));
142 }
143
144 getAllValidMoves() {
145 const moves = this.filterValid(super.getAllPotentialMoves());
146 if (this.movesCount % 4 <= 1) return moves;
147 const emptyIdx = moves.findIndex(m => m.vanish.length == 0);
148 if (emptyIdx >= 0)
149 // Keep only one pass move (for computer play)
150 return moves.filter((m, i) => m.vanish.length > 0 || i == emptyIdx);
151 return moves;
152 }
153
154 play(move, noFlag) {
155 if (this.movesCount % 4 == 0) this.initfenStack.push(this.getBaseFen());
156 if (!noFlag) move.flags = JSON.stringify(this.aggregateFlags());
157 // Do not play on board (would reveal the move...)
158 this.turn = V.GetOppCol(this.turn);
159 this.movesCount++;
160 if ([0, 3].includes(this.movesCount % 4)) this.postPlay(move);
161 else super.postPlay(move); //resolve synchrone move
162 }
163
164 postPlay(move) {
165 if (this.turn == 'b') {
166 // NOTE: whiteMove is used read-only, so no need to copy
167 this.whiteMove = move;
168 return;
169 }
170
171 // A full "deterministic" turn just ended: no need to resolve
172 const smove = {
173 appear: this.whiteMove.appear.concat(move.appear),
174 vanish: this.whiteMove.vanish.concat(move.vanish)
175 };
176 V.PlayOnBoard(this.board, smove);
177 move.whiteMove = this.whiteMove; //for undo
178 this.whiteMove = null;
179
180 // Update king position + flags
181 let kingAppear = { 'w': false, 'b': false };
182 for (let i=0; i < smove.appear.length; i++) {
183 if (smove.appear[i].p == V.KING) {
184 const c = smove.appear[i].c;
185 kingAppear[c] = true;
186 this.kingPos[c][0] = smove.appear[i].x;
187 this.kingPos[c][1] = smove.appear[i].y;
188 }
189 }
190 for (let i = 0; i < smove.vanish.length; i++) {
191 if (smove.vanish[i].p == V.KING) {
192 const c = smove.vanish[i].c;
193 if (!kingAppear[c]) {
194 this.kingPos[c][0] = -1;
195 this.kingPos[c][1] = -1;
196 }
197 break;
198 }
199 }
200 super.updateCastleFlags(smove);
201 move.smove = smove;
202 }
203
204 undo(move, noFlag) {
205 if (!noFlag) this.disaggregateFlags(JSON.parse(move.flags));
206 if (this.turn == 'w')
207 // Back to the middle of the move
208 V.UndoOnBoard(this.board, move.smove);
209 this.turn = V.GetOppCol(this.turn);
210 this.movesCount--;
211 if (this.movesCount % 4 == 0) this.initfenStack.pop();
212 this.postUndo(move);
213 }
214
215 postUndo(move) {
216 if (this.turn == 'w') {
217 // Reset king positions: scan board (TODO: could be more efficient)
218 if (move.vanish.length > 0) this.scanKings();
219 // Also reset whiteMove
220 this.whiteMove = null;
221 }
222 else this.whiteMove = move.whiteMove;
223 }
224
225 getCurrentScore() {
226 if (this.movesCount % 4 != 0)
227 // Turn (2 x [white + black]) not over yet
228 return "*";
229 // Was a king captured?
230 if (this.kingPos['w'][0] < 0) return "0-1";
231 if (this.kingPos['b'][0] < 0) return "1-0";
232 const whiteCanMove = this.atLeastOneMove('w');
233 const blackCanMove = this.atLeastOneMove('b');
234 if (whiteCanMove && blackCanMove) return "*";
235 // Game over
236 const whiteInCheck = this.underCheck('w');
237 const blackInCheck = this.underCheck('b');
238 if (
239 (whiteCanMove && !this.underCheck('b')) ||
240 (blackCanMove && !this.underCheck('w'))
241 ) {
242 return "1/2";
243 }
244 // Checkmate: could be mutual
245 if (!whiteCanMove && !blackCanMove) return "1/2";
246 return (whiteCanMove ? "1-0" : "0-1");
247 }
248
249 getComputerMove() {
250 if (this.movesCount % 4 <= 1) return super.getComputerMove();
251 const moves = this.getAllValidMoves();
252 return moves[randInt(moves.length)];
253 }
254
255 getNotation(move) {
256 if (move.vanish.length == 0) return "pass";
257 return super.getNotation(move);
258 }
259
260 };