1 class UltimaRules
extends ChessRules
3 static get HasFlags() { return false; }
5 static get HasEnpassant() { return false; }
9 if (b
[1] == "m") //'m' for Immobilizer (I is too similar to 1)
11 return b
; //usual piece
16 return ChessRules
.PIECES
.concat([V
.IMMOBILIZER
]);
19 // No castling, but checks, so keep track of kings
20 setOtherVariables(fen
)
22 this.kingPos
= {'w':[-1,-1], 'b':[-1,-1]};
23 const fenParts
= fen
.split(" ");
24 const position
= fenParts
[0].split("/");
25 for (let i
=0; i
<position
.length
; i
++)
28 for (let j
=0; j
<position
[i
].length
; j
++)
30 switch (position
[i
].charAt(j
))
33 this.kingPos
['b'] = [i
,k
];
36 this.kingPos
['w'] = [i
,k
];
39 let num
= parseInt(position
[i
].charAt(j
));
48 static get IMMOBILIZER() { return 'm'; }
49 // Although other pieces keep their names here for coding simplicity,
51 // - a "rook" is a coordinator, capturing by coordinating with the king
52 // - a "knight" is a long-leaper, capturing as in draughts
53 // - a "bishop" is a chameleon, capturing as its prey
54 // - a "queen" is a withdrawer, capturing by moving away from pieces
56 // Is piece on square (x,y) immobilized?
59 const piece
= this.getPiece(x
,y
);
60 const color
= this.getColor(x
,y
);
61 const oppCol
= this.getOppCol(color
);
62 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
64 for (let step
of adjacentSteps
)
66 const [i
,j
] = [x
+step
[0],y
+step
[1]];
67 if (V
.OnBoard(i
,j
) && this.board
[i
][j
] != V
.EMPTY
68 && this.getColor(i
,j
) == oppCol
)
70 const oppPiece
= this.getPiece(i
,j
);
71 if (oppPiece
== V
.IMMOBILIZER
)
73 // Moving is impossible only if this immobilizer is not neutralized
74 for (let step2
of adjacentSteps
)
76 const [i2
,j2
] = [i
+step2
[0],j
+step2
[1]];
77 if (i2
== x
&& j2
== y
)
78 continue; //skip initial piece!
79 if (V
.OnBoard(i2
,j2
) && this.board
[i2
][j2
] != V
.EMPTY
80 && this.getColor(i2
,j2
) == color
)
82 if ([V
.BISHOP
,V
.IMMOBILIZER
].includes(this.getPiece(i2
,j2
)))
86 return true; //immobilizer isn't neutralized
88 // Chameleons can't be immobilized twice, because there is only one immobilizer
89 if (oppPiece
== V
.BISHOP
&& piece
== V
.IMMOBILIZER
)
96 getPotentialMovesFrom([x
,y
])
98 // Pre-check: is thing on this square immobilized?
99 if (this.isImmobilized([x
,y
]))
101 switch (this.getPiece(x
,y
))
104 return this.getPotentialImmobilizerMoves([x
,y
]);
106 return super.getPotentialMovesFrom([x
,y
]);
110 getSlideNJumpMoves([x
,y
], steps
, oneStep
)
112 const color
= this.getColor(x
,y
);
113 const piece
= this.getPiece(x
,y
);
116 for (let step
of steps
)
120 while (V
.OnBoard(i
,j
) && this.board
[i
][j
] == V
.EMPTY
)
122 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
123 if (oneStep
!== undefined)
128 // Only king can take on occupied square:
129 if (piece
==V
.KING
&& V
.OnBoard(i
,j
) && this.canTake([x
,y
], [i
,j
]))
130 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
135 // Modify capturing moves among listed pawn moves
136 addPawnCaptures(moves
, byChameleon
)
138 const steps
= V
.steps
[V
.ROOK
];
139 const color
= this.turn
;
140 const oppCol
= this.getOppCol(color
);
142 if (!!byChameleon
&& m
.start
.x
!=m
.end
.x
&& m
.start
.y
!=m
.end
.y
)
143 return; //chameleon not moving as pawn
144 // Try capturing in every direction
145 for (let step
of steps
)
147 const sq2
= [m
.end
.x
+2*step
[0],m
.end
.y
+2*step
[1]];
148 if (V
.OnBoard(sq2
[0],sq2
[1]) && this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
149 && this.getColor(sq2
[0],sq2
[1]) == color
)
152 const sq1
= [m
.end
.x
+step
[0],m
.end
.y
+step
[1]];
153 if (this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
154 && this.getColor(sq1
[0],sq1
[1]) == oppCol
)
156 const piece1
= this.getPiece(sq1
[0],sq1
[1]);
157 if (!byChameleon
|| piece1
== V
.PAWN
)
159 m
.vanish
.push(new PiPo({
173 getPotentialPawnMoves([x
,y
])
175 let moves
= super.getPotentialRookMoves([x
,y
]);
176 this.addPawnCaptures(moves
);
180 addRookCaptures(moves
, byChameleon
)
182 const color
= this.turn
;
183 const oppCol
= this.getOppCol(color
);
184 const kp
= this.kingPos
[color
];
186 // Check piece-king rectangle (if any) corners for enemy pieces
187 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1])
188 return; //"flat rectangle"
189 const corner1
= [m
.end
.x
, kp
[1]];
190 const corner2
= [kp
[0], m
.end
.y
];
191 for (let [i
,j
] of [corner1
,corner2
])
193 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == oppCol
)
195 const piece
= this.getPiece(i
,j
);
196 if (!byChameleon
|| piece
== V
.ROOK
)
198 m
.vanish
.push( new PiPo({
211 getPotentialRookMoves(sq
)
213 let moves
= super.getPotentialQueenMoves(sq
);
214 this.addRookCaptures(moves
);
219 getKnightCaptures(startSquare
, byChameleon
)
221 // Look in every direction for captures
222 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
223 const color
= this.turn
;
224 const oppCol
= this.getOppCol(color
);
226 const [x
,y
] = [startSquare
[0],startSquare
[1]];
227 const piece
= this.getPiece(x
,y
); //might be a chameleon!
229 for (let step
of steps
)
231 let [i
,j
] = [x
+step
[0], y
+step
[1]];
232 while (V
.OnBoard(i
,j
) && this.board
[i
][j
]==V
.EMPTY
)
237 if (!V
.OnBoard(i
,j
) || this.getColor(i
,j
)==color
238 || (!!byChameleon
&& this.getPiece(i
,j
)!=V
.KNIGHT
))
242 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
243 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
244 // add move until cur square; if cur is occupied then stop if !!byChameleon and
245 // the square not occupied by a leaper.
247 let cur
= [i
+step
[0],j
+step
[1]];
248 let vanished
= [ new PiPo({x:x
,y:y
,c:color
,p:piece
}) ];
249 while (V
.OnBoard(cur
[0],cur
[1]))
251 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
)
253 const oppPiece
= this.getPiece(last
[0],last
[1]);
254 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
)
257 vanished
.push( new PiPo({x:last
[0],y:last
[1],c:oppCol
,p:oppPiece
}) );
259 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
)
261 if (this.getColor(cur
[0],cur
[1]) == color
262 || this.board
[last
[0]][last
[1]] != V
.EMPTY
) //TODO: redundant test
269 moves
.push(new Move({
270 appear: [ new PiPo({x:cur
[0],y:cur
[1],c:color
,p:piece
}) ],
271 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
273 end: {x:cur
[0],y:cur
[1]}
276 last
= [last
[0]+step
[0],last
[1]+step
[1]];
277 cur
= [cur
[0]+step
[0],cur
[1]+step
[1]];
284 getPotentialKnightMoves(sq
)
286 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
289 getPotentialBishopMoves([x
,y
])
291 let moves
= super.getPotentialQueenMoves([x
,y
])
292 .concat(this.getKnightCaptures([x
,y
],"asChameleon"));
293 // No "king capture" because king cannot remain under check
294 this.addPawnCaptures(moves
, "asChameleon");
295 this.addRookCaptures(moves
, "asChameleon");
296 this.addQueenCaptures(moves
, "asChameleon");
297 // Post-processing: merge similar moves, concatenating vanish arrays
298 let mergedMoves
= {};
300 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
301 if (!mergedMoves
[key
])
302 mergedMoves
[key
] = m
;
305 for (let i
=1; i
<m
.vanish
.length
; i
++)
306 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
309 // Finally return an array
311 Object
.keys(mergedMoves
).forEach(k
=> { moves
.push(mergedMoves
[k
]); });
316 addQueenCaptures(moves
, byChameleon
)
318 if (moves
.length
== 0)
320 const [x
,y
] = [moves
[0].start
.x
,moves
[0].start
.y
];
321 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
322 let capturingDirections
= [];
323 const color
= this.turn
;
324 const oppCol
= this.getOppCol(color
);
325 adjacentSteps
.forEach(step
=> {
326 const [i
,j
] = [x
+step
[0],y
+step
[1]];
327 if (V
.OnBoard(i
,j
) && this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == oppCol
328 && (!byChameleon
|| this.getPiece(i
,j
) == V
.QUEEN
))
330 capturingDirections
.push(step
);
335 m
.end
.x
!=x
? (m
.end
.x
-x
)/Math
.abs(m
.end
.x
-x
) : 0,
336 m
.end
.y
!=y
? (m
.end
.y
-y
)/Math
.abs(m
.end
.y
-y
) : 0
338 // NOTE: includes() and even _.isEqual() functions fail...
339 // TODO: this test should be done only once per direction
340 if (capturingDirections
.some(dir
=>
341 { return (dir
[0]==-step
[0] && dir
[1]==-step
[1]); }))
343 const [i
,j
] = [x
-step
[0],y
-step
[1]];
344 m
.vanish
.push(new PiPo({
347 p:this.getPiece(i
,j
),
354 getPotentialQueenMoves(sq
)
356 let moves
= super.getPotentialQueenMoves(sq
);
357 this.addQueenCaptures(moves
);
361 getPotentialImmobilizerMoves(sq
)
363 // Immobilizer doesn't capture
364 return super.getPotentialQueenMoves(sq
);
367 getPotentialKingMoves(sq
)
369 return this.getSlideNJumpMoves(sq
,
370 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
373 // isAttacked() is OK because the immobilizer doesn't take
375 isAttackedByPawn([x
,y
], colors
)
377 // Square (x,y) must be surroundable by two enemy pieces,
378 // and one of them at least should be a pawn (moving).
379 const dirs
= [ [1,0],[0,1] ];
380 const steps
= V
.steps
[V
.ROOK
];
381 for (let dir
of dirs
)
383 const [i1
,j1
] = [x
-dir
[0],y
-dir
[1]]; //"before"
384 const [i2
,j2
] = [x
+dir
[0],y
+dir
[1]]; //"after"
385 if (V
.OnBoard(i1
,j1
) && V
.OnBoard(i2
,j2
))
387 if ((this.board
[i1
][j1
]!=V
.EMPTY
&& colors
.includes(this.getColor(i1
,j1
))
388 && this.board
[i2
][j2
]==V
.EMPTY
)
390 (this.board
[i2
][j2
]!=V
.EMPTY
&& colors
.includes(this.getColor(i2
,j2
))
391 && this.board
[i1
][j1
]==V
.EMPTY
))
393 // Search a movable enemy pawn landing on the empty square
394 for (let step
of steps
)
396 let [ii
,jj
] = (this.board
[i1
][j1
]==V
.EMPTY
? [i1
,j1
] : [i2
,j2
]);
397 let [i3
,j3
] = [ii
+step
[0],jj
+step
[1]];
398 while (V
.OnBoard(i3
,j3
) && this.board
[i3
][j3
]==V
.EMPTY
)
403 if (V
.OnBoard(i3
,j3
) && colors
.includes(this.getColor(i3
,j3
))
404 && this.getPiece(i3
,j3
) == V
.PAWN
&& !this.isImmobilized([i3
,j3
]))
415 isAttackedByRook([x
,y
], colors
)
417 // King must be on same column or row,
418 // and a rook should be able to reach a capturing square
419 // colors contains only one element, giving the oppCol and thus king position
420 const sameRow
= (x
== this.kingPos
[colors
[0]][0]);
421 const sameColumn
= (y
== this.kingPos
[colors
[0]][1]);
422 if (sameRow
|| sameColumn
)
424 // Look for the enemy rook (maximum 1)
425 for (let i
=0; i
<V
.size
.x
; i
++)
427 for (let j
=0; j
<V
.size
.y
; j
++)
429 if (this.board
[i
][j
] != V
.EMPTY
&& colors
.includes(this.getColor(i
,j
))
430 && this.getPiece(i
,j
) == V
.ROOK
)
432 if (this.isImmobilized([i
,j
]))
433 return false; //because only one rook
434 // Can it reach a capturing square?
435 // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
436 const moves
= this.getPotentialMovesFrom([i
,j
]);
437 for (let move of moves
)
439 if (sameRow
&& move.end
.y
== y
|| sameColumn
&& move.end
.x
== x
)
449 isAttackedByKnight([x
,y
], colors
)
451 // Square (x,y) must be on same line as a knight,
452 // and there must be empty square(s) behind.
453 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
455 for (let step
of steps
)
457 const [i0
,j0
] = [x
+step
[0],y
+step
[1]];
458 if (V
.OnBoard(i0
,j0
) && this.board
[i0
][j0
] == V
.EMPTY
)
460 // Try in opposite direction:
461 let [i
,j
] = [x
-step
[0],y
-step
[1]];
462 while (V
.OnBoard(i
,j
))
464 while (V
.OnBoard(i
,j
) && this.board
[i
][j
] == V
.EMPTY
)
471 if (colors
.includes(this.getColor(i
,j
)))
473 if (this.getPiece(i
,j
) == V
.KNIGHT
&& !this.isImmobilized([i
,j
]))
477 // [else] Our color, could be captured *if there was an empty space*
478 if (this.board
[i
+step
[0]][j
+step
[1]] != V
.EMPTY
)
489 isAttackedByBishop([x
,y
], colors
)
491 // We cheat a little here: since this function is used exclusively for king,
492 // it's enough to check the immediate surrounding of the square.
493 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
494 for (let step
of adjacentSteps
)
496 const [i
,j
] = [x
+step
[0],y
+step
[1]];
497 if (V
.OnBoard(i
,j
) && this.board
[i
][j
]!=V
.EMPTY
498 && colors
.includes(this.getColor(i
,j
)) && this.getPiece(i
,j
) == V
.BISHOP
)
500 return true; //bishops are never immobilized
506 isAttackedByQueen([x
,y
], colors
)
508 // Square (x,y) must be adjacent to a queen, and the queen must have
509 // some free space in the opposite direction from (x,y)
510 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
511 for (let step
of adjacentSteps
)
513 const sq2
= [x
+2*step
[0],y
+2*step
[1]];
514 if (V
.OnBoard(sq2
[0],sq2
[1]) && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
)
516 const sq1
= [x
+step
[0],y
+step
[1]];
517 if (this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
518 && colors
.includes(this.getColor(sq1
[0],sq1
[1]))
519 && this.getPiece(sq1
[0],sq1
[1]) == V
.QUEEN
520 && !this.isImmobilized(sq1
))
529 updateVariables(move)
531 // Just update king(s) position(s)
532 const piece
= move.vanish
[0].p
;
533 const c
= move.vanish
[0].c
;
534 if (piece
== V
.KING
&& move.appear
.length
> 0)
536 this.kingPos
[c
][0] = move.appear
[0].x
;
537 this.kingPos
[c
][1] = move.appear
[0].y
;
543 // TODO: totally experimental!
555 static get SEARCH_DEPTH() { return 2; } //TODO?
557 static GenRandInitFen()
559 let pieces
= { "w": new Array(8), "b": new Array(8) };
560 // Shuffle pieces on first and last rank
561 for (let c
of ["w","b"])
563 let positions
= _
.range(8);
564 // Get random squares for every piece, totally freely
566 let randIndex
= _
.random(7);
567 const bishop1Pos
= positions
[randIndex
];
568 positions
.splice(randIndex
, 1);
570 randIndex
= _
.random(6);
571 const bishop2Pos
= positions
[randIndex
];
572 positions
.splice(randIndex
, 1);
574 randIndex
= _
.random(5);
575 const knight1Pos
= positions
[randIndex
];
576 positions
.splice(randIndex
, 1);
578 randIndex
= _
.random(4);
579 const knight2Pos
= positions
[randIndex
];
580 positions
.splice(randIndex
, 1);
582 randIndex
= _
.random(3);
583 const queenPos
= positions
[randIndex
];
584 positions
.splice(randIndex
, 1);
586 randIndex
= _
.random(2);
587 const kingPos
= positions
[randIndex
];
588 positions
.splice(randIndex
, 1);
590 randIndex
= _
.random(1);
591 const rookPos
= positions
[randIndex
];
592 positions
.splice(randIndex
, 1);
593 const immobilizerPos
= positions
[0];
595 pieces
[c
][bishop1Pos
] = 'b';
596 pieces
[c
][bishop2Pos
] = 'b';
597 pieces
[c
][knight1Pos
] = 'n';
598 pieces
[c
][knight2Pos
] = 'n';
599 pieces
[c
][queenPos
] = 'q';
600 pieces
[c
][kingPos
] = 'k';
601 pieces
[c
][rookPos
] = 'r';
602 pieces
[c
][immobilizerPos
] = 'm';
604 return pieces
["b"].join("") +
605 "/pppppppp/8/8/8/8/PPPPPPPP/" +
606 pieces
["w"].join("").toUpperCase() +
612 const initialSquare
= V
.CoordsToSquare(move.start
);
613 const finalSquare
= V
.CoordsToSquare(move.end
);
614 let notation
= undefined;
615 if (move.appear
[0].p
== V
.PAWN
)
617 // Pawn: generally ambiguous short notation, so we use full description
618 notation
= "P" + initialSquare
+ finalSquare
;
620 else if (move.appear
[0].p
== V
.KING
)
621 notation
= "K" + (move.vanish
.length
>1 ? "x" : "") + finalSquare
;
623 notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
624 if (move.vanish
.length
> 1 && move.appear
[0].p
!= V
.KING
)
625 notation
+= "X"; //capture mark (not describing what is captured...)
630 const VariantRules
= UltimaRules
;