Finish Magnetic chess
[vchess.git] / public / javascripts / variants / Magnetic.js
1 class MagneticRules extends ChessRules
2 {
3 getEpSquare(move)
4 {
5 return undefined; //no en-passant
6 }
7
8 // Complete a move with magnetic actions
9 applyMagneticLaws([x,y], move)
10 {
11 const standardMove = JSON.parse(JSON.stringify(move));
12 this.play(standardMove);
13 const color = this.getColor(x,y);
14 const [sizeX,sizeY] = VariantRules.size;
15 for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
16 {
17 let [i,j] = [x+step[0],y+step[1]];
18 while (i>=0 && i<sizeX && j>=0 && j<sizeY)
19 {
20 if (this.board[i][j] != VariantRules.EMPTY)
21 {
22 // Found something. Same color or not?
23 if (this.getColor(i,j) != color)
24 {
25 // Attraction
26 if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2)
27 && this.getPiece(i,j) != VariantRules.KING)
28 {
29 move.vanish.push(
30 new PiPo({
31 p:this.getPiece(i,j),
32 c:this.getColor(i,j),
33 x:i,
34 y:j
35 })
36 );
37 move.appear.push(
38 new PiPo({
39 p:this.getPiece(i,j),
40 c:this.getColor(i,j),
41 x:x+step[0],
42 y:y+step[1]
43 })
44 );
45 }
46 }
47 else
48 {
49 // Repulsion
50 if (this.getPiece(i,j) != VariantRules.KING)
51 {
52 // Push it until we meet an obstacle or edge of the board
53 let [ii,jj] = [i+step[0],j+step[1]];
54 while (ii>=0 && ii<sizeX && jj>=0 && jj<sizeY)
55 {
56 if (this.board[ii][jj] != VariantRules.EMPTY)
57 break;
58 ii += step[0];
59 jj += step[1];
60 }
61 ii -= step[0];
62 jj -= step[1];
63 if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
64 {
65 move.vanish.push(
66 new PiPo({
67 p:this.getPiece(i,j),
68 c:this.getColor(i,j),
69 x:i,
70 y:j
71 })
72 );
73 move.appear.push(
74 new PiPo({
75 p:this.getPiece(i,j),
76 c:this.getColor(i,j),
77 x:ii,
78 y:jj
79 })
80 );
81 }
82 }
83 }
84 break;
85 }
86 i += step[0];
87 j += step[1];
88 }
89 }
90 this.undo(standardMove);
91 }
92
93 getBasicMove([sx,sy], [ex,ey], tr)
94 {
95 var mv = new Move({
96 appear: [
97 new PiPo({
98 x: ex,
99 y: ey,
100 c: !!tr ? tr.c : this.getColor(sx,sy),
101 p: !!tr ? tr.p : this.getPiece(sx,sy)
102 })
103 ],
104 vanish: [
105 new PiPo({
106 x: sx,
107 y: sy,
108 c: this.getColor(sx,sy),
109 p: this.getPiece(sx,sy)
110 })
111 ]
112 });
113
114 if (this.board[ex][ey] != VariantRules.EMPTY)
115 {
116 mv.vanish.push(
117 new PiPo({
118 x: ex,
119 y: ey,
120 c: this.getColor(ex,ey),
121 p: this.getPiece(ex,ey)
122 })
123 );
124 }
125 this.applyMagneticLaws([ex,ey], mv);
126 return mv;
127 }
128
129 getPotentialPawnMoves([x,y])
130 {
131 const color = this.getColor(x,y);
132 var moves = [];
133 var V = VariantRules;
134 const [sizeX,sizeY] = VariantRules.size;
135 let shift = (color == "w" ? -1 : 1);
136 let startRank = (color == "w" ? sizeY-2 : 1);
137 let firstRank = (color == 'w' ? sizeY-1 : 0);
138 let lastRank = (color == "w" ? 0 : sizeY-1);
139
140 if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
141 {
142 // Normal moves
143 if (this.board[x+shift][y] == V.EMPTY)
144 {
145 moves.push(this.getBasicMove([x,y], [x+shift,y]));
146 if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
147 {
148 // Two squares jump
149 moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
150 }
151 }
152 // Captures
153 if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
154 moves.push(this.getBasicMove([x,y], [x+shift,y-1]));
155 if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
156 moves.push(this.getBasicMove([x,y], [x+shift,y+1]));
157 }
158
159 if (x+shift == lastRank)
160 {
161 // Promotion
162 let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
163 promotionPieces.forEach(p => {
164 // Normal move
165 if (this.board[x+shift][y] == V.EMPTY)
166 moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
167 // Captures
168 if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
169 moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:p}));
170 if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
171 moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:p}));
172 });
173 }
174
175 // No en passant
176
177 return moves;
178 }
179
180 getCastleMoves([x,y])
181 {
182 const c = this.getColor(x,y);
183 if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
184 return []; //x isn't first rank, or king has moved (shortcut)
185
186 const V = VariantRules;
187
188 // Castling ?
189 const oppCol = this.getOppCol(c);
190 let moves = [];
191 let i = 0;
192 const finalSquares = [ [2,3], [6,5] ]; //king, then rook
193 castlingCheck:
194 for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
195 {
196 if (!this.flags[c][castleSide])
197 continue;
198 // If this code is reached, rooks and king are on initial position
199
200 // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
201 let step = finalSquares[castleSide][0] < y ? -1 : 1;
202 for (i=y; i!=finalSquares[castleSide][0]; i+=step)
203 {
204 if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY &&
205 // NOTE: next check is enough, because of chessboard constraints
206 (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
207 {
208 continue castlingCheck;
209 }
210 }
211
212 // Nothing on the path to the rook?
213 step = castleSide == 0 ? -1 : 1;
214 for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step)
215 {
216 if (this.board[x][i] != V.EMPTY)
217 continue castlingCheck;
218 }
219 const rookPos = this.INIT_COL_ROOK[c][castleSide];
220
221 // Nothing on final squares, except maybe king and castling rook?
222 for (i=0; i<2; i++)
223 {
224 if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
225 this.getPiece(x,finalSquares[castleSide][i]) != V.KING &&
226 finalSquares[castleSide][i] != rookPos)
227 {
228 continue castlingCheck;
229 }
230 }
231
232 // If this code is reached, castle is valid
233 let cmove = new Move({
234 appear: [
235 new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}),
236 new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})],
237 vanish: [
238 new PiPo({x:x,y:y,p:V.KING,c:c}),
239 new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})],
240 end: Math.abs(y - rookPos) <= 2
241 ? {x:x, y:rookPos}
242 : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)}
243 });
244 this.applyMagneticLaws([x,finalSquares[castleSide][1]], cmove);
245 moves.push(cmove);
246 }
247
248 return moves;
249 }
250
251 // TODO: verify this assertion
252 // atLeastOneMove()
253 // {
254 // return true; //always at least one possible move
255 // }
256
257 underCheck(move)
258 {
259 return false; //there is no check
260 }
261
262 getCheckSquares(move)
263 {
264 const c = this.getOppCol(this.turn); //opponent
265 const saveKingPos = this.kingPos[c]; //king might be taken
266 this.play(move);
267 // The only way to be "under check" is to have lost the king (thus game over)
268 let res = this.kingPos[c][0] < 0
269 ? [ JSON.parse(JSON.stringify(saveKingPos)) ]
270 : [ ];
271 this.undo(move);
272 return res;
273 }
274
275 updateVariables(move)
276 {
277 super.updateVariables(move);
278 const c = this.getColor(move.start.x,move.start.y);
279 if (c != this.getColor(move.end.x,move.end.y)
280 && this.board[move.end.x][move.end.y] != VariantRules.EMPTY
281 && this.getPiece(move.end.x,move.end.y) == VariantRules.KING)
282 {
283 // We took opponent king !
284 const oppCol = this.getOppCol(c);
285 this.kingPos[oppCol] = [-1,-1];
286 this.flags[oppCol] = [false,false];
287 }
288 }
289
290 unupdateVariables(move)
291 {
292 super.unupdateVariables(move);
293 const c = this.getColor(move.start.x,move.start.y);
294 const oppCol = this.getOppCol(c);
295 if (this.kingPos[oppCol][0] < 0)
296 {
297 // Last move took opponent's king
298 for (let psq of move.vanish)
299 {
300 if (psq.p == 'k')
301 {
302 this.kingPos[oppCol] = [psq.x, psq.y];
303 break;
304 }
305 }
306 }
307 }
308
309 checkGameOver()
310 {
311 if (this.checkRepetition())
312 return "1/2";
313
314 const color = this.turn;
315 // TODO: do we need "atLeastOneMove()"?
316 if (this.atLeastOneMove() && this.kingPos[color][0] >= 0)
317 return "*";
318
319 return this.checkGameEnd();
320 }
321
322 checkGameEnd()
323 {
324 // No valid move: our king disappeared
325 return this.turn == "w" ? "0-1" : "1-0";
326 }
327 }