Save current state (unfinished, untested)
[vchess.git] / public / javascripts / variants / Checkered.js
CommitLineData
1d184b4c
BA
1class CheckeredRules extends ChessRules
2{
1d184b4c
BA
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 {
7931e479 22 // Tolerate upper-case versions of checkered pieces (why not?)
1d184b4c
BA
23 const checkered_pieces = {
24 's': 'p',
7931e479 25 'S': 'p',
1d184b4c 26 't': 'q',
7931e479 27 'T': 'q',
1d184b4c 28 'u': 'r',
7931e479 29 'U': 'r',
1d184b4c 30 'c': 'b',
7931e479 31 'C': 'b',
1d184b4c 32 'o': 'n',
7931e479 33 'O': 'n',
1d184b4c
BA
34 };
35 if (Object.keys(checkered_pieces).includes(f))
36 return 'c'+checkered_pieces[f];
37 return ChessRules.fen2board(f);
38 }
39
7931e479
BA
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
c794dbb8 50 setFlags(fenflags)
1d184b4c 51 {
c794dbb8 52 super.setFlags(fenflags); //castleFlags
2526c041
BA
53 this.pawnFlags =
54 {
c794dbb8
BA
55 "w": _.map(_.range(8), i => true), //pawns can move 2 squares?
56 "b": _.map(_.range(8), i => true)
2526c041 57 };
c794dbb8
BA
58 if (!fenflags)
59 return;
60 const flags = fenflags.substr(4); //skip first 4 digits, for castle
1d184b4c
BA
61 for (let c of ['w','b'])
62 {
63 for (let i=0; i<8; i++)
2526c041 64 this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
1d184b4c 65 }
1d184b4c
BA
66 }
67
2526c041
BA
68 // Aggregates flags into one object
69 get flags() {
70 return [this.castleFlags, this.pawnFlags];
1d184b4c
BA
71 }
72
2526c041
BA
73 // Reverse operation
74 parseFlags(flags)
1d184b4c 75 {
2526c041
BA
76 this.castleFlags = flags[0];
77 this.pawnFlags = flags[1];
1d184b4c
BA
78 }
79
2526c041 80 canTake([x1,y1], [x2,y2])
1d184b4c 81 {
2526c041
BA
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);
1d184b4c
BA
86 }
87
2526c041
BA
88 // Post-processing: apply "checkerization" of standard moves
89 getPotentialMovesFrom([x,y])
1d184b4c 90 {
2526c041 91 let standardMoves = super.getPotentialMovesFrom([x,y]);
aea1443e 92 const lastRank = this.turn == "w" ? 0 : 7;
0b7d99ec 93 if (this.getPiece(x,y) == V.KING)
2526c041 94 return standardMoves; //king has to be treated differently (for castles)
1d184b4c 95 let moves = [];
2526c041 96 standardMoves.forEach(m => {
0b7d99ec 97 if (m.vanish[0].p == V.PAWN)
1d184b4c 98 {
2316f8b8
BA
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
0b7d99ec
BA
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')
2316f8b8
BA
103 {
104 return; //checkered pawns cannot take en-passant
105 }
1d184b4c 106 }
2526c041
BA
107 if (m.vanish.length == 1)
108 moves.push(m); //no capture
109 else
1d184b4c 110 {
2526c041
BA
111 // A capture occured (m.vanish.length == 2)
112 m.appear[0].c = "c";
aea1443e 113 moves.push(m);
68f5ccc8 114 if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
0b7d99ec 115 && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
1d184b4c 116 {
2526c041
BA
117 // Add transformation into captured piece
118 let m2 = JSON.parse(JSON.stringify(m));
aea1443e 119 m2.appear[0].p = m.vanish[1].p;
2526c041 120 moves.push(m2);
1d184b4c
BA
121 }
122 }
2526c041 123 });
1d184b4c
BA
124 return moves;
125 }
126
46302e64 127 canIplay(side, [x,y])
1d184b4c 128 {
7931e479 129 return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
1d184b4c
BA
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
1d184b4c
BA
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 [];
46302e64 147 const color = this.turn;
1d184b4c
BA
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;
46302e64 152 return !this.underCheck(m);
1d184b4c
BA
153 });
154 }
155
46302e64
BA
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 {
0b7d99ec 166 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
46302e64
BA
167 && this.getColor(x+pawnShift,y+i)==c)
168 {
169 return true;
170 }
171 }
172 }
173 }
174 return false;
175 }
1d184b4c 176
46302e64 177 underCheck(move)
1d184b4c 178 {
46302e64 179 const color = this.turn;
1d184b4c 180 this.play(move);
46302e64 181 let res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']);
1d184b4c
BA
182 this.undo(move);
183 return res;
184 }
185
46302e64 186 getCheckSquares(move)
bd6ff57c
BA
187 {
188 this.play(move);
46302e64 189 const color = this.turn;
cd4cad04 190 this.moves.push(move); //artifically change turn, for checkered pawns (TODO)
92342261
BA
191 const kingAttacked = this.isAttacked(
192 this.kingPos[color], [this.getOppCol(color),'c']);
bd6ff57c 193 let res = kingAttacked
46302e64 194 ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate!
bd6ff57c 195 : [ ];
cd4cad04 196 this.moves.pop();
bd6ff57c
BA
197 this.undo(move);
198 return res;
199 }
200
1d184b4c
BA
201 updateVariables(move)
202 {
0b7d99ec
BA
203 super.updateVariables(move);
204 // Does this move turn off a 2-squares pawn flag?
1d184b4c 205 const secondRank = [1,6];
0b7d99ec 206 if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
2526c041 207 this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
1d184b4c
BA
208 }
209
46302e64 210 checkGameEnd()
1d184b4c 211 {
46302e64 212 const color = this.turn;
7931e479
BA
213 // Artifically change turn, for checkered pawns
214 this.turn = this.getOppCol(this.turn);
e2b216fe
BA
215 const res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c'])
216 ? (color == "w" ? "0-1" : "1-0")
217 : "1/2";
7931e479 218 this.turn = this.getOppCol(this.turn);
e2b216fe 219 return res;
1d184b4c
BA
220 }
221
222 evalPosition()
223 {
1d184b4c
BA
224 let evaluation = 0;
225 //Just count material for now, considering checkered neutral (...)
0b7d99ec 226 for (let i=0; i<V.size.x; i++)
1d184b4c 227 {
0b7d99ec 228 for (let j=0; j<V.size.y; j++)
1d184b4c 229 {
0b7d99ec 230 if (this.board[i][j] != V.EMPTY)
1d184b4c
BA
231 {
232 const sqColor = this.getColor(i,j);
233 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
0b7d99ec 234 evaluation += sign * V.VALUES[this.getPiece(i,j)];
1d184b4c
BA
235 }
236 }
237 }
238 return evaluation;
239 }
240
241 static GenRandInitFen()
242 {
c794dbb8
BA
243 const randFen = ChessRules.GenRandInitFen();
244 // Add 16 pawns flags:
245 return randFen.replace(" 1111 w", " 11111111111111111111 w");
1d184b4c
BA
246 }
247
248 getFlagsFen()
249 {
2526c041 250 let fen = super.getFlagsFen();
1d184b4c
BA
251 // Add pawns flags
252 for (let c of ['w','b'])
253 {
254 for (let i=0; i<8; i++)
2526c041 255 fen += this.pawnFlags[c][i] ? '1' : '0';
1d184b4c
BA
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 =
0b7d99ec 273 String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
1d184b4c
BA
274
275 let piece = this.getPiece(move.start.x, move.start.y);
0b7d99ec 276 if (piece == V.PAWN)
1d184b4c
BA
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);
92342261
BA
284 notation = startColumn + "x" + finalSquare +
285 "=" + move.appear[0].p.toUpperCase();
1d184b4c
BA
286 }
287 else //no capture
098e8468 288 {
1d184b4c 289 notation = finalSquare;
098e8468
BA
290 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
291 notation += "=" + move.appear[0].p.toUpperCase();
292 }
1d184b4c
BA
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}