Check variants. All OK except Dark (bug), Checkered (missing internal moves stack...
[vchess.git] / client / src / variants / Checkered.js
1 // TODO: to detect oppositeMoves, we need last move --> encoded in FEN
2 // + local moves stack (for AlphaBeta) + lastMove (in FEN)
3
4 import { ChessRules } from "@/base_rules";
5
6 export const VariantRules = class CheckeredRules extends ChessRules
7 {
8 static getPpath(b)
9 {
10 return b[0]=='c' ? "Checkered/"+b : b;
11 }
12
13 static board2fen(b)
14 {
15 const checkered_codes = {
16 'p': 's',
17 'q': 't',
18 'r': 'u',
19 'b': 'c',
20 'n': 'o',
21 };
22 if (b[0]=="c")
23 return checkered_codes[b[1]];
24 return ChessRules.board2fen(b);
25 }
26
27 static fen2board(f)
28 {
29 // Tolerate upper-case versions of checkered pieces (why not?)
30 const checkered_pieces = {
31 's': 'p',
32 'S': 'p',
33 't': 'q',
34 'T': 'q',
35 'u': 'r',
36 'U': 'r',
37 'c': 'b',
38 'C': 'b',
39 'o': 'n',
40 'O': 'n',
41 };
42 if (Object.keys(checkered_pieces).includes(f))
43 return 'c'+checkered_pieces[f];
44 return ChessRules.fen2board(f);
45 }
46
47 static get PIECES()
48 {
49 return ChessRules.PIECES.concat(['s','t','u','c','o']);
50 }
51
52 static IsGoodFlags(flags)
53 {
54 // 4 for castle + 16 for pawns
55 return !!flags.match(/^[01]{20,20}$/);
56 }
57
58 setFlags(fenflags)
59 {
60 super.setFlags(fenflags); //castleFlags
61 this.pawnFlags =
62 {
63 "w": [...Array(8).fill(true)], //pawns can move 2 squares?
64 "b": [...Array(8).fill(true)],
65 };
66 if (!fenflags)
67 return;
68 const flags = fenflags.substr(4); //skip first 4 digits, for castle
69 for (let c of ['w','b'])
70 {
71 for (let i=0; i<8; i++)
72 this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
73 }
74 }
75
76 aggregateFlags()
77 {
78 return [this.castleFlags, this.pawnFlags];
79 }
80
81 disaggregateFlags(flags)
82 {
83 this.castleFlags = flags[0];
84 this.pawnFlags = flags[1];
85 }
86
87 canTake([x1,y1], [x2,y2])
88 {
89 const color1 = this.getColor(x1,y1);
90 const color2 = this.getColor(x2,y2);
91 // Checkered aren't captured
92 return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
93 }
94
95 // Post-processing: apply "checkerization" of standard moves
96 getPotentialMovesFrom([x,y])
97 {
98 let standardMoves = super.getPotentialMovesFrom([x,y]);
99 const lastRank = this.turn == "w" ? 0 : 7;
100 if (this.getPiece(x,y) == V.KING)
101 return standardMoves; //king has to be treated differently (for castles)
102 let moves = [];
103 standardMoves.forEach(m => {
104 if (m.vanish[0].p == V.PAWN)
105 {
106 if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
107 return; //skip forbidden 2-squares jumps
108 if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
109 && this.getColor(m.start.x,m.start.y) == 'c')
110 {
111 return; //checkered pawns cannot take en-passant
112 }
113 }
114 if (m.vanish.length == 1)
115 moves.push(m); //no capture
116 else
117 {
118 // A capture occured (m.vanish.length == 2)
119 m.appear[0].c = "c";
120 moves.push(m);
121 if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
122 && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
123 {
124 // Add transformation into captured piece
125 let m2 = JSON.parse(JSON.stringify(m));
126 m2.appear[0].p = m.vanish[1].p;
127 moves.push(m2);
128 }
129 }
130 });
131 return moves;
132 }
133
134 canIplay(side, [x,y])
135 {
136 return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
137 }
138
139 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
140 oppositeMoves(m1, m2)
141 {
142 return m1.appear.length == 1 && m2.appear.length == 1
143 && m1.vanish.length == 1 && m2.vanish.length == 1
144 && m1.start.x == m2.end.x && m1.end.x == m2.start.x
145 && m1.start.y == m2.end.y && m1.end.y == m2.start.y
146 && m1.appear[0].c == m2.vanish[0].c && m1.appear[0].p == m2.vanish[0].p
147 && m1.vanish[0].c == m2.appear[0].c && m1.vanish[0].p == m2.appear[0].p;
148 }
149
150 filterValid(moves)
151 {
152 if (moves.length == 0)
153 return [];
154 const color = this.turn;
155 return moves.filter(m => {
156 const L = this.moves.length;
157 if (L > 0 && this.oppositeMoves(this.moves[L-1], m))
158 return false;
159 this.play(m);
160 const res = !this.underCheck(color);
161 this.undo(m);
162 return res;
163 });
164 }
165
166 isAttackedByPawn([x,y], colors)
167 {
168 for (let c of colors)
169 {
170 const color = (c=="c" ? this.turn : c);
171 let pawnShift = (color=="w" ? 1 : -1);
172 if (x+pawnShift>=0 && x+pawnShift<8)
173 {
174 for (let i of [-1,1])
175 {
176 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
177 && this.getColor(x+pawnShift,y+i)==c)
178 {
179 return true;
180 }
181 }
182 }
183 }
184 return false;
185 }
186
187 underCheck(color)
188 {
189 return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
190 }
191
192 getCheckSquares(color)
193 {
194 // Artifically change turn, for checkered pawns
195 this.turn = V.GetOppCol(color);
196 const kingAttacked = this.isAttacked(
197 this.kingPos[color], [V.GetOppCol(color),'c']);
198 let res = kingAttacked
199 ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
200 : [];
201 this.turn = color;
202 return res;
203 }
204
205 updateVariables(move)
206 {
207 super.updateVariables(move);
208 // Does this move turn off a 2-squares pawn flag?
209 const secondRank = [1,6];
210 if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
211 this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
212 }
213
214 getCurrentScore()
215 {
216 if (this.atLeastOneMove()) // game not over
217 return "*";
218
219 const color = this.turn;
220 // Artifically change turn, for checkered pawns
221 this.turn = V.GetOppCol(this.turn);
222 const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
223 ? (color == "w" ? "0-1" : "1-0")
224 : "1/2";
225 this.turn = V.GetOppCol(this.turn);
226 return res;
227 }
228
229 evalPosition()
230 {
231 let evaluation = 0;
232 //Just count material for now, considering checkered neutral (...)
233 for (let i=0; i<V.size.x; i++)
234 {
235 for (let j=0; j<V.size.y; j++)
236 {
237 if (this.board[i][j] != V.EMPTY)
238 {
239 const sqColor = this.getColor(i,j);
240 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
241 evaluation += sign * V.VALUES[this.getPiece(i,j)];
242 }
243 }
244 }
245 return evaluation;
246 }
247
248 static GenRandInitFen()
249 {
250 const randFen = ChessRules.GenRandInitFen();
251 // Add 16 pawns flags:
252 return randFen.replace(" w 0 1111", " w 0 11111111111111111111");
253 }
254
255 getFlagsFen()
256 {
257 let fen = super.getFlagsFen();
258 // Add pawns flags
259 for (let c of ['w','b'])
260 {
261 for (let i=0; i<8; i++)
262 fen += this.pawnFlags[c][i] ? '1' : '0';
263 }
264 return fen;
265 }
266
267 getNotation(move)
268 {
269 if (move.appear.length == 2)
270 {
271 // Castle
272 if (move.end.y < move.start.y)
273 return "0-0-0";
274 else
275 return "0-0";
276 }
277
278 // Translate final square
279 const finalSquare = V.CoordsToSquare(move.end);
280
281 const piece = this.getPiece(move.start.x, move.start.y);
282 if (piece == V.PAWN)
283 {
284 // Pawn move
285 let notation = "";
286 if (move.vanish.length > 1)
287 {
288 // Capture
289 const startColumn = V.CoordToColumn(move.start.y);
290 notation = startColumn + "x" + finalSquare +
291 "=" + move.appear[0].p.toUpperCase();
292 }
293 else //no capture
294 {
295 notation = finalSquare;
296 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
297 notation += "=" + move.appear[0].p.toUpperCase();
298 }
299 return notation;
300 }
301
302 else
303 {
304 // Piece movement
305 return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
306 + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
307 }
308 }
309 }