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