Add TODO for pawns promotions in Magnetic + slightly improve rules description
[vchess.git] / public / javascripts / variants / Magnetic.js
CommitLineData
7f0711a8 1class MagneticRules extends ChessRules
1af36beb
BA
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 {
7f0711a8
BA
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);
1af36beb
BA
91 }
92
43c0f1d8 93 // TODO: when pawn is pushed to 8th rank, apply promotions (similar change as in Checkered)
1af36beb
BA
94 getBasicMove([sx,sy], [ex,ey], tr)
95 {
96 var mv = new Move({
97 appear: [
98 new PiPo({
99 x: ex,
100 y: ey,
101 c: !!tr ? tr.c : this.getColor(sx,sy),
102 p: !!tr ? tr.p : this.getPiece(sx,sy)
103 })
104 ],
105 vanish: [
106 new PiPo({
107 x: sx,
108 y: sy,
109 c: this.getColor(sx,sy),
110 p: this.getPiece(sx,sy)
111 })
112 ]
113 });
114
115 if (this.board[ex][ey] != VariantRules.EMPTY)
116 {
117 mv.vanish.push(
118 new PiPo({
119 x: ex,
120 y: ey,
121 c: this.getColor(ex,ey),
122 p: this.getPiece(ex,ey)
123 })
124 );
125 }
126 this.applyMagneticLaws([ex,ey], mv);
127 return mv;
128 }
129
7f0711a8
BA
130 getPotentialPawnMoves([x,y])
131 {
132 const color = this.getColor(x,y);
133 var moves = [];
134 var V = VariantRules;
135 const [sizeX,sizeY] = VariantRules.size;
136 let shift = (color == "w" ? -1 : 1);
137 let startRank = (color == "w" ? sizeY-2 : 1);
138 let firstRank = (color == 'w' ? sizeY-1 : 0);
139 let lastRank = (color == "w" ? 0 : sizeY-1);
140
141 if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
142 {
143 // Normal moves
144 if (this.board[x+shift][y] == V.EMPTY)
145 {
146 moves.push(this.getBasicMove([x,y], [x+shift,y]));
147 if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
148 {
149 // Two squares jump
150 moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
151 }
152 }
153 // Captures
154 if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
155 moves.push(this.getBasicMove([x,y], [x+shift,y-1]));
156 if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
157 moves.push(this.getBasicMove([x,y], [x+shift,y+1]));
158 }
159
160 if (x+shift == lastRank)
161 {
162 // Promotion
163 let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
164 promotionPieces.forEach(p => {
165 // Normal move
166 if (this.board[x+shift][y] == V.EMPTY)
167 moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
168 // Captures
169 if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
170 moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:p}));
171 if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
172 moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:p}));
173 });
174 }
175
176 // No en passant
177
178 return moves;
179 }
180
1af36beb
BA
181 getCastleMoves([x,y])
182 {
183 const c = this.getColor(x,y);
184 if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
185 return []; //x isn't first rank, or king has moved (shortcut)
186
187 const V = VariantRules;
188
189 // Castling ?
190 const oppCol = this.getOppCol(c);
191 let moves = [];
192 let i = 0;
193 const finalSquares = [ [2,3], [6,5] ]; //king, then rook
194 castlingCheck:
195 for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
196 {
197 if (!this.flags[c][castleSide])
198 continue;
199 // If this code is reached, rooks and king are on initial position
200
201 // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
202 let step = finalSquares[castleSide][0] < y ? -1 : 1;
203 for (i=y; i!=finalSquares[castleSide][0]; i+=step)
204 {
205 if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY &&
206 // NOTE: next check is enough, because of chessboard constraints
207 (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
208 {
209 continue castlingCheck;
210 }
211 }
212
213 // Nothing on the path to the rook?
214 step = castleSide == 0 ? -1 : 1;
215 for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step)
216 {
217 if (this.board[x][i] != V.EMPTY)
218 continue castlingCheck;
219 }
220 const rookPos = this.INIT_COL_ROOK[c][castleSide];
221
222 // Nothing on final squares, except maybe king and castling rook?
223 for (i=0; i<2; i++)
224 {
225 if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
226 this.getPiece(x,finalSquares[castleSide][i]) != V.KING &&
227 finalSquares[castleSide][i] != rookPos)
228 {
229 continue castlingCheck;
230 }
231 }
232
233 // If this code is reached, castle is valid
234 let cmove = new Move({
235 appear: [
236 new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}),
237 new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})],
238 vanish: [
239 new PiPo({x:x,y:y,p:V.KING,c:c}),
240 new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})],
241 end: Math.abs(y - rookPos) <= 2
242 ? {x:x, y:rookPos}
243 : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)}
244 });
245 this.applyMagneticLaws([x,finalSquares[castleSide][1]], cmove);
246 moves.push(cmove);
247 }
248
249 return moves;
250 }
251
252 // TODO: verify this assertion
253// atLeastOneMove()
254// {
255// return true; //always at least one possible move
256// }
257
258 underCheck(move)
259 {
260 return false; //there is no check
261 }
262
263 getCheckSquares(move)
264 {
265 const c = this.getOppCol(this.turn); //opponent
266 const saveKingPos = this.kingPos[c]; //king might be taken
267 this.play(move);
268 // The only way to be "under check" is to have lost the king (thus game over)
7f0711a8 269 let res = this.kingPos[c][0] < 0
1af36beb
BA
270 ? [ JSON.parse(JSON.stringify(saveKingPos)) ]
271 : [ ];
272 this.undo(move);
273 return res;
274 }
275
276 updateVariables(move)
277 {
278 super.updateVariables(move);
279 const c = this.getColor(move.start.x,move.start.y);
280 if (c != this.getColor(move.end.x,move.end.y)
281 && this.board[move.end.x][move.end.y] != VariantRules.EMPTY
282 && this.getPiece(move.end.x,move.end.y) == VariantRules.KING)
283 {
284 // We took opponent king !
285 const oppCol = this.getOppCol(c);
286 this.kingPos[oppCol] = [-1,-1];
287 this.flags[oppCol] = [false,false];
288 }
289 }
290
291 unupdateVariables(move)
292 {
293 super.unupdateVariables(move);
294 const c = this.getColor(move.start.x,move.start.y);
295 const oppCol = this.getOppCol(c);
296 if (this.kingPos[oppCol][0] < 0)
297 {
298 // Last move took opponent's king
299 for (let psq of move.vanish)
300 {
301 if (psq.p == 'k')
302 {
303 this.kingPos[oppCol] = [psq.x, psq.y];
304 break;
305 }
306 }
307 }
308 }
309
310 checkGameOver()
311 {
312 if (this.checkRepetition())
313 return "1/2";
314
315 const color = this.turn;
316 // TODO: do we need "atLeastOneMove()"?
317 if (this.atLeastOneMove() && this.kingPos[color][0] >= 0)
318 return "*";
319
320 return this.checkGameEnd();
321 }
322
323 checkGameEnd()
324 {
325 // No valid move: our king disappeared
326 return this.turn == "w" ? "0-1" : "1-0";
327 }
328}