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 getPotentialMovesFrom([x
,y
])
55 // Pre-check: is thing on this square 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 const friendlyPiece
= this.getPiece(i2
,j2
);
82 if ([V
.BISHOP
,V
.IMMOBILIZER
].includes(friendlyPiece
))
86 return []; //immobilizer isn't neutralized
90 switch (this.getPiece(x
,y
))
92 case VariantRules
.IMMOBILIZER:
93 return this.getPotentialImmobilizerMoves([x
,y
]);
95 return super.getPotentialMovesFrom([x
,y
]);
99 getSlideNJumpMoves([x
,y
], steps
, oneStep
)
101 const color
= this.getColor(x
,y
);
102 const piece
= this.getPiece(x
,y
);
104 const [sizeX
,sizeY
] = VariantRules
.size
;
106 for (let step
of steps
)
110 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
111 && this.board
[i
][j
] == VariantRules
.EMPTY
)
113 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
114 if (oneStep
!== undefined)
119 // Only king can take on occupied square:
120 if (piece
==VariantRules
.KING
&& i
>=0 && i
<sizeX
&& j
>=0
121 && j
<sizeY
&& this.canTake([x
,y
], [i
,j
]))
123 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
129 // Modify capturing moves among listed pawn moves
130 addPawnCaptures(moves
, byChameleon
)
132 const steps
= VariantRules
.steps
[VariantRules
.ROOK
];
133 const [sizeX
,sizeY
] = VariantRules
.size
;
134 const color
= this.turn
;
135 const oppCol
= this.getOppCol(color
);
137 if (!!byChameleon
&& m
.start
.x
!=m
.end
.x
&& m
.start
.y
!=m
.end
.y
)
138 return; //chameleon not moving as pawn
139 // Try capturing in every direction
140 for (let step
of steps
)
142 const sq2
= [m
.end
.x
+2*step
[0],m
.end
.y
+2*step
[1]];
143 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
144 && this.board
[sq2
[0]][sq2
[1]] != VariantRules
.EMPTY
145 && this.getColor(sq2
[0],sq2
[1]) == color
)
148 const sq1
= [m
.end
.x
+step
[0],m
.end
.y
+step
[1]];
149 if (this.board
[sq1
[0]][sq1
[1]] != VariantRules
.EMPTY
150 && this.getColor(sq1
[0],sq1
[1]) == oppCol
)
152 const piece1
= this.getPiece(sq1
[0],sq1
[1]);
153 if (!byChameleon
|| piece1
== VariantRules
.PAWN
)
155 m
.vanish
.push(new PiPo({
169 getPotentialPawnMoves([x
,y
])
171 let moves
= super.getPotentialRookMoves([x
,y
]);
172 this.addPawnCaptures(moves
);
176 addRookCaptures(moves
, byChameleon
)
178 const color
= this.turn
;
179 const oppCol
= this.getOppCol(color
);
180 const kp
= this.kingPos
[color
];
182 // Check piece-king rectangle (if any) corners for enemy pieces
183 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1])
184 return; //"flat rectangle"
185 const corner1
= [Math
.max(m
.end
.x
,kp
[0]), Math
.min(m
.end
.y
,kp
[1])];
186 const corner2
= [Math
.min(m
.end
.x
,kp
[0]), Math
.max(m
.end
.y
,kp
[1])];
187 for (let [i
,j
] of [corner1
,corner2
])
189 if (this.board
[i
][j
] != VariantRules
.EMPTY
&& this.getColor(i
,j
) == oppCol
)
191 const piece
= this.getPiece(i
,j
);
192 if (!byChameleon
|| piece
== VariantRules
.ROOK
)
194 m
.vanish
.push( new PiPo({
207 getPotentialRookMoves(sq
)
209 let moves
= super.getPotentialQueenMoves(sq
);
210 this.addRookCaptures(moves
);
215 getKnightCaptures(startSquare
, byChameleon
)
217 // Look in every direction for captures
218 const V
= VariantRules
;
219 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
220 const [sizeX
,sizeY
] = V
.size
;
221 const color
= this.turn
;
222 const oppCol
= this.getOppCol(color
);
224 const [x
,y
] = [startSquare
[0],startSquare
[1]];
225 const piece
= this.getPiece(x
,y
); //might be a chameleon!
227 for (let step
of steps
)
229 let [i
,j
] = [x
+step
[0], y
+step
[1]];
230 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]==V
.EMPTY
)
235 if (i
<0 || i
>=sizeX
|| j
<0 || j
>=sizeY
|| this.getColor(i
,j
)==color
236 || (!!byChameleon
&& this.getPiece(i
,j
)!=V
.KNIGHT
))
240 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
241 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
242 // add move until cur square; if cur is occupied then stop if !!byChameleon and
243 // the square not occupied by a leaper.
245 let cur
= [i
+step
[0],j
+step
[1]];
246 let vanished
= [ new PiPo({x:x
,y:y
,c:color
,p:piece
}) ];
247 while (cur
[0]>=0 && cur
[0]<sizeX
&& cur
[1]>=0 && cur
[1]<sizeY
)
249 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
)
251 const oppPiece
= this.getPiece(last
[0],last
[1]);
252 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
)
255 vanished
.push( new PiPo({x:last
[0],y:last
[1],c:oppCol
,p:oppPiece
}) );
257 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
)
259 if (this.getColor(cur
[0],cur
[1]) == color
260 || this.board
[last
[0]][last
[1]] != V
.EMPTY
) //TODO: redundant test
267 moves
.push(new Move({
268 appear: [ new PiPo({x:cur
[0],y:cur
[1],c:color
,p:piece
}) ],
269 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
271 end: {x:cur
[0],y:cur
[1]}
274 last
= [last
[0]+step
[0],last
[1]+step
[1]];
275 cur
= [cur
[0]+step
[0],cur
[1]+step
[1]];
282 getPotentialKnightMoves(sq
)
284 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
287 getPotentialBishopMoves([x
,y
])
289 let moves
= super.getPotentialQueenMoves([x
,y
])
290 .concat(this.getKnightCaptures([x
,y
],"asChameleon"));
291 this.addPawnCaptures(moves
, "asChameleon");
292 this.addRookCaptures(moves
, "asChameleon");
293 this.addQueenCaptures(moves
, "asChameleon");
294 // Add king capture if it's within range
295 const oppKp
= this.kingPos
[this.getOppCol(this.turn
)];
296 if (Math
.abs(x
-oppKp
[0]) <= 1 && Math
.abs(y
-oppKp
[1]) <= 1)
297 moves
.push(this.getBasicMove([x
,y
],oppKp
));
298 // Post-processing: merge similar moves, concatenating vanish arrays
299 let mergedMoves
= {};
300 const [sizeX
,sizeY
] = VariantRules
.size
;
302 const key
= m
.end
.x
+ sizeX
* m
.end
.y
;
303 if (!mergedMoves
[key
])
304 mergedMoves
[key
] = m
;
307 for (let i
=1; i
<m
.vanish
.length
; i
++)
308 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
311 // Finally return an array
313 Object
.keys(mergedMoves
).forEach(k
=> { moves
.push(mergedMoves
[k
]); });
318 addQueenCaptures(moves
, byChameleon
)
320 if (moves
.length
== 0)
322 const [x
,y
] = [moves
[0].start
.x
,moves
[0].start
.y
];
323 const V
= VariantRules
;
324 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
325 let capturingDirections
= [];
326 const color
= this.turn
;
327 const oppCol
= this.getOppCol(color
);
328 const [sizeX
,sizeY
] = V
.size
;
329 adjacentSteps
.forEach(step
=> {
330 const [i
,j
] = [x
+step
[0],y
+step
[1]];
331 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
332 && this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == oppCol
333 && (!byChameleon
|| this.getPiece(i
,j
) == V
.QUEEN
))
335 capturingDirections
.push(step
);
340 m
.end
.x
!=x
? (m
.end
.x
-x
)/Math
.abs(m
.end
.x
-x
) : 0,
341 m
.end
.y
!=y
? (m
.end
.y
-y
)/Math
.abs(m
.end
.y
-y
) : 0
343 // NOTE: includes() and even _.isEqual() functions fail...
344 // TODO: this test should be done only once per direction
345 if (capturingDirections
.some(dir
=>
346 { return (dir
[0]==-step
[0] && dir
[1]==-step
[1]); }))
348 const [i
,j
] = [x
-step
[0],y
-step
[1]];
349 m
.vanish
.push(new PiPo({
352 p:this.getPiece(i
,j
),
359 getPotentialQueenMoves(sq
)
361 let moves
= super.getPotentialQueenMoves(sq
);
362 this.addQueenCaptures(moves
);
366 getPotentialImmobilizerMoves(sq
)
368 // Immobilizer doesn't capture
369 return super.getPotentialQueenMoves(sq
);
372 getPotentialKingMoves(sq
)
374 const V
= VariantRules
;
375 return this.getSlideNJumpMoves(sq
,
376 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
381 if (this.kingPos
[this.turn
][0] < 0)
383 return super.atLeastOneMove();
388 return false; //there is no check
391 getCheckSquares(move)
393 const c
= this.getOppCol(this.turn
); //opponent
394 const saveKingPos
= this.kingPos
[c
]; //king might be taken
396 // The only way to be "under check" is to have lost the king (thus game over)
397 let res
= this.kingPos
[c
][0] < 0
398 ? [ JSON
.parse(JSON
.stringify(saveKingPos
)) ]
404 updateVariables(move)
406 // Just update king(s) position(s)
407 const piece
= this.getPiece(move.start
.x
,move.start
.y
);
408 const c
= this.getColor(move.start
.x
,move.start
.y
);
409 if (piece
== VariantRules
.KING
&& move.appear
.length
> 0)
411 this.kingPos
[c
][0] = move.appear
[0].x
;
412 this.kingPos
[c
][1] = move.appear
[0].y
;
414 // Does this move takes opponent's king?
415 const oppCol
= this.getOppCol(c
);
416 for (let i
=1; i
<move.vanish
.length
; i
++)
418 if (move.vanish
[i
].p
== VariantRules
.KING
)
420 this.kingPos
[oppCol
] = [-1,-1];
426 unupdateVariables(move)
428 super.unupdateVariables(move);
429 const c
= this.getColor(move.start
.x
,move.start
.y
);
430 const oppCol
= this.getOppCol(c
);
431 if (this.kingPos
[oppCol
][0] < 0)
433 // Last move took opponent's king
434 for (let i
=1; i
<move.vanish
.length
; i
++)
436 const psq
= move.vanish
[i
];
439 this.kingPos
[oppCol
] = [psq
.x
, psq
.y
];
448 // Stalemate, or our king disappeared
449 return this.turn
== "w" ? "0-1" : "1-0";
452 static get VALUES() { //TODO: totally experimental!
464 static get SEARCH_DEPTH() { return 2; } //TODO?
466 static get THRESHOLD_MATE() {
467 return 500; //checkmates evals may be slightly below 1000
470 static GenRandInitFen()
472 let pieces
= { "w": new Array(8), "b": new Array(8) };
473 // Shuffle pieces on first and last rank
474 for (let c
of ["w","b"])
476 let positions
= _
.range(8);
477 // Get random squares for every piece, totally freely
479 let randIndex
= _
.random(7);
480 const bishop1Pos
= positions
[randIndex
];
481 positions
.splice(randIndex
, 1);
483 randIndex
= _
.random(6);
484 const bishop2Pos
= positions
[randIndex
];
485 positions
.splice(randIndex
, 1);
487 randIndex
= _
.random(5);
488 const knight1Pos
= positions
[randIndex
];
489 positions
.splice(randIndex
, 1);
491 randIndex
= _
.random(4);
492 const knight2Pos
= positions
[randIndex
];
493 positions
.splice(randIndex
, 1);
495 randIndex
= _
.random(3);
496 const queenPos
= positions
[randIndex
];
497 positions
.splice(randIndex
, 1);
499 randIndex
= _
.random(2);
500 const kingPos
= positions
[randIndex
];
501 positions
.splice(randIndex
, 1);
503 randIndex
= _
.random(1);
504 const rookPos
= positions
[randIndex
];
505 positions
.splice(randIndex
, 1);
506 const immobilizerPos
= positions
[0];
508 pieces
[c
][bishop1Pos
] = 'b';
509 pieces
[c
][bishop2Pos
] = 'b';
510 pieces
[c
][knight1Pos
] = 'n';
511 pieces
[c
][knight2Pos
] = 'n';
512 pieces
[c
][queenPos
] = 'q';
513 pieces
[c
][kingPos
] = 'k';
514 pieces
[c
][rookPos
] = 'r';
515 pieces
[c
][immobilizerPos
] = 'm';
517 return pieces
["b"].join("") +
518 "/pppppppp/8/8/8/8/PPPPPPPP/" +
519 pieces
["w"].join("").toUpperCase() +
520 " 0000"; //TODO: flags?!
525 return "0000"; //TODO: or "-" ?