57f3e25c6e8cd6fbabfafe7ccc224fde1c1e3c8b
1 class UltimaRules
extends ChessRules
5 if (b
[1] == "m") //'m' for Immobilizer (I is too similar to 1)
7 return b
; //usual piece
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
++)
18 for (let j
=0; j
<position
[i
].length
; j
++)
20 switch (position
[i
].charAt(j
))
23 this.kingPos
['b'] = [i
,k
];
26 this.kingPos
['w'] = [i
,k
];
29 let num
= parseInt(position
[i
].charAt(j
));
36 this.epSquares
= []; //no en-passant here
41 // TODO: for compatibility?
42 this.castleFlags
= {"w":[false,false], "b":[false,false]};
45 static get IMMOBILIZER() { return 'm'; }
46 // Although other pieces keep their names here for coding simplicity,
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
53 // Is piece on square (x,y) immobilized?
56 // Final check: is this knight immobilized?
57 let foundImmobilizer
= false;
58 let neutralized
= false;
60 for (let step
of steps
)
62 const [i2
,j2
] = [i
+step
[0],j
+step
[1]];
63 if (i2
>=0 && i2
<sizeX
&& j2
>=0 && j2
<sizeY
64 && this.board
[i2
][j2
] != V
.EMPTY
65 && this.getColor(i2
,j2
) == oppCol
66 && this.getPiece(i2
,j2
) == V
.IMMOBILIZER
)
68 foundImmobilizer
= true;
69 // Moving is possible only if this immobilizer is neutralized
70 for (let step2
of steps
)
72 const [i3
,j3
] = [i2
+step2
[0],j2
+step2
[1]];
73 if (i3
>=0 && i3
<sizeX
&& j3
>=0 && j3
<sizeY
74 && this.board
[i3
][j3
] != V
.EMPTY
&& this.getColor(i3
,j3
) == color
75 && [V
.BISHOP
,V
.IMMOBILIZER
].includes(this.getPiece(i3
,j3
)))
83 if (!foundImmobilizer
|| neutralized
)
86 const piece
= this.getPiece(x
,y
);
87 const color
= this.getColor(x
,y
);
88 const oppCol
= this.getOppCol(color
);
89 const V
= VariantRules
;
90 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
91 const [sizeX
,sizeY
] = V
.size
;
93 for (let step
of adjacentSteps
)
95 const [i
,j
] = [x
+step
[0],y
+step
[1]];
96 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
] != V
.EMPTY
97 && this.getColor(i
,j
) == oppCol
)
99 const oppPiece
= this.getPiece(i
,j
);
100 if (oppPiece
== V
.BISHOP
&& piece
== V
.IMMOBILIZER
)
102 if (oppPiece
== V
.IMMOBILIZER
&& ![V
.BISHOP
,V
.IMMOBILIZER
].includes(piece
))
104 // Moving is impossible only if this immobilizer is not neutralized
105 for (let step2
of adjacentSteps
)
107 const [i2
,j2
] = [i
+step2
[0],j
+step2
[1]];
108 if (i2
>=0 && i2
<sizeX
&& j2
>=0 && j2
<sizeY
109 && this.board
[i2
][j2
] != V
.EMPTY
&& this.getColor(i2
,j2
) == color
)
111 const friendlyPiece
= this.getPiece(i2
,j2
);
112 if ([V
.BISHOP
,V
.IMMOBILIZER
].includes(friendlyPiece
))
116 return []; //immobilizer isn't neutralized
122 getPotentialMovesFrom([x
,y
])
124 // Pre-check: is thing on this square immobilized?
125 if (this.isImmobilized([x
,y
]))
127 switch (this.getPiece(x
,y
))
129 case VariantRules
.IMMOBILIZER:
130 return this.getPotentialImmobilizerMoves([x
,y
]);
132 return super.getPotentialMovesFrom([x
,y
]);
136 getSlideNJumpMoves([x
,y
], steps
, oneStep
)
138 const color
= this.getColor(x
,y
);
139 const piece
= this.getPiece(x
,y
);
141 const [sizeX
,sizeY
] = VariantRules
.size
;
143 for (let step
of steps
)
147 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
148 && this.board
[i
][j
] == VariantRules
.EMPTY
)
150 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
151 if (oneStep
!== undefined)
156 // Only king can take on occupied square:
157 if (piece
==VariantRules
.KING
&& i
>=0 && i
<sizeX
&& j
>=0
158 && j
<sizeY
&& this.canTake([x
,y
], [i
,j
]))
160 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
166 // Modify capturing moves among listed pawn moves
167 addPawnCaptures(moves
, byChameleon
)
169 const steps
= VariantRules
.steps
[VariantRules
.ROOK
];
170 const [sizeX
,sizeY
] = VariantRules
.size
;
171 const color
= this.turn
;
172 const oppCol
= this.getOppCol(color
);
174 if (!!byChameleon
&& m
.start
.x
!=m
.end
.x
&& m
.start
.y
!=m
.end
.y
)
175 return; //chameleon not moving as pawn
176 // Try capturing in every direction
177 for (let step
of steps
)
179 const sq2
= [m
.end
.x
+2*step
[0],m
.end
.y
+2*step
[1]];
180 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
181 && this.board
[sq2
[0]][sq2
[1]] != VariantRules
.EMPTY
182 && this.getColor(sq2
[0],sq2
[1]) == color
)
185 const sq1
= [m
.end
.x
+step
[0],m
.end
.y
+step
[1]];
186 if (this.board
[sq1
[0]][sq1
[1]] != VariantRules
.EMPTY
187 && this.getColor(sq1
[0],sq1
[1]) == oppCol
)
189 const piece1
= this.getPiece(sq1
[0],sq1
[1]);
190 if (!byChameleon
|| piece1
== VariantRules
.PAWN
)
192 m
.vanish
.push(new PiPo({
206 getPotentialPawnMoves([x
,y
])
208 let moves
= super.getPotentialRookMoves([x
,y
]);
209 this.addPawnCaptures(moves
);
213 addRookCaptures(moves
, byChameleon
)
215 const color
= this.turn
;
216 const oppCol
= this.getOppCol(color
);
217 const kp
= this.kingPos
[color
];
219 // Check piece-king rectangle (if any) corners for enemy pieces
220 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1])
221 return; //"flat rectangle"
222 const corner1
= [Math
.max(m
.end
.x
,kp
[0]), Math
.min(m
.end
.y
,kp
[1])];
223 const corner2
= [Math
.min(m
.end
.x
,kp
[0]), Math
.max(m
.end
.y
,kp
[1])];
224 for (let [i
,j
] of [corner1
,corner2
])
226 if (this.board
[i
][j
] != VariantRules
.EMPTY
&& this.getColor(i
,j
) == oppCol
)
228 const piece
= this.getPiece(i
,j
);
229 if (!byChameleon
|| piece
== VariantRules
.ROOK
)
231 m
.vanish
.push( new PiPo({
244 getPotentialRookMoves(sq
)
246 let moves
= super.getPotentialQueenMoves(sq
);
247 this.addRookCaptures(moves
);
252 getKnightCaptures(startSquare
, byChameleon
)
254 // Look in every direction for captures
255 const V
= VariantRules
;
256 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
257 const [sizeX
,sizeY
] = V
.size
;
258 const color
= this.turn
;
259 const oppCol
= this.getOppCol(color
);
261 const [x
,y
] = [startSquare
[0],startSquare
[1]];
262 const piece
= this.getPiece(x
,y
); //might be a chameleon!
264 for (let step
of steps
)
266 let [i
,j
] = [x
+step
[0], y
+step
[1]];
267 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]==V
.EMPTY
)
272 if (i
<0 || i
>=sizeX
|| j
<0 || j
>=sizeY
|| this.getColor(i
,j
)==color
273 || (!!byChameleon
&& this.getPiece(i
,j
)!=V
.KNIGHT
))
277 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
278 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
279 // add move until cur square; if cur is occupied then stop if !!byChameleon and
280 // the square not occupied by a leaper.
282 let cur
= [i
+step
[0],j
+step
[1]];
283 let vanished
= [ new PiPo({x:x
,y:y
,c:color
,p:piece
}) ];
284 while (cur
[0]>=0 && cur
[0]<sizeX
&& cur
[1]>=0 && cur
[1]<sizeY
)
286 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
)
288 const oppPiece
= this.getPiece(last
[0],last
[1]);
289 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
)
292 vanished
.push( new PiPo({x:last
[0],y:last
[1],c:oppCol
,p:oppPiece
}) );
294 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
)
296 if (this.getColor(cur
[0],cur
[1]) == color
297 || this.board
[last
[0]][last
[1]] != V
.EMPTY
) //TODO: redundant test
304 moves
.push(new Move({
305 appear: [ new PiPo({x:cur
[0],y:cur
[1],c:color
,p:piece
}) ],
306 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
308 end: {x:cur
[0],y:cur
[1]}
311 last
= [last
[0]+step
[0],last
[1]+step
[1]];
312 cur
= [cur
[0]+step
[0],cur
[1]+step
[1]];
319 getPotentialKnightMoves(sq
)
321 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
324 getPotentialBishopMoves([x
,y
])
326 let moves
= super.getPotentialQueenMoves([x
,y
])
327 .concat(this.getKnightCaptures([x
,y
],"asChameleon"));
328 // No "king capture" because king cannot remain under check
329 this.addPawnCaptures(moves
, "asChameleon");
330 this.addRookCaptures(moves
, "asChameleon");
331 this.addQueenCaptures(moves
, "asChameleon");
332 // Post-processing: merge similar moves, concatenating vanish arrays
333 let mergedMoves
= {};
334 const [sizeX
,sizeY
] = VariantRules
.size
;
336 const key
= m
.end
.x
+ sizeX
* m
.end
.y
;
337 if (!mergedMoves
[key
])
338 mergedMoves
[key
] = m
;
341 for (let i
=1; i
<m
.vanish
.length
; i
++)
342 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
345 // Finally return an array
347 Object
.keys(mergedMoves
).forEach(k
=> { moves
.push(mergedMoves
[k
]); });
352 addQueenCaptures(moves
, byChameleon
)
354 if (moves
.length
== 0)
356 const [x
,y
] = [moves
[0].start
.x
,moves
[0].start
.y
];
357 const V
= VariantRules
;
358 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
359 let capturingDirections
= [];
360 const color
= this.turn
;
361 const oppCol
= this.getOppCol(color
);
362 const [sizeX
,sizeY
] = V
.size
;
363 adjacentSteps
.forEach(step
=> {
364 const [i
,j
] = [x
+step
[0],y
+step
[1]];
365 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
366 && this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == oppCol
367 && (!byChameleon
|| this.getPiece(i
,j
) == V
.QUEEN
))
369 capturingDirections
.push(step
);
374 m
.end
.x
!=x
? (m
.end
.x
-x
)/Math
.abs(m
.end
.x
-x
) : 0,
375 m
.end
.y
!=y
? (m
.end
.y
-y
)/Math
.abs(m
.end
.y
-y
) : 0
377 // NOTE: includes() and even _.isEqual() functions fail...
378 // TODO: this test should be done only once per direction
379 if (capturingDirections
.some(dir
=>
380 { return (dir
[0]==-step
[0] && dir
[1]==-step
[1]); }))
382 const [i
,j
] = [x
-step
[0],y
-step
[1]];
383 m
.vanish
.push(new PiPo({
386 p:this.getPiece(i
,j
),
393 getPotentialQueenMoves(sq
)
395 let moves
= super.getPotentialQueenMoves(sq
);
396 this.addQueenCaptures(moves
);
400 getPotentialImmobilizerMoves(sq
)
402 // Immobilizer doesn't capture
403 return super.getPotentialQueenMoves(sq
);
406 getPotentialKingMoves(sq
)
408 const V
= VariantRules
;
409 return this.getSlideNJumpMoves(sq
,
410 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
413 // isAttacked() is OK because the immobilizer doesn't take
415 // TODO: check if any pawn can reach capturing square + !immobilized
416 isAttackedByPawn([x
,y
], colors
)
418 // Square (x,y) must be surrounded by two enemy pieces,
419 // and one of them at least should be a pawn.
420 const dirs
= [ [1,0],[0,1],[1,1],[-1,1] ];
421 const [sizeX
,sizeY
] = VariantRules
.size
;
422 for (let dir
of dirs
)
424 const [i1
,j1
] = [x
-dir
[0],y
-dir
[1]]; //"before"
425 const [i2
,j2
] = [x
+dir
[0],y
+dir
[1]]; //"after"
426 if (i1
>=0 && i1
<sizeX
&& i2
>=0 && i2
<sizeX
427 && j1
>=0 && j1
<sizeY
&& j2
>=0 && j2
<sizeY
428 && this.board
[i1
][j1
]!=VariantRules
.EMPTY
429 && this.board
[i2
][j2
]!=VariantRules
.EMPTY
430 && colors
.includes(this.getColor(i1
,j1
))
431 && colors
.includes(this.getColor(i2
,j2
))
432 && [this.getPiece(i1
,j1
),this.getPiece(i2
,j2
)].includes(VariantRules
.PAWN
))
440 // TODO: check if enemy's rook can reach capturing squares + !immobilized
441 isAttackedByRook([x
,y
], colors
)
443 const [sizeX
,sizeY
] = VariantRules
.size
;
444 // King must be on same column and a rook on same row (or reverse)
445 if (x
== this.kingPos
[colors
[0]][0]) //using colors[0], only element in this case
447 // Look for enemy rook on this column
448 for (let i
=0; i
<sizeY
; i
++)
450 if (this.board
[x
][i
] != VariantRules
.EMPTY
451 && colors
.includes(this.getColor(x
,i
))
452 && this.getPiece(x
,i
) == VariantRules
.ROOK
)
458 else if (y
== this.kingPos
[colors
[0]][1])
460 // Look for enemy rook on this row
461 for (let i
=0; i
<sizeX
; i
++)
463 if (this.board
[i
][y
] != VariantRules
.EMPTY
464 && colors
.includes(this.getColor(i
,y
))
465 && this.getPiece(i
,y
) == VariantRules
.ROOK
)
474 isAttackedByKnight([x
,y
], colors
)
476 // Square (x,y) must be on same line as a knight,
477 // and there must be empty square(s) behind.
478 const V
= VariantRules
;
479 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
480 const [sizeX
,sizeY
] = V
.size
;
482 for (let step
of steps
)
484 const [i0
,j0
] = [x
+step
[0],y
+step
[1]];
485 if (i0
>=0 && i0
<sizeX
&& j0
>=0 && j0
<sizeY
&& this.board
[i0
][j0
] == V
.EMPTY
)
487 // Try in opposite direction:
488 let [i
,j
] = [x
-step
[0],y
-step
[1]];
489 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
] == V
.EMPTY
)
494 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& colors
.includes(this.getColor(i
,j
))
495 && this.getPiece(i
,j
) == V
.KNIGHT
)
497 if (!this.isImmobilized([i
,j
]))
505 isAttackedByBishop([x
,y
], colors
)
507 // We cheat a little here: since this function is used exclusively for king,
508 // it's enough to check the immediate surrounding of the square.
509 const V
= VariantRules
;
510 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
511 const [sizeX
,sizeY
] = V
.size
;
512 for (let step
of adjacentSteps
)
514 const [i
,j
] = [x
+step
[0],y
+step
[1]];
515 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]!=V
.EMPTY
516 && colors
.includes(this.getColor(i
,j
)) && this.getPiece(i
,j
) == V
.BISHOP
)
518 return true; //bishops are never immobilized
524 isAttackedByQueen([x
,y
], colors
)
526 // Square (x,y) must be adjacent to a queen, and the queen must have
527 // some free space in the opposite direction from (x,y)
528 const V
= VariantRules
;
529 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
530 const [sizeX
,sizeY
] = V
.size
;
531 for (let step
of adjacentSteps
)
533 const sq2
= [x
+2*step
[0],y
+2*step
[1]];
534 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
535 && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
)
537 const sq1
= [x
+step
[0],y
+step
[1]];
538 if (this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
539 && colors
.includes(this.getColor(sq1
[0],sq1
[1]))
540 && this.getPiece(sq1
[0],sq1
[1]) == V
.QUEEN
541 && !this.isImmobilized(sq1
))
550 updateVariables(move)
552 // Just update king(s) position(s)
553 const piece
= this.getPiece(move.start
.x
,move.start
.y
);
554 const c
= this.getColor(move.start
.x
,move.start
.y
);
555 if (piece
== VariantRules
.KING
&& move.appear
.length
> 0)
557 this.kingPos
[c
][0] = move.appear
[0].x
;
558 this.kingPos
[c
][1] = move.appear
[0].y
;
562 static get VALUES() { //TODO: totally experimental!
574 static get SEARCH_DEPTH() { return 2; } //TODO?
576 static GenRandInitFen()
578 let pieces
= { "w": new Array(8), "b": new Array(8) };
579 // Shuffle pieces on first and last rank
580 for (let c
of ["w","b"])
582 let positions
= _
.range(8);
583 // Get random squares for every piece, totally freely
585 let randIndex
= _
.random(7);
586 const bishop1Pos
= positions
[randIndex
];
587 positions
.splice(randIndex
, 1);
589 randIndex
= _
.random(6);
590 const bishop2Pos
= positions
[randIndex
];
591 positions
.splice(randIndex
, 1);
593 randIndex
= _
.random(5);
594 const knight1Pos
= positions
[randIndex
];
595 positions
.splice(randIndex
, 1);
597 randIndex
= _
.random(4);
598 const knight2Pos
= positions
[randIndex
];
599 positions
.splice(randIndex
, 1);
601 randIndex
= _
.random(3);
602 const queenPos
= positions
[randIndex
];
603 positions
.splice(randIndex
, 1);
605 randIndex
= _
.random(2);
606 const kingPos
= positions
[randIndex
];
607 positions
.splice(randIndex
, 1);
609 randIndex
= _
.random(1);
610 const rookPos
= positions
[randIndex
];
611 positions
.splice(randIndex
, 1);
612 const immobilizerPos
= positions
[0];
614 pieces
[c
][bishop1Pos
] = 'b';
615 pieces
[c
][bishop2Pos
] = 'b';
616 pieces
[c
][knight1Pos
] = 'n';
617 pieces
[c
][knight2Pos
] = 'n';
618 pieces
[c
][queenPos
] = 'q';
619 pieces
[c
][kingPos
] = 'k';
620 pieces
[c
][rookPos
] = 'r';
621 pieces
[c
][immobilizerPos
] = 'm';
623 return pieces
["b"].join("") +
624 "/pppppppp/8/8/8/8/PPPPPPPP/" +
625 pieces
["w"].join("").toUpperCase() +
626 " 0000"; //TODO: flags?!
631 return "0000"; //TODO: or "-" ?