Implemented a very basic DarkBot + a few fixes + advance on rules
[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
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 }
21
22 static fen2board(f)
23 {
24 // Tolerate upper-case versions of checkered pieces (why not?)
25 const checkered_pieces = {
26 's': 'p',
27 'S': 'p',
28 't': 'q',
29 'T': 'q',
30 'u': 'r',
31 'U': 'r',
32 'c': 'b',
33 'C': 'b',
34 'o': 'n',
35 'O': 'n',
36 };
37 if (Object.keys(checkered_pieces).includes(f))
38 return 'c'+checkered_pieces[f];
39 return ChessRules.fen2board(f);
40 }
41
42 static get PIECES()
43 {
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
53 setFlags(fenflags)
54 {
55 super.setFlags(fenflags); //castleFlags
56 this.pawnFlags =
57 {
58 "w": _.map(_.range(8), i => true), //pawns can move 2 squares?
59 "b": _.map(_.range(8), i => true)
60 };
61 if (!fenflags)
62 return;
63 const flags = fenflags.substr(4); //skip first 4 digits, for castle
64 for (let c of ['w','b'])
65 {
66 for (let i=0; i<8; i++)
67 this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
68 }
69 }
70
71 aggregateFlags()
72 {
73 return [this.castleFlags, this.pawnFlags];
74 }
75
76 disaggregateFlags(flags)
77 {
78 this.castleFlags = flags[0];
79 this.pawnFlags = flags[1];
80 }
81
82 canTake([x1,y1], [x2,y2])
83 {
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);
88 }
89
90 // Post-processing: apply "checkerization" of standard moves
91 getPotentialMovesFrom([x,y])
92 {
93 let standardMoves = super.getPotentialMovesFrom([x,y]);
94 const lastRank = this.turn == "w" ? 0 : 7;
95 if (this.getPiece(x,y) == V.KING)
96 return standardMoves; //king has to be treated differently (for castles)
97 let moves = [];
98 standardMoves.forEach(m => {
99 if (m.vanish[0].p == V.PAWN)
100 {
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
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')
105 {
106 return; //checkered pawns cannot take en-passant
107 }
108 }
109 if (m.vanish.length == 1)
110 moves.push(m); //no capture
111 else
112 {
113 // A capture occured (m.vanish.length == 2)
114 m.appear[0].c = "c";
115 moves.push(m);
116 if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
117 && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
118 {
119 // Add transformation into captured piece
120 let m2 = JSON.parse(JSON.stringify(m));
121 m2.appear[0].p = m.vanish[1].p;
122 moves.push(m2);
123 }
124 }
125 });
126 return moves;
127 }
128
129 canIplay(side, [x,y])
130 {
131 return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
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
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 [];
149 const color = this.turn;
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;
154 this.play(m);
155 const res = !this.underCheck(color);
156 this.undo(m);
157 return res;
158 });
159 }
160
161 isAttackedByPawn([x,y], colors)
162 {
163 for (let c of colors)
164 {
165 const color = (c=="c" ? this.turn : c);
166 let pawnShift = (color=="w" ? 1 : -1);
167 if (x+pawnShift>=0 && x+pawnShift<8)
168 {
169 for (let i of [-1,1])
170 {
171 if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
172 && this.getColor(x+pawnShift,y+i)==c)
173 {
174 return true;
175 }
176 }
177 }
178 }
179 return false;
180 }
181
182 underCheck(color)
183 {
184 return this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']);
185 }
186
187 getCheckSquares(color)
188 {
189 // Artifically change turn, for checkered pawns
190 this.turn = this.getOppCol(color);
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.turn = color;
197 return res;
198 }
199
200 updateVariables(move)
201 {
202 super.updateVariables(move);
203 // Does this move turn off a 2-squares pawn flag?
204 const secondRank = [1,6];
205 if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
206 this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
207 }
208
209 checkGameEnd()
210 {
211 const color = this.turn;
212 // Artifically change turn, for checkered pawns
213 this.turn = this.getOppCol(this.turn);
214 const res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c'])
215 ? (color == "w" ? "0-1" : "1-0")
216 : "1/2";
217 this.turn = this.getOppCol(this.turn);
218 return res;
219 }
220
221 evalPosition()
222 {
223 let evaluation = 0;
224 //Just count material for now, considering checkered neutral (...)
225 for (let i=0; i<V.size.x; i++)
226 {
227 for (let j=0; j<V.size.y; j++)
228 {
229 if (this.board[i][j] != V.EMPTY)
230 {
231 const sqColor = this.getColor(i,j);
232 const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
233 evaluation += sign * V.VALUES[this.getPiece(i,j)];
234 }
235 }
236 }
237 return evaluation;
238 }
239
240 static GenRandInitFen()
241 {
242 const randFen = ChessRules.GenRandInitFen();
243 // Add 16 pawns flags:
244 return randFen.replace(" w 1111", " w 11111111111111111111");
245 }
246
247 getFlagsFen()
248 {
249 let fen = super.getFlagsFen();
250 // Add pawns flags
251 for (let c of ['w','b'])
252 {
253 for (let i=0; i<8; i++)
254 fen += this.pawnFlags[c][i] ? '1' : '0';
255 }
256 return fen;
257 }
258
259 getNotation(move)
260 {
261 if (move.appear.length == 2)
262 {
263 // Castle
264 if (move.end.y < move.start.y)
265 return "0-0-0";
266 else
267 return "0-0";
268 }
269
270 // Translate final square
271 const finalSquare = V.CoordsToSquare(move.end);
272
273 const piece = this.getPiece(move.start.x, move.start.y);
274 if (piece == V.PAWN)
275 {
276 // Pawn move
277 let notation = "";
278 if (move.vanish.length > 1)
279 {
280 // Capture
281 const startColumn = V.CoordToColumn(move.start.y);
282 notation = startColumn + "x" + finalSquare +
283 "=" + move.appear[0].p.toUpperCase();
284 }
285 else //no capture
286 {
287 notation = finalSquare;
288 if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
289 notation += "=" + move.appear[0].p.toUpperCase();
290 }
291 return notation;
292 }
293
294 else
295 {
296 // Piece movement
297 return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
298 + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
299 }
300 }
301 }
302
303 const VariantRules = CheckeredRules;