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 const piece
= this.getPiece(x
,y
);
57 const color
= this.getColor(x
,y
);
58 const oppCol
= this.getOppCol(color
);
59 const V
= VariantRules
;
60 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
61 const [sizeX
,sizeY
] = V
.size
;
63 for (let step
of adjacentSteps
)
65 const [i
,j
] = [x
+step
[0],y
+step
[1]];
66 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
] != V
.EMPTY
67 && this.getColor(i
,j
) == oppCol
)
69 const oppPiece
= this.getPiece(i
,j
);
70 if (oppPiece
== V
.BISHOP
&& piece
== V
.IMMOBILIZER
)
72 if (oppPiece
== V
.IMMOBILIZER
&& ![V
.BISHOP
,V
.IMMOBILIZER
].includes(piece
))
74 // Moving is impossible only if this immobilizer is not neutralized
75 for (let step2
of adjacentSteps
)
77 const [i2
,j2
] = [i
+step2
[0],j
+step2
[1]];
78 if (i2
>=0 && i2
<sizeX
&& j2
>=0 && j2
<sizeY
79 && this.board
[i2
][j2
] != V
.EMPTY
&& this.getColor(i2
,j2
) == color
)
81 if ([V
.BISHOP
,V
.IMMOBILIZER
].includes(this.getPiece(i2
,j2
)))
85 return true; //immobilizer isn't neutralized
92 getPotentialMovesFrom([x
,y
])
94 // Pre-check: is thing on this square immobilized?
95 if (this.isImmobilized([x
,y
]))
97 switch (this.getPiece(x
,y
))
99 case VariantRules
.IMMOBILIZER:
100 return this.getPotentialImmobilizerMoves([x
,y
]);
102 return super.getPotentialMovesFrom([x
,y
]);
106 getSlideNJumpMoves([x
,y
], steps
, oneStep
)
108 const color
= this.getColor(x
,y
);
109 const piece
= this.getPiece(x
,y
);
111 const [sizeX
,sizeY
] = VariantRules
.size
;
113 for (let step
of steps
)
117 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
118 && this.board
[i
][j
] == VariantRules
.EMPTY
)
120 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
121 if (oneStep
!== undefined)
126 // Only king can take on occupied square:
127 if (piece
==VariantRules
.KING
&& i
>=0 && i
<sizeX
&& j
>=0
128 && j
<sizeY
&& this.canTake([x
,y
], [i
,j
]))
130 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
136 // Modify capturing moves among listed pawn moves
137 addPawnCaptures(moves
, byChameleon
)
139 const steps
= VariantRules
.steps
[VariantRules
.ROOK
];
140 const [sizeX
,sizeY
] = VariantRules
.size
;
141 const color
= this.turn
;
142 const oppCol
= this.getOppCol(color
);
144 if (!!byChameleon
&& m
.start
.x
!=m
.end
.x
&& m
.start
.y
!=m
.end
.y
)
145 return; //chameleon not moving as pawn
146 // Try capturing in every direction
147 for (let step
of steps
)
149 const sq2
= [m
.end
.x
+2*step
[0],m
.end
.y
+2*step
[1]];
150 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
151 && this.board
[sq2
[0]][sq2
[1]] != VariantRules
.EMPTY
152 && this.getColor(sq2
[0],sq2
[1]) == color
)
155 const sq1
= [m
.end
.x
+step
[0],m
.end
.y
+step
[1]];
156 if (this.board
[sq1
[0]][sq1
[1]] != VariantRules
.EMPTY
157 && this.getColor(sq1
[0],sq1
[1]) == oppCol
)
159 const piece1
= this.getPiece(sq1
[0],sq1
[1]);
160 if (!byChameleon
|| piece1
== VariantRules
.PAWN
)
162 m
.vanish
.push(new PiPo({
176 getPotentialPawnMoves([x
,y
])
178 let moves
= super.getPotentialRookMoves([x
,y
]);
179 this.addPawnCaptures(moves
);
183 addRookCaptures(moves
, byChameleon
)
185 const color
= this.turn
;
186 const oppCol
= this.getOppCol(color
);
187 const kp
= this.kingPos
[color
];
189 // Check piece-king rectangle (if any) corners for enemy pieces
190 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1])
191 return; //"flat rectangle"
192 const corner1
= [Math
.max(m
.end
.x
,kp
[0]), Math
.min(m
.end
.y
,kp
[1])];
193 const corner2
= [Math
.min(m
.end
.x
,kp
[0]), Math
.max(m
.end
.y
,kp
[1])];
194 for (let [i
,j
] of [corner1
,corner2
])
196 if (this.board
[i
][j
] != VariantRules
.EMPTY
&& this.getColor(i
,j
) == oppCol
)
198 const piece
= this.getPiece(i
,j
);
199 if (!byChameleon
|| piece
== VariantRules
.ROOK
)
201 m
.vanish
.push( new PiPo({
214 getPotentialRookMoves(sq
)
216 let moves
= super.getPotentialQueenMoves(sq
);
217 this.addRookCaptures(moves
);
222 getKnightCaptures(startSquare
, byChameleon
)
224 // Look in every direction for captures
225 const V
= VariantRules
;
226 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
227 const [sizeX
,sizeY
] = V
.size
;
228 const color
= this.turn
;
229 const oppCol
= this.getOppCol(color
);
231 const [x
,y
] = [startSquare
[0],startSquare
[1]];
232 const piece
= this.getPiece(x
,y
); //might be a chameleon!
234 for (let step
of steps
)
236 let [i
,j
] = [x
+step
[0], y
+step
[1]];
237 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]==V
.EMPTY
)
242 if (i
<0 || i
>=sizeX
|| j
<0 || j
>=sizeY
|| this.getColor(i
,j
)==color
243 || (!!byChameleon
&& this.getPiece(i
,j
)!=V
.KNIGHT
))
247 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
248 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
249 // add move until cur square; if cur is occupied then stop if !!byChameleon and
250 // the square not occupied by a leaper.
252 let cur
= [i
+step
[0],j
+step
[1]];
253 let vanished
= [ new PiPo({x:x
,y:y
,c:color
,p:piece
}) ];
254 while (cur
[0]>=0 && cur
[0]<sizeX
&& cur
[1]>=0 && cur
[1]<sizeY
)
256 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
)
258 const oppPiece
= this.getPiece(last
[0],last
[1]);
259 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
)
262 vanished
.push( new PiPo({x:last
[0],y:last
[1],c:oppCol
,p:oppPiece
}) );
264 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
)
266 if (this.getColor(cur
[0],cur
[1]) == color
267 || this.board
[last
[0]][last
[1]] != V
.EMPTY
) //TODO: redundant test
274 moves
.push(new Move({
275 appear: [ new PiPo({x:cur
[0],y:cur
[1],c:color
,p:piece
}) ],
276 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
278 end: {x:cur
[0],y:cur
[1]}
281 last
= [last
[0]+step
[0],last
[1]+step
[1]];
282 cur
= [cur
[0]+step
[0],cur
[1]+step
[1]];
289 getPotentialKnightMoves(sq
)
291 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
294 getPotentialBishopMoves([x
,y
])
296 let moves
= super.getPotentialQueenMoves([x
,y
])
297 .concat(this.getKnightCaptures([x
,y
],"asChameleon"));
298 // No "king capture" because king cannot remain under check
299 this.addPawnCaptures(moves
, "asChameleon");
300 this.addRookCaptures(moves
, "asChameleon");
301 this.addQueenCaptures(moves
, "asChameleon");
302 // Post-processing: merge similar moves, concatenating vanish arrays
303 let mergedMoves
= {};
304 const [sizeX
,sizeY
] = VariantRules
.size
;
306 const key
= m
.end
.x
+ sizeX
* m
.end
.y
;
307 if (!mergedMoves
[key
])
308 mergedMoves
[key
] = m
;
311 for (let i
=1; i
<m
.vanish
.length
; i
++)
312 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
315 // Finally return an array
317 Object
.keys(mergedMoves
).forEach(k
=> { moves
.push(mergedMoves
[k
]); });
322 addQueenCaptures(moves
, byChameleon
)
324 if (moves
.length
== 0)
326 const [x
,y
] = [moves
[0].start
.x
,moves
[0].start
.y
];
327 const V
= VariantRules
;
328 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
329 let capturingDirections
= [];
330 const color
= this.turn
;
331 const oppCol
= this.getOppCol(color
);
332 const [sizeX
,sizeY
] = V
.size
;
333 adjacentSteps
.forEach(step
=> {
334 const [i
,j
] = [x
+step
[0],y
+step
[1]];
335 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
336 && this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == oppCol
337 && (!byChameleon
|| this.getPiece(i
,j
) == V
.QUEEN
))
339 capturingDirections
.push(step
);
344 m
.end
.x
!=x
? (m
.end
.x
-x
)/Math
.abs(m
.end
.x
-x
) : 0,
345 m
.end
.y
!=y
? (m
.end
.y
-y
)/Math
.abs(m
.end
.y
-y
) : 0
347 // NOTE: includes() and even _.isEqual() functions fail...
348 // TODO: this test should be done only once per direction
349 if (capturingDirections
.some(dir
=>
350 { return (dir
[0]==-step
[0] && dir
[1]==-step
[1]); }))
352 const [i
,j
] = [x
-step
[0],y
-step
[1]];
353 m
.vanish
.push(new PiPo({
356 p:this.getPiece(i
,j
),
363 getPotentialQueenMoves(sq
)
365 let moves
= super.getPotentialQueenMoves(sq
);
366 this.addQueenCaptures(moves
);
370 getPotentialImmobilizerMoves(sq
)
372 // Immobilizer doesn't capture
373 return super.getPotentialQueenMoves(sq
);
376 getPotentialKingMoves(sq
)
378 const V
= VariantRules
;
379 return this.getSlideNJumpMoves(sq
,
380 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
383 // isAttacked() is OK because the immobilizer doesn't take
385 isAttackedByPawn([x
,y
], colors
)
387 // Square (x,y) must be surroundable by two enemy pieces,
388 // and one of them at least should be a pawn (moving).
389 const dirs
= [ [1,0],[0,1],[1,1],[-1,1] ];
390 const steps
= VariantRules
.steps
[VariantRules
.ROOK
]
391 .concat(VariantRules
.steps
[VariantRules
.BISHOP
]);
392 const [sizeX
,sizeY
] = VariantRules
.size
;
393 for (let dir
of dirs
)
395 const [i1
,j1
] = [x
-dir
[0],y
-dir
[1]]; //"before"
396 const [i2
,j2
] = [x
+dir
[0],y
+dir
[1]]; //"after"
397 if (i1
>=0 && i1
<sizeX
&& i2
>=0 && i2
<sizeX
398 && j1
>=0 && j1
<sizeY
&& j2
>=0 && j2
<sizeY
)
400 if ((this.board
[i1
][j1
]!=VariantRules
.EMPTY
401 && colors
.includes(this.getColor(i1
,j1
))
402 && this.board
[i2
][j2
]==VariantRules
.EMPTY
)
404 (this.board
[i2
][j2
]!=VariantRules
.EMPTY
405 && colors
.includes(this.getColor(i2
,j2
))
406 && this.board
[i1
][j1
]==VariantRules
.EMPTY
))
408 // Search a movable enemy pawn landing on the empty square
409 for (let step
of steps
)
411 let [ii
,jj
] = (this.board
[i1
][j1
]==VariantRules
.EMPTY
? [i1
,j1
] : [i2
,j2
]);
412 let [i3
,j3
] = [ii
+step
[0],jj
+step
[1]];
413 while (i3
>=0 && i3
<sizeX
&& j3
>=0 && j3
<sizeY
414 && this.board
[i3
][j3
]==VariantRules
.EMPTY
)
419 if (i3
>=0 && i3
<sizeX
&& j3
>=0 && j3
<sizeY
420 && this.getPiece(i3
,j3
) == VariantRules
.PAWN
421 && !this.isImmobilized([i3
,j3
]))
432 isAttackedByRook([x
,y
], colors
)
434 // King must be on same column or row,
435 // and a rook should be able to reach a capturing square
436 const [sizeX
,sizeY
] = VariantRules
.size
;
437 // colors contains only one element, giving the oppCol and thus king position
438 const sameRow
= (x
== this.kingPos
[colors
[0]][0]);
439 const sameColumn
= (y
== this.kingPos
[colors
[0]][1]);
440 if (sameRow
|| sameColumn
)
442 // Look for the enemy rook (maximum 1)
443 for (let i
=0; i
<sizeX
; i
++)
445 for (let j
=0; j
<sizeY
; j
++)
447 if (this.board
[i
][j
] != VariantRules
.EMPTY
448 && colors
.includes(this.getColor(i
,j
))
449 && this.getPiece(i
,j
) == VariantRules
.ROOK
)
451 if (this.isImmobilized([i
,j
]))
452 return false; //because only one rook
453 // Can it reach a capturing square?
454 // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
455 const moves
= this.getPotentialMovesFrom([i
,j
]);
456 for (let move of moves
)
458 if (sameRow
&& move.end
.y
== y
|| sameColumn
&& move.end
.x
== x
)
468 isAttackedByKnight([x
,y
], colors
)
470 // Square (x,y) must be on same line as a knight,
471 // and there must be empty square(s) behind.
472 const V
= VariantRules
;
473 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
474 const [sizeX
,sizeY
] = V
.size
;
476 for (let step
of steps
)
478 const [i0
,j0
] = [x
+step
[0],y
+step
[1]];
479 if (i0
>=0 && i0
<sizeX
&& j0
>=0 && j0
<sizeY
&& this.board
[i0
][j0
] == V
.EMPTY
)
481 // Try in opposite direction:
482 let [i
,j
] = [x
-step
[0],y
-step
[1]];
483 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
] == V
.EMPTY
)
488 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& colors
.includes(this.getColor(i
,j
))
489 && this.getPiece(i
,j
) == V
.KNIGHT
)
491 if (!this.isImmobilized([i
,j
]))
499 isAttackedByBishop([x
,y
], colors
)
501 // We cheat a little here: since this function is used exclusively for king,
502 // it's enough to check the immediate surrounding of the square.
503 const V
= VariantRules
;
504 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
505 const [sizeX
,sizeY
] = V
.size
;
506 for (let step
of adjacentSteps
)
508 const [i
,j
] = [x
+step
[0],y
+step
[1]];
509 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]!=V
.EMPTY
510 && colors
.includes(this.getColor(i
,j
)) && this.getPiece(i
,j
) == V
.BISHOP
)
512 return true; //bishops are never immobilized
518 isAttackedByQueen([x
,y
], colors
)
520 // Square (x,y) must be adjacent to a queen, and the queen must have
521 // some free space in the opposite direction from (x,y)
522 const V
= VariantRules
;
523 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
524 const [sizeX
,sizeY
] = V
.size
;
525 for (let step
of adjacentSteps
)
527 const sq2
= [x
+2*step
[0],y
+2*step
[1]];
528 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
529 && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
)
531 const sq1
= [x
+step
[0],y
+step
[1]];
532 if (this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
533 && colors
.includes(this.getColor(sq1
[0],sq1
[1]))
534 && this.getPiece(sq1
[0],sq1
[1]) == V
.QUEEN
535 && !this.isImmobilized(sq1
))
544 updateVariables(move)
546 // Just update king(s) position(s)
547 const piece
= this.getPiece(move.start
.x
,move.start
.y
);
548 const c
= this.getColor(move.start
.x
,move.start
.y
);
549 if (piece
== VariantRules
.KING
&& move.appear
.length
> 0)
551 this.kingPos
[c
][0] = move.appear
[0].x
;
552 this.kingPos
[c
][1] = move.appear
[0].y
;
556 static get VALUES() { //TODO: totally experimental!
568 static get SEARCH_DEPTH() { return 2; } //TODO?
570 static GenRandInitFen()
572 let pieces
= { "w": new Array(8), "b": new Array(8) };
573 // Shuffle pieces on first and last rank
574 for (let c
of ["w","b"])
576 let positions
= _
.range(8);
577 // Get random squares for every piece, totally freely
579 let randIndex
= _
.random(7);
580 const bishop1Pos
= positions
[randIndex
];
581 positions
.splice(randIndex
, 1);
583 randIndex
= _
.random(6);
584 const bishop2Pos
= positions
[randIndex
];
585 positions
.splice(randIndex
, 1);
587 randIndex
= _
.random(5);
588 const knight1Pos
= positions
[randIndex
];
589 positions
.splice(randIndex
, 1);
591 randIndex
= _
.random(4);
592 const knight2Pos
= positions
[randIndex
];
593 positions
.splice(randIndex
, 1);
595 randIndex
= _
.random(3);
596 const queenPos
= positions
[randIndex
];
597 positions
.splice(randIndex
, 1);
599 randIndex
= _
.random(2);
600 const kingPos
= positions
[randIndex
];
601 positions
.splice(randIndex
, 1);
603 randIndex
= _
.random(1);
604 const rookPos
= positions
[randIndex
];
605 positions
.splice(randIndex
, 1);
606 const immobilizerPos
= positions
[0];
608 pieces
[c
][bishop1Pos
] = 'b';
609 pieces
[c
][bishop2Pos
] = 'b';
610 pieces
[c
][knight1Pos
] = 'n';
611 pieces
[c
][knight2Pos
] = 'n';
612 pieces
[c
][queenPos
] = 'q';
613 pieces
[c
][kingPos
] = 'k';
614 pieces
[c
][rookPos
] = 'r';
615 pieces
[c
][immobilizerPos
] = 'm';
617 return pieces
["b"].join("") +
618 "/pppppppp/8/8/8/8/PPPPPPPP/" +
619 pieces
["w"].join("").toUpperCase() +
620 " 0000"; //TODO: flags?!
625 return "0000"; //TODO: or "-" ?
630 const initialSquare
=
631 String
.fromCharCode(97 + move.start
.y
) + (VariantRules
.size
[0]-move.start
.x
);
633 String
.fromCharCode(97 + move.end
.y
) + (VariantRules
.size
[0]-move.end
.x
);
634 let notation
= undefined;
635 if (move.appear
[0].p
== VariantRules
.PAWN
)
637 // Pawn: generally ambiguous short notation, so we use full description
638 notation
= "P" + initialSquare
+ finalSquare
;
640 else if (move.appear
[0].p
== VariantRules
.KING
)
641 notation
= "K" + (move.vanish
.length
>1 ? "x" : "") + finalSquare
;
643 notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
644 if (move.vanish
.length
> 1 && move.appear
[0].p
!= VariantRules
.KING
)
645 notation
+= "X"; //capture mark (not describing what is captured...)