Commit | Line | Data |
---|---|---|
32cfcea4 BA |
1 | class UltimaRules extends ChessRules |
2 | { | |
2eef6db6 BA |
3 | static getPpath(b) |
4 | { | |
5 | if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1) | |
6 | return "Ultima/" + b; | |
7 | return b; //usual piece | |
8 | } | |
32cfcea4 | 9 | |
2eef6db6 BA |
10 | initVariables(fen) |
11 | { | |
12 | this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; | |
13 | const fenParts = fen.split(" "); | |
14 | const position = fenParts[0].split("/"); | |
15 | for (let i=0; i<position.length; i++) | |
16 | { | |
17 | let k = 0; | |
18 | for (let j=0; j<position[i].length; j++) | |
19 | { | |
20 | switch (position[i].charAt(j)) | |
21 | { | |
22 | case 'k': | |
23 | this.kingPos['b'] = [i,k]; | |
24 | break; | |
25 | case 'K': | |
26 | this.kingPos['w'] = [i,k]; | |
27 | break; | |
28 | default: | |
29 | let num = parseInt(position[i].charAt(j)); | |
30 | if (!isNaN(num)) | |
31 | k += (num-1); | |
32 | } | |
33 | k++; | |
34 | } | |
35 | } | |
36 | this.epSquares = []; //no en-passant here | |
37 | } | |
38 | ||
39 | setFlags(fen) | |
40 | { | |
41 | // TODO: for compatibility? | |
42 | this.castleFlags = {"w":[false,false], "b":[false,false]}; | |
43 | } | |
44 | ||
45 | static get IMMOBILIZER() { return 'm'; } | |
46 | // Although other pieces keep their names here for coding simplicity, | |
47 | // keep in mind that: | |
48 | // - a "rook" is a coordinator, capturing by coordinating with the king | |
49 | // - a "knight" is a long-leaper, capturing as in draughts | |
50 | // - a "bishop" is a chameleon, capturing as its prey | |
51 | // - a "queen" is a withdrawer, capturing by moving away from pieces | |
52 | ||
2f3c8451 BA |
53 | // Is piece on square (x,y) immobilized? |
54 | isImmobilized([x,y]) | |
2eef6db6 | 55 | { |
7688bf77 BA |
56 | const piece = this.getPiece(x,y); |
57 | const color = this.getColor(x,y); | |
58 | const oppCol = this.getOppCol(color); | |
7688bf77 | 59 | const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
9d218497 | 60 | outerLoop: |
7688bf77 BA |
61 | for (let step of adjacentSteps) |
62 | { | |
63 | const [i,j] = [x+step[0],y+step[1]]; | |
0b7d99ec | 64 | if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY |
7688bf77 BA |
65 | && this.getColor(i,j) == oppCol) |
66 | { | |
67 | const oppPiece = this.getPiece(i,j); | |
2316f8b8 | 68 | if (oppPiece == V.IMMOBILIZER) |
7688bf77 | 69 | { |
9d218497 BA |
70 | // Moving is impossible only if this immobilizer is not neutralized |
71 | for (let step2 of adjacentSteps) | |
72 | { | |
73 | const [i2,j2] = [i+step2[0],j+step2[1]]; | |
2316f8b8 BA |
74 | if (i2 == x && j2 == y) |
75 | continue; //skip initial piece! | |
0b7d99ec BA |
76 | if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY |
77 | && this.getColor(i2,j2) == color) | |
9d218497 | 78 | { |
f4fd6580 BA |
79 | if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2))) |
80 | return false; | |
9d218497 BA |
81 | } |
82 | } | |
f4fd6580 | 83 | return true; //immobilizer isn't neutralized |
7688bf77 | 84 | } |
2316f8b8 BA |
85 | // Chameleons can't be immobilized twice, because there is only one immobilizer |
86 | if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER) | |
87 | return true; | |
7688bf77 BA |
88 | } |
89 | } | |
f4fd6580 | 90 | return false; |
2f3c8451 BA |
91 | } |
92 | ||
93 | getPotentialMovesFrom([x,y]) | |
94 | { | |
95 | // Pre-check: is thing on this square immobilized? | |
96 | if (this.isImmobilized([x,y])) | |
97 | return []; | |
2eef6db6 BA |
98 | switch (this.getPiece(x,y)) |
99 | { | |
0b7d99ec | 100 | case V.IMMOBILIZER: |
2eef6db6 BA |
101 | return this.getPotentialImmobilizerMoves([x,y]); |
102 | default: | |
103 | return super.getPotentialMovesFrom([x,y]); | |
104 | } | |
2eef6db6 BA |
105 | } |
106 | ||
107 | getSlideNJumpMoves([x,y], steps, oneStep) | |
108 | { | |
109 | const color = this.getColor(x,y); | |
110 | const piece = this.getPiece(x,y); | |
111 | let moves = []; | |
2eef6db6 BA |
112 | outerLoop: |
113 | for (let step of steps) | |
114 | { | |
115 | let i = x + step[0]; | |
116 | let j = y + step[1]; | |
0b7d99ec | 117 | while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY) |
2eef6db6 BA |
118 | { |
119 | moves.push(this.getBasicMove([x,y], [i,j])); | |
120 | if (oneStep !== undefined) | |
121 | continue outerLoop; | |
122 | i += step[0]; | |
123 | j += step[1]; | |
124 | } | |
125 | // Only king can take on occupied square: | |
0b7d99ec | 126 | if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j])) |
2eef6db6 | 127 | moves.push(this.getBasicMove([x,y], [i,j])); |
2eef6db6 BA |
128 | } |
129 | return moves; | |
130 | } | |
131 | ||
a3c86ec9 BA |
132 | // Modify capturing moves among listed pawn moves |
133 | addPawnCaptures(moves, byChameleon) | |
134 | { | |
0b7d99ec | 135 | const steps = V.steps[V.ROOK]; |
a3c86ec9 BA |
136 | const color = this.turn; |
137 | const oppCol = this.getOppCol(color); | |
138 | moves.forEach(m => { | |
139 | if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y) | |
140 | return; //chameleon not moving as pawn | |
141 | // Try capturing in every direction | |
142 | for (let step of steps) | |
143 | { | |
144 | const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]]; | |
0b7d99ec | 145 | if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY |
a3c86ec9 BA |
146 | && this.getColor(sq2[0],sq2[1]) == color) |
147 | { | |
148 | // Potential capture | |
149 | const sq1 = [m.end.x+step[0],m.end.y+step[1]]; | |
0b7d99ec | 150 | if (this.board[sq1[0]][sq1[1]] != V.EMPTY |
a3c86ec9 BA |
151 | && this.getColor(sq1[0],sq1[1]) == oppCol) |
152 | { | |
153 | const piece1 = this.getPiece(sq1[0],sq1[1]); | |
0b7d99ec | 154 | if (!byChameleon || piece1 == V.PAWN) |
a3c86ec9 BA |
155 | { |
156 | m.vanish.push(new PiPo({ | |
157 | x:sq1[0], | |
158 | y:sq1[1], | |
159 | c:oppCol, | |
160 | p:piece1 | |
161 | })); | |
162 | } | |
163 | } | |
164 | } | |
165 | } | |
166 | }); | |
167 | } | |
168 | ||
7688bf77 | 169 | // "Pincher" |
2eef6db6 BA |
170 | getPotentialPawnMoves([x,y]) |
171 | { | |
7688bf77 | 172 | let moves = super.getPotentialRookMoves([x,y]); |
a3c86ec9 BA |
173 | this.addPawnCaptures(moves); |
174 | return moves; | |
2eef6db6 BA |
175 | } |
176 | ||
a3c86ec9 | 177 | addRookCaptures(moves, byChameleon) |
2eef6db6 | 178 | { |
a3c86ec9 | 179 | const color = this.turn; |
7688bf77 BA |
180 | const oppCol = this.getOppCol(color); |
181 | const kp = this.kingPos[color]; | |
7688bf77 BA |
182 | moves.forEach(m => { |
183 | // Check piece-king rectangle (if any) corners for enemy pieces | |
184 | if (m.end.x == kp[0] || m.end.y == kp[1]) | |
185 | return; //"flat rectangle" | |
523da5d5 BA |
186 | const corner1 = [m.end.x, kp[1]]; |
187 | const corner2 = [kp[0], m.end.y]; | |
7688bf77 BA |
188 | for (let [i,j] of [corner1,corner2]) |
189 | { | |
0b7d99ec | 190 | if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol) |
7688bf77 | 191 | { |
a3c86ec9 | 192 | const piece = this.getPiece(i,j); |
0b7d99ec | 193 | if (!byChameleon || piece == V.ROOK) |
a3c86ec9 BA |
194 | { |
195 | m.vanish.push( new PiPo({ | |
196 | x:i, | |
197 | y:j, | |
198 | p:piece, | |
199 | c:oppCol | |
200 | }) ); | |
201 | } | |
7688bf77 BA |
202 | } |
203 | } | |
204 | }); | |
a3c86ec9 BA |
205 | } |
206 | ||
207 | // Coordinator | |
208 | getPotentialRookMoves(sq) | |
209 | { | |
210 | let moves = super.getPotentialQueenMoves(sq); | |
211 | this.addRookCaptures(moves); | |
7688bf77 | 212 | return moves; |
2eef6db6 BA |
213 | } |
214 | ||
7688bf77 | 215 | // Long-leaper |
a3c86ec9 | 216 | getKnightCaptures(startSquare, byChameleon) |
2eef6db6 | 217 | { |
7688bf77 | 218 | // Look in every direction for captures |
7688bf77 | 219 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
a3c86ec9 BA |
220 | const color = this.turn; |
221 | const oppCol = this.getOppCol(color); | |
222 | let moves = []; | |
223 | const [x,y] = [startSquare[0],startSquare[1]]; | |
224 | const piece = this.getPiece(x,y); //might be a chameleon! | |
225 | outerLoop: | |
7688bf77 BA |
226 | for (let step of steps) |
227 | { | |
228 | let [i,j] = [x+step[0], y+step[1]]; | |
0b7d99ec | 229 | while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY) |
7688bf77 BA |
230 | { |
231 | i += step[0]; | |
232 | j += step[1]; | |
233 | } | |
0b7d99ec | 234 | if (!V.OnBoard(i,j) || this.getColor(i,j)==color |
a3c86ec9 BA |
235 | || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT)) |
236 | { | |
7688bf77 | 237 | continue; |
a3c86ec9 BA |
238 | } |
239 | // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits, | |
240 | // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then | |
241 | // add move until cur square; if cur is occupied then stop if !!byChameleon and | |
242 | // the square not occupied by a leaper. | |
243 | let last = [i,j]; | |
244 | let cur = [i+step[0],j+step[1]]; | |
245 | let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ]; | |
0b7d99ec | 246 | while (V.OnBoard(cur[0],cur[1])) |
a3c86ec9 BA |
247 | { |
248 | if (this.board[last[0]][last[1]] != V.EMPTY) | |
249 | { | |
250 | const oppPiece = this.getPiece(last[0],last[1]); | |
251 | if (!!byChameleon && oppPiece != V.KNIGHT) | |
252 | continue outerLoop; | |
253 | // Something to eat: | |
254 | vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) ); | |
255 | } | |
256 | if (this.board[cur[0]][cur[1]] != V.EMPTY) | |
257 | { | |
258 | if (this.getColor(cur[0],cur[1]) == color | |
259 | || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test | |
260 | { | |
261 | continue outerLoop; | |
262 | } | |
263 | } | |
264 | else | |
265 | { | |
266 | moves.push(new Move({ | |
267 | appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ], | |
268 | vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required? | |
269 | start: {x:x,y:y}, | |
270 | end: {x:cur[0],y:cur[1]} | |
271 | })); | |
272 | } | |
273 | last = [last[0]+step[0],last[1]+step[1]]; | |
274 | cur = [cur[0]+step[0],cur[1]+step[1]]; | |
275 | } | |
7688bf77 BA |
276 | } |
277 | return moves; | |
2eef6db6 BA |
278 | } |
279 | ||
a3c86ec9 BA |
280 | // Long-leaper |
281 | getPotentialKnightMoves(sq) | |
282 | { | |
283 | return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq)); | |
284 | } | |
285 | ||
c28265aa | 286 | getPotentialBishopMoves([x,y]) |
2eef6db6 | 287 | { |
c28265aa BA |
288 | let moves = super.getPotentialQueenMoves([x,y]) |
289 | .concat(this.getKnightCaptures([x,y],"asChameleon")); | |
2f3c8451 | 290 | // No "king capture" because king cannot remain under check |
a3c86ec9 BA |
291 | this.addPawnCaptures(moves, "asChameleon"); |
292 | this.addRookCaptures(moves, "asChameleon"); | |
293 | this.addQueenCaptures(moves, "asChameleon"); | |
294 | // Post-processing: merge similar moves, concatenating vanish arrays | |
295 | let mergedMoves = {}; | |
a3c86ec9 | 296 | moves.forEach(m => { |
0b7d99ec | 297 | const key = m.end.x + V.size.x * m.end.y; |
a3c86ec9 BA |
298 | if (!mergedMoves[key]) |
299 | mergedMoves[key] = m; | |
300 | else | |
301 | { | |
302 | for (let i=1; i<m.vanish.length; i++) | |
303 | mergedMoves[key].vanish.push(m.vanish[i]); | |
304 | } | |
305 | }); | |
306 | // Finally return an array | |
307 | moves = []; | |
308 | Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); }); | |
309 | return moves; | |
2eef6db6 BA |
310 | } |
311 | ||
a3c86ec9 BA |
312 | // Withdrawer |
313 | addQueenCaptures(moves, byChameleon) | |
2eef6db6 | 314 | { |
a3c86ec9 BA |
315 | if (moves.length == 0) |
316 | return; | |
317 | const [x,y] = [moves[0].start.x,moves[0].start.y]; | |
7688bf77 BA |
318 | const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
319 | let capturingDirections = []; | |
a3c86ec9 | 320 | const color = this.turn; |
7688bf77 BA |
321 | const oppCol = this.getOppCol(color); |
322 | adjacentSteps.forEach(step => { | |
323 | const [i,j] = [x+step[0],y+step[1]]; | |
0b7d99ec | 324 | if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol |
a3c86ec9 BA |
325 | && (!byChameleon || this.getPiece(i,j) == V.QUEEN)) |
326 | { | |
7688bf77 | 327 | capturingDirections.push(step); |
a3c86ec9 | 328 | } |
7688bf77 BA |
329 | }); |
330 | moves.forEach(m => { | |
331 | const step = [ | |
332 | m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0, | |
333 | m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0 | |
334 | ]; | |
a3c86ec9 | 335 | // NOTE: includes() and even _.isEqual() functions fail... |
7688bf77 | 336 | // TODO: this test should be done only once per direction |
a3c86ec9 BA |
337 | if (capturingDirections.some(dir => |
338 | { return (dir[0]==-step[0] && dir[1]==-step[1]); })) | |
7688bf77 BA |
339 | { |
340 | const [i,j] = [x-step[0],y-step[1]]; | |
341 | m.vanish.push(new PiPo({ | |
342 | x:i, | |
343 | y:j, | |
344 | p:this.getPiece(i,j), | |
345 | c:oppCol | |
346 | })); | |
347 | } | |
348 | }); | |
2eef6db6 BA |
349 | } |
350 | ||
a3c86ec9 BA |
351 | getPotentialQueenMoves(sq) |
352 | { | |
353 | let moves = super.getPotentialQueenMoves(sq); | |
354 | this.addQueenCaptures(moves); | |
355 | return moves; | |
356 | } | |
357 | ||
45338cdd BA |
358 | getPotentialImmobilizerMoves(sq) |
359 | { | |
a3c86ec9 | 360 | // Immobilizer doesn't capture |
45338cdd BA |
361 | return super.getPotentialQueenMoves(sq); |
362 | } | |
363 | ||
2eef6db6 BA |
364 | getPotentialKingMoves(sq) |
365 | { | |
2eef6db6 BA |
366 | return this.getSlideNJumpMoves(sq, |
367 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); | |
368 | } | |
369 | ||
2f3c8451 | 370 | // isAttacked() is OK because the immobilizer doesn't take |
9d218497 | 371 | |
2f3c8451 | 372 | isAttackedByPawn([x,y], colors) |
2eef6db6 | 373 | { |
f4fd6580 BA |
374 | // Square (x,y) must be surroundable by two enemy pieces, |
375 | // and one of them at least should be a pawn (moving). | |
e1cce115 | 376 | const dirs = [ [1,0],[0,1] ]; |
0b7d99ec | 377 | const steps = V.steps[V.ROOK]; |
2f3c8451 BA |
378 | for (let dir of dirs) |
379 | { | |
380 | const [i1,j1] = [x-dir[0],y-dir[1]]; //"before" | |
381 | const [i2,j2] = [x+dir[0],y+dir[1]]; //"after" | |
0b7d99ec | 382 | if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2)) |
2f3c8451 | 383 | { |
0b7d99ec BA |
384 | if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1)) |
385 | && this.board[i2][j2]==V.EMPTY) | |
f4fd6580 | 386 | || |
0b7d99ec BA |
387 | (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2)) |
388 | && this.board[i1][j1]==V.EMPTY)) | |
f4fd6580 BA |
389 | { |
390 | // Search a movable enemy pawn landing on the empty square | |
391 | for (let step of steps) | |
392 | { | |
0b7d99ec | 393 | let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]); |
f4fd6580 | 394 | let [i3,j3] = [ii+step[0],jj+step[1]]; |
0b7d99ec | 395 | while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY) |
f4fd6580 BA |
396 | { |
397 | i3 += step[0]; | |
398 | j3 += step[1]; | |
399 | } | |
0b7d99ec BA |
400 | if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3)) |
401 | && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3])) | |
f4fd6580 BA |
402 | { |
403 | return true; | |
404 | } | |
405 | } | |
406 | } | |
2f3c8451 BA |
407 | } |
408 | } | |
409 | return false; | |
2eef6db6 BA |
410 | } |
411 | ||
2f3c8451 | 412 | isAttackedByRook([x,y], colors) |
2eef6db6 | 413 | { |
f4fd6580 BA |
414 | // King must be on same column or row, |
415 | // and a rook should be able to reach a capturing square | |
f4fd6580 BA |
416 | // colors contains only one element, giving the oppCol and thus king position |
417 | const sameRow = (x == this.kingPos[colors[0]][0]); | |
418 | const sameColumn = (y == this.kingPos[colors[0]][1]); | |
419 | if (sameRow || sameColumn) | |
2f3c8451 | 420 | { |
f4fd6580 | 421 | // Look for the enemy rook (maximum 1) |
0b7d99ec | 422 | for (let i=0; i<V.size.x; i++) |
2f3c8451 | 423 | { |
0b7d99ec | 424 | for (let j=0; j<V.size.y; j++) |
2f3c8451 | 425 | { |
0b7d99ec BA |
426 | if (this.board[i][j] != V.EMPTY && colors.includes(this.getColor(i,j)) |
427 | && this.getPiece(i,j) == V.ROOK) | |
f4fd6580 BA |
428 | { |
429 | if (this.isImmobilized([i,j])) | |
430 | return false; //because only one rook | |
431 | // Can it reach a capturing square? | |
432 | // Easy but quite suboptimal way (TODO): generate all moves (turn is OK) | |
433 | const moves = this.getPotentialMovesFrom([i,j]); | |
434 | for (let move of moves) | |
435 | { | |
436 | if (sameRow && move.end.y == y || sameColumn && move.end.x == x) | |
437 | return true; | |
438 | } | |
439 | } | |
2f3c8451 BA |
440 | } |
441 | } | |
442 | } | |
443 | return false; | |
2eef6db6 BA |
444 | } |
445 | ||
2f3c8451 | 446 | isAttackedByKnight([x,y], colors) |
2eef6db6 | 447 | { |
2f3c8451 BA |
448 | // Square (x,y) must be on same line as a knight, |
449 | // and there must be empty square(s) behind. | |
2f3c8451 | 450 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
2f3c8451 BA |
451 | outerLoop: |
452 | for (let step of steps) | |
7aea7942 | 453 | { |
2f3c8451 | 454 | const [i0,j0] = [x+step[0],y+step[1]]; |
0b7d99ec | 455 | if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY) |
2f3c8451 BA |
456 | { |
457 | // Try in opposite direction: | |
458 | let [i,j] = [x-step[0],y-step[1]]; | |
0b7d99ec | 459 | while (V.OnBoard(i,j)) |
2f3c8451 | 460 | { |
0b7d99ec | 461 | while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY) |
523da5d5 BA |
462 | { |
463 | i -= step[0]; | |
464 | j -= step[1]; | |
465 | } | |
0b7d99ec | 466 | if (V.OnBoard(i,j)) |
523da5d5 BA |
467 | { |
468 | if (colors.includes(this.getColor(i,j))) | |
469 | { | |
470 | if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j])) | |
471 | return true; | |
472 | continue outerLoop; | |
473 | } | |
e1cce115 BA |
474 | // [else] Our color, could be captured *if there was an empty space* |
475 | if (this.board[i+step[0]][j+step[1]] != V.EMPTY) | |
476 | continue outerLoop; | |
523da5d5 BA |
477 | i -= step[0]; |
478 | j -= step[1]; | |
479 | } | |
2f3c8451 BA |
480 | } |
481 | } | |
c28265aa | 482 | } |
2f3c8451 BA |
483 | return false; |
484 | } | |
485 | ||
486 | isAttackedByBishop([x,y], colors) | |
487 | { | |
488 | // We cheat a little here: since this function is used exclusively for king, | |
489 | // it's enough to check the immediate surrounding of the square. | |
2f3c8451 | 490 | const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
2f3c8451 | 491 | for (let step of adjacentSteps) |
c28265aa | 492 | { |
2f3c8451 | 493 | const [i,j] = [x+step[0],y+step[1]]; |
0b7d99ec | 494 | if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY |
2f3c8451 | 495 | && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP) |
7aea7942 | 496 | { |
2f3c8451 | 497 | return true; //bishops are never immobilized |
7aea7942 BA |
498 | } |
499 | } | |
2f3c8451 | 500 | return false; |
2eef6db6 BA |
501 | } |
502 | ||
2f3c8451 | 503 | isAttackedByQueen([x,y], colors) |
2eef6db6 | 504 | { |
2f3c8451 BA |
505 | // Square (x,y) must be adjacent to a queen, and the queen must have |
506 | // some free space in the opposite direction from (x,y) | |
2f3c8451 | 507 | const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); |
2f3c8451 | 508 | for (let step of adjacentSteps) |
7aea7942 | 509 | { |
2f3c8451 | 510 | const sq2 = [x+2*step[0],y+2*step[1]]; |
0b7d99ec | 511 | if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY) |
7aea7942 | 512 | { |
2f3c8451 BA |
513 | const sq1 = [x+step[0],y+step[1]]; |
514 | if (this.board[sq1[0]][sq1[1]] != V.EMPTY | |
515 | && colors.includes(this.getColor(sq1[0],sq1[1])) | |
516 | && this.getPiece(sq1[0],sq1[1]) == V.QUEEN | |
517 | && !this.isImmobilized(sq1)) | |
7aea7942 | 518 | { |
2f3c8451 | 519 | return true; |
7aea7942 BA |
520 | } |
521 | } | |
522 | } | |
2f3c8451 | 523 | return false; |
2eef6db6 BA |
524 | } |
525 | ||
2f3c8451 | 526 | updateVariables(move) |
a3c86ec9 | 527 | { |
2f3c8451 BA |
528 | // Just update king(s) position(s) |
529 | const piece = this.getPiece(move.start.x,move.start.y); | |
530 | const c = this.getColor(move.start.x,move.start.y); | |
0b7d99ec | 531 | if (piece == V.KING && move.appear.length > 0) |
2f3c8451 BA |
532 | { |
533 | this.kingPos[c][0] = move.appear[0].x; | |
534 | this.kingPos[c][1] = move.appear[0].y; | |
535 | } | |
a3c86ec9 BA |
536 | } |
537 | ||
2eef6db6 BA |
538 | static get VALUES() { //TODO: totally experimental! |
539 | return { | |
540 | 'p': 1, | |
541 | 'r': 2, | |
542 | 'n': 5, | |
543 | 'b': 3, | |
544 | 'q': 3, | |
545 | 'm': 5, | |
546 | 'k': 1000 | |
547 | }; | |
548 | } | |
549 | ||
550 | static get SEARCH_DEPTH() { return 2; } //TODO? | |
551 | ||
552 | static GenRandInitFen() | |
553 | { | |
554 | let pieces = { "w": new Array(8), "b": new Array(8) }; | |
555 | // Shuffle pieces on first and last rank | |
556 | for (let c of ["w","b"]) | |
557 | { | |
558 | let positions = _.range(8); | |
559 | // Get random squares for every piece, totally freely | |
560 | ||
561 | let randIndex = _.random(7); | |
562 | const bishop1Pos = positions[randIndex]; | |
563 | positions.splice(randIndex, 1); | |
564 | ||
565 | randIndex = _.random(6); | |
566 | const bishop2Pos = positions[randIndex]; | |
567 | positions.splice(randIndex, 1); | |
568 | ||
569 | randIndex = _.random(5); | |
570 | const knight1Pos = positions[randIndex]; | |
571 | positions.splice(randIndex, 1); | |
572 | ||
573 | randIndex = _.random(4); | |
574 | const knight2Pos = positions[randIndex]; | |
575 | positions.splice(randIndex, 1); | |
576 | ||
577 | randIndex = _.random(3); | |
578 | const queenPos = positions[randIndex]; | |
579 | positions.splice(randIndex, 1); | |
580 | ||
581 | randIndex = _.random(2); | |
582 | const kingPos = positions[randIndex]; | |
583 | positions.splice(randIndex, 1); | |
584 | ||
585 | randIndex = _.random(1); | |
586 | const rookPos = positions[randIndex]; | |
587 | positions.splice(randIndex, 1); | |
45338cdd | 588 | const immobilizerPos = positions[0]; |
2eef6db6 BA |
589 | |
590 | pieces[c][bishop1Pos] = 'b'; | |
591 | pieces[c][bishop2Pos] = 'b'; | |
592 | pieces[c][knight1Pos] = 'n'; | |
593 | pieces[c][knight2Pos] = 'n'; | |
594 | pieces[c][queenPos] = 'q'; | |
595 | pieces[c][kingPos] = 'k'; | |
596 | pieces[c][rookPos] = 'r'; | |
597 | pieces[c][immobilizerPos] = 'm'; | |
598 | } | |
599 | return pieces["b"].join("") + | |
600 | "/pppppppp/8/8/8/8/PPPPPPPP/" + | |
601 | pieces["w"].join("").toUpperCase() + | |
602 | " 0000"; //TODO: flags?! | |
603 | } | |
604 | ||
605 | getFlagsFen() | |
606 | { | |
607 | return "0000"; //TODO: or "-" ? | |
608 | } | |
15c1295a BA |
609 | |
610 | getNotation(move) | |
611 | { | |
612 | const initialSquare = | |
0b7d99ec BA |
613 | String.fromCharCode(97 + move.start.y) + (V.size.x-move.start.x); |
614 | const finalSquare = String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x); | |
15c1295a | 615 | let notation = undefined; |
0b7d99ec | 616 | if (move.appear[0].p == V.PAWN) |
15c1295a BA |
617 | { |
618 | // Pawn: generally ambiguous short notation, so we use full description | |
619 | notation = "P" + initialSquare + finalSquare; | |
620 | } | |
0b7d99ec | 621 | else if (move.appear[0].p == V.KING) |
15c1295a BA |
622 | notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare; |
623 | else | |
624 | notation = move.appear[0].p.toUpperCase() + finalSquare; | |
0b7d99ec | 625 | if (move.vanish.length > 1 && move.appear[0].p != V.KING) |
15c1295a BA |
626 | notation += "X"; //capture mark (not describing what is captured...) |
627 | return notation; | |
628 | } | |
32cfcea4 | 629 | } |