Almost finished problems logic. TODO: showProblem() part
[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(fen)
51 {
52 super.setFlags(fen); //castleFlags
53 this.pawnFlags =
54 {
55 "w": new Array(8), //pawns can move 2 squares?
56 "b": new Array(8)
57 };
58 const flags = fen.split(" ")[1].substr(4); //skip first 4 digits, for castle
59 for (let c of ['w','b'])
60 {
61 for (let i=0; i<8; i++)
62 this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
63 }
64 }
65
66 // Aggregates flags into one object
67 get flags() {
68 return [this.castleFlags, this.pawnFlags];
69 }
70
71 // Reverse operation
72 parseFlags(flags)
73 {
74 this.castleFlags = flags[0];
75 this.pawnFlags = flags[1];
76 }
77
78 canTake([x1,y1], [x2,y2])
79 {
80 const color1 = this.getColor(x1,y1);
81 const color2 = this.getColor(x2,y2);
82 // Checkered aren't captured
83 return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
84 }
85
86 // Post-processing: apply "checkerization" of standard moves
87 getPotentialMovesFrom([x,y])
88 {
89 let standardMoves = super.getPotentialMovesFrom([x,y]);
90 const lastRank = this.turn == "w" ? 0 : 7;
91 if (this.getPiece(x,y) == V.KING)
92 return standardMoves; //king has to be treated differently (for castles)
93 let moves = [];
94 standardMoves.forEach(m => {
95 if (m.vanish[0].p == V.PAWN)
96 {
97 if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
98 return; //skip forbidden 2-squares jumps
99 if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
100 && this.getColor(m.start.x,m.start.y) == 'c')
101 {
102 return; //checkered pawns cannot take en-passant
103 }
104 }
105 if (m.vanish.length == 1)
106 moves.push(m); //no capture
107 else
108 {
109 // A capture occured (m.vanish.length == 2)
110 m.appear[0].c = "c";
111 moves.push(m);
112 if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
113 && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
114 {
115 // Add transformation into captured piece
116 let m2 = JSON.parse(JSON.stringify(m));
117 m2.appear[0].p = m.vanish[1].p;
118 moves.push(m2);
119 }
120 }
121 });
122 return moves;
123 }
124
125 canIplay(side, [x,y])
126 {
127 return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
128 }
129
130 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
131 oppositeMoves(m1, m2)
132 {
133 return m1.appear.length == 1 && m2.appear.length == 1
134 && m1.vanish.length == 1 && m2.vanish.length == 1
135 && m1.start.x == m2.end.x && m1.end.x == m2.start.x
136 && m1.start.y == m2.end.y && m1.end.y == m2.start.y
137 && m1.appear[0].c == m2.vanish[0].c && m1.appear[0].p == m2.vanish[0].p
138 && m1.vanish[0].c == m2.appear[0].c && m1.vanish[0].p == m2.appear[0].p;
139 }
140
141 filterValid(moves)
142 {
143 if (moves.length == 0)
144 return [];
145 const color = this.turn;
146 return moves.filter(m => {
147 const L = this.moves.length;
148 if (L > 0 && this.oppositeMoves(this.moves[L-1], m))
149 return false;
150 return !this.underCheck(m);
151 });
152 }
153
154 isAttackedByPawn([x,y], colors)
155 {
156 for (let c of colors)
157 {
158 const color = (c=="c" ? this.turn : c);
159 let pawnShift = (color=="w" ? 1 : -1);
160 if (x+pawnShift>=0 && x+pawnShift<8)
161 {
162 for (let i of [-1,1])
163 {
164 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
165 && this.getColor(x+pawnShift,y+i)==c)
166 {
167 return true;
168 }
169 }
170 }
171 }
172 return false;
173 }
174
175 underCheck(move)
176 {
177 const color = this.turn;
178 this.play(move);
179 let res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']);
180 this.undo(move);
181 return res;
182 }
183
184 getCheckSquares(move)
185 {
186 this.play(move);
187 const color = this.turn;
188 this.moves.push(move); //artifically change turn, for checkered pawns (TODO)
189 const kingAttacked = this.isAttacked(
190 this.kingPos[color], [this.getOppCol(color),'c']);
191 let res = kingAttacked
192 ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate!
193 : [ ];
194 this.moves.pop();
195 this.undo(move);
196 return res;
197 }
198
199 updateVariables(move)
200 {
201 super.updateVariables(move);
202 // Does this move turn off a 2-squares pawn flag?
203 const secondRank = [1,6];
204 if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
205 this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
206 }
207
208 checkGameEnd()
209 {
210 const color = this.turn;
211 // Artifically change turn, for checkered pawns
212 this.turn = this.getOppCol(this.turn);
213 const res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c'])
214 ? (color == "w" ? "0-1" : "1-0")
215 : "1/2";
216 this.turn = this.getOppCol(this.turn);
217 return res;
218 }
219
220 evalPosition()
221 {
222 let evaluation = 0;
223 //Just count material for now, considering checkered neutral (...)
224 for (let i=0; i<V.size.x; i++)
225 {
226 for (let j=0; j<V.size.y; j++)
227 {
228 if (this.board[i][j] != V.EMPTY)
229 {
230 const sqColor = this.getColor(i,j);
231 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
232 evaluation += sign * V.VALUES[this.getPiece(i,j)];
233 }
234 }
235 }
236 return evaluation;
237 }
238
239 static GenRandInitFen()
240 {
241 return ChessRules.GenRandInitFen() + "1111111111111111"; //add 16 pawns flags
242 }
243
244 getFlagsFen()
245 {
246 let fen = super.getFlagsFen();
247 // Add pawns flags
248 for (let c of ['w','b'])
249 {
250 for (let i=0; i<8; i++)
251 fen += this.pawnFlags[c][i] ? '1' : '0';
252 }
253 return fen;
254 }
255
256 getNotation(move)
257 {
258 if (move.appear.length == 2)
259 {
260 // Castle
261 if (move.end.y < move.start.y)
262 return "0-0-0";
263 else
264 return "0-0";
265 }
266
267 // Translate final square
268 let finalSquare =
269 String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
270
271 let piece = this.getPiece(move.start.x, move.start.y);
272 if (piece == V.PAWN)
273 {
274 // Pawn move
275 let notation = "";
276 if (move.vanish.length > 1)
277 {
278 // Capture
279 let startColumn = String.fromCharCode(97 + move.start.y);
280 notation = startColumn + "x" + finalSquare +
281 "=" + move.appear[0].p.toUpperCase();
282 }
283 else //no capture
284 {
285 notation = finalSquare;
286 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
287 notation += "=" + move.appear[0].p.toUpperCase();
288 }
289 return notation;
290 }
291
292 else
293 {
294 // Piece movement
295 return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
296 + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
297 }
298 }
299 }