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