Check variants. All OK except Dark (bug), Checkered (missing internal moves stack...
[vchess.git] / client / src / variants / Checkered.js
CommitLineData
0c3fe8a6
BA
1// TODO: to detect oppositeMoves, we need last move --> encoded in FEN
2// + local moves stack (for AlphaBeta) + lastMove (in FEN)
3
4import { ChessRules } from "@/base_rules";
5
6export const VariantRules = class CheckeredRules extends ChessRules
1d184b4c 7{
1d184b4c
BA
8 static getPpath(b)
9 {
10 return b[0]=='c' ? "Checkered/"+b : b;
11 }
2d7194bd 12
1d184b4c
BA
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 }
2d7194bd 26
1d184b4c
BA
27 static fen2board(f)
28 {
7931e479 29 // Tolerate upper-case versions of checkered pieces (why not?)
1d184b4c
BA
30 const checkered_pieces = {
31 's': 'p',
7931e479 32 'S': 'p',
1d184b4c 33 't': 'q',
7931e479 34 'T': 'q',
1d184b4c 35 'u': 'r',
7931e479 36 'U': 'r',
1d184b4c 37 'c': 'b',
7931e479 38 'C': 'b',
1d184b4c 39 'o': 'n',
7931e479 40 'O': 'n',
1d184b4c
BA
41 };
42 if (Object.keys(checkered_pieces).includes(f))
43 return 'c'+checkered_pieces[f];
44 return ChessRules.fen2board(f);
45 }
46
2d7194bd
BA
47 static get PIECES()
48 {
7931e479
BA
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
c794dbb8 58 setFlags(fenflags)
1d184b4c 59 {
c794dbb8 60 super.setFlags(fenflags); //castleFlags
2526c041
BA
61 this.pawnFlags =
62 {
8d61fc4a
BA
63 "w": [...Array(8).fill(true)], //pawns can move 2 squares?
64 "b": [...Array(8).fill(true)],
2526c041 65 };
c794dbb8
BA
66 if (!fenflags)
67 return;
68 const flags = fenflags.substr(4); //skip first 4 digits, for castle
1d184b4c
BA
69 for (let c of ['w','b'])
70 {
71 for (let i=0; i<8; i++)
2526c041 72 this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
1d184b4c 73 }
1d184b4c
BA
74 }
75
2d7194bd
BA
76 aggregateFlags()
77 {
2526c041 78 return [this.castleFlags, this.pawnFlags];
1d184b4c
BA
79 }
80
2d7194bd 81 disaggregateFlags(flags)
1d184b4c 82 {
2526c041
BA
83 this.castleFlags = flags[0];
84 this.pawnFlags = flags[1];
1d184b4c
BA
85 }
86
2526c041 87 canTake([x1,y1], [x2,y2])
1d184b4c 88 {
2526c041
BA
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);
1d184b4c
BA
93 }
94
2526c041
BA
95 // Post-processing: apply "checkerization" of standard moves
96 getPotentialMovesFrom([x,y])
1d184b4c 97 {
2526c041 98 let standardMoves = super.getPotentialMovesFrom([x,y]);
aea1443e 99 const lastRank = this.turn == "w" ? 0 : 7;
0b7d99ec 100 if (this.getPiece(x,y) == V.KING)
2526c041 101 return standardMoves; //king has to be treated differently (for castles)
1d184b4c 102 let moves = [];
2526c041 103 standardMoves.forEach(m => {
0b7d99ec 104 if (m.vanish[0].p == V.PAWN)
1d184b4c 105 {
2316f8b8
BA
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
0b7d99ec
BA
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')
2316f8b8
BA
110 {
111 return; //checkered pawns cannot take en-passant
112 }
1d184b4c 113 }
2526c041
BA
114 if (m.vanish.length == 1)
115 moves.push(m); //no capture
116 else
1d184b4c 117 {
2526c041
BA
118 // A capture occured (m.vanish.length == 2)
119 m.appear[0].c = "c";
aea1443e 120 moves.push(m);
68f5ccc8 121 if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
0b7d99ec 122 && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
1d184b4c 123 {
2526c041
BA
124 // Add transformation into captured piece
125 let m2 = JSON.parse(JSON.stringify(m));
aea1443e 126 m2.appear[0].p = m.vanish[1].p;
2526c041 127 moves.push(m2);
1d184b4c
BA
128 }
129 }
2526c041 130 });
1d184b4c
BA
131 return moves;
132 }
133
46302e64 134 canIplay(side, [x,y])
1d184b4c 135 {
7931e479 136 return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
1d184b4c
BA
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
1d184b4c
BA
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 [];
46302e64 154 const color = this.turn;
1d184b4c
BA
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;
f6dbe8e3
BA
159 this.play(m);
160 const res = !this.underCheck(color);
161 this.undo(m);
162 return res;
1d184b4c
BA
163 });
164 }
165
46302e64
BA
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 {
0b7d99ec 176 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
46302e64
BA
177 && this.getColor(x+pawnShift,y+i)==c)
178 {
179 return true;
180 }
181 }
182 }
183 }
184 return false;
185 }
1d184b4c 186
f6dbe8e3 187 underCheck(color)
1d184b4c 188 {
26b8e4f7 189 return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
1d184b4c
BA
190 }
191
f6dbe8e3 192 getCheckSquares(color)
bd6ff57c 193 {
2d7194bd 194 // Artifically change turn, for checkered pawns
26b8e4f7 195 this.turn = V.GetOppCol(color);
92342261 196 const kingAttacked = this.isAttacked(
26b8e4f7 197 this.kingPos[color], [V.GetOppCol(color),'c']);
bd6ff57c 198 let res = kingAttacked
2d7194bd
BA
199 ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
200 : [];
201 this.turn = color;
bd6ff57c
BA
202 return res;
203 }
204
1d184b4c
BA
205 updateVariables(move)
206 {
0b7d99ec
BA
207 super.updateVariables(move);
208 // Does this move turn off a 2-squares pawn flag?
1d184b4c 209 const secondRank = [1,6];
0b7d99ec 210 if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
2526c041 211 this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
1d184b4c
BA
212 }
213
0c3fe8a6 214 getCurrentScore()
1d184b4c 215 {
0c3fe8a6
BA
216 if (this.atLeastOneMove()) // game not over
217 return "*";
218
219 const color = this.turn;
7931e479 220 // Artifically change turn, for checkered pawns
26b8e4f7
BA
221 this.turn = V.GetOppCol(this.turn);
222 const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
e2b216fe
BA
223 ? (color == "w" ? "0-1" : "1-0")
224 : "1/2";
26b8e4f7 225 this.turn = V.GetOppCol(this.turn);
e2b216fe 226 return res;
1d184b4c
BA
227 }
228
229 evalPosition()
230 {
1d184b4c
BA
231 let evaluation = 0;
232 //Just count material for now, considering checkered neutral (...)
0b7d99ec 233 for (let i=0; i<V.size.x; i++)
1d184b4c 234 {
0b7d99ec 235 for (let j=0; j<V.size.y; j++)
1d184b4c 236 {
0b7d99ec 237 if (this.board[i][j] != V.EMPTY)
1d184b4c
BA
238 {
239 const sqColor = this.getColor(i,j);
240 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
0b7d99ec 241 evaluation += sign * V.VALUES[this.getPiece(i,j)];
1d184b4c
BA
242 }
243 }
244 }
245 return evaluation;
246 }
247
248 static GenRandInitFen()
249 {
c794dbb8
BA
250 const randFen = ChessRules.GenRandInitFen();
251 // Add 16 pawns flags:
0c3fe8a6 252 return randFen.replace(" w 0 1111", " w 0 11111111111111111111");
1d184b4c
BA
253 }
254
255 getFlagsFen()
256 {
2526c041 257 let fen = super.getFlagsFen();
1d184b4c
BA
258 // Add pawns flags
259 for (let c of ['w','b'])
260 {
261 for (let i=0; i<8; i++)
2526c041 262 fen += this.pawnFlags[c][i] ? '1' : '0';
1d184b4c
BA
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
2d7194bd 279 const finalSquare = V.CoordsToSquare(move.end);
1d184b4c 280
2d7194bd 281 const piece = this.getPiece(move.start.x, move.start.y);
0b7d99ec 282 if (piece == V.PAWN)
1d184b4c
BA
283 {
284 // Pawn move
285 let notation = "";
286 if (move.vanish.length > 1)
287 {
288 // Capture
26c1e3bd 289 const startColumn = V.CoordToColumn(move.start.y);
92342261
BA
290 notation = startColumn + "x" + finalSquare +
291 "=" + move.appear[0].p.toUpperCase();
1d184b4c
BA
292 }
293 else //no capture
098e8468 294 {
1d184b4c 295 notation = finalSquare;
098e8468
BA
296 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
297 notation += "=" + move.appear[0].p.toUpperCase();
298 }
1d184b4c
BA
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}