Fix Synchrone2::filterValid()
[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);
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][1]) ? 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 play(move) {
145 if (this.movesCount % 4 == 0) this.initfenStack.push(this.getBaseFen());
146 move.flags = JSON.stringify(this.aggregateFlags());
147 // Do not play on board (would reveal the move...)
148 this.turn = V.GetOppCol(this.turn);
149 this.movesCount++;
150 if ([0, 3].includes(this.movesCount % 4)) this.postPlay(move);
151 else super.postPlay(move); //resolve synchrone move
152 }
153
154 postPlay(move) {
155 if (this.turn == 'b') {
156 // NOTE: whiteMove is used read-only, so no need to copy
157 this.whiteMove = move;
158 return;
159 }
160
161 // A full "deterministic" turn just ended: no need to resolve
162 const smove = {
163 appear: this.whiteMove.appear.concat(move.appear),
164 vanish: this.whiteMove.vanish.concat(move.vanish)
165 };
166 V.PlayOnBoard(this.board, smove);
167 move.whiteMove = this.whiteMove; //for undo
168 this.whiteMove = null;
169
170 // Update king position + flags
171 let kingAppear = { 'w': false, 'b': false };
172 for (let i=0; i < smove.appear.length; i++) {
173 if (smove.appear[i].p == V.KING) {
174 const c = smove.appear[i].c;
175 kingAppear[c] = true;
176 this.kingPos[c][0] = smove.appear[i].x;
177 this.kingPos[c][1] = smove.appear[i].y;
178 }
179 }
180 for (let i = 0; i < smove.vanish.length; i++) {
181 if (smove.vanish[i].p == V.KING) {
182 const c = smove.vanish[i].c;
183 if (!kingAppear[c]) {
184 this.kingPos[c][0] = -1;
185 this.kingPos[c][1] = -1;
186 }
187 break;
188 }
189 }
190 super.updateCastleFlags(smove);
191 move.smove = smove;
192 }
193
194 undo(move) {
195 this.disaggregateFlags(JSON.parse(move.flags));
196 if (this.turn == 'w')
197 // Back to the middle of the move
198 V.UndoOnBoard(this.board, move.smove);
199 this.turn = V.GetOppCol(this.turn);
200 this.movesCount--;
201 if (this.movesCount % 4 == 0) this.initfenStack.pop();
202 this.postUndo(move);
203 }
204
205 postUndo(move) {
206 if (this.turn == 'w') {
207 // Reset king positions: scan board (TODO: could be more efficient)
208 if (move.vanish.length > 0) this.scanKings();
209 // Also reset whiteMove
210 this.whiteMove = null;
211 }
212 else this.whiteMove = move.whiteMove;
213 }
214
215 getCurrentScore() {
216 if (this.movesCount % 4 != 0)
217 // Turn (2 x [white + black]) not over yet
218 return "*";
219 // Was a king captured?
220 if (this.kingPos['w'][0] < 0) return "0-1";
221 if (this.kingPos['b'][0] < 0) return "1-0";
222 const whiteCanMove = this.atLeastOneMove('w');
223 const blackCanMove = this.atLeastOneMove('b');
224 if (whiteCanMove && blackCanMove) return "*";
225 // Game over
226 const whiteInCheck = this.underCheck('w');
227 const blackInCheck = this.underCheck('b');
228 if (
229 (whiteCanMove && !this.underCheck('b')) ||
230 (blackCanMove && !this.underCheck('w'))
231 ) {
232 return "1/2";
233 }
234 // Checkmate: could be mutual
235 if (!whiteCanMove && !blackCanMove) return "1/2";
236 return (whiteCanMove ? "1-0" : "0-1");
237 }
238
239 getNotation(move) {
240 if (move.vanish.length == 0) return "pass";
241 return super.getNotation(move);
242 }
243
244 };