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 // In this case add potential suicide as a move "taking the immobilizer"
57 const piece
= this.getPiece(x
,y
);
58 const color
= this.getColor(x
,y
);
59 const oppCol
= this.getOppCol(color
);
60 const V
= VariantRules
;
61 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
62 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
.IMMOBILIZER
71 || (oppPiece
== V
.BISHOP
&& piece
== V
.IMMOBILIZER
))
75 vanish: [new PiPo({x:x
,y:y
,p:piece
,c:color
})],
82 switch (this.getPiece(x
,y
))
84 case VariantRules
.IMMOBILIZER:
85 return this.getPotentialImmobilizerMoves([x
,y
]);
87 return super.getPotentialMovesFrom([x
,y
]);
91 getSlideNJumpMoves([x
,y
], steps
, oneStep
)
93 const color
= this.getColor(x
,y
);
94 const piece
= this.getPiece(x
,y
);
96 const [sizeX
,sizeY
] = VariantRules
.size
;
98 for (let step
of steps
)
102 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
103 && this.board
[i
][j
] == VariantRules
.EMPTY
)
105 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
106 if (oneStep
!== undefined)
111 // Only king can take on occupied square:
112 if (piece
==VariantRules
.KING
&& i
>=0 && i
<sizeX
&& j
>=0
113 && j
<sizeY
&& this.canTake([x
,y
], [i
,j
]))
115 moves
.push(this.getBasicMove([x
,y
], [i
,j
]));
121 // Modify capturing moves among listed pawn moves
122 addPawnCaptures(moves
, byChameleon
)
124 const steps
= VariantRules
.steps
[VariantRules
.ROOK
];
125 const [sizeX
,sizeY
] = VariantRules
.size
;
126 const color
= this.turn
;
127 const oppCol
= this.getOppCol(color
);
129 if (!!byChameleon
&& m
.start
.x
!=m
.end
.x
&& m
.start
.y
!=m
.end
.y
)
130 return; //chameleon not moving as pawn
131 // Try capturing in every direction
132 for (let step
of steps
)
134 const sq2
= [m
.end
.x
+2*step
[0],m
.end
.y
+2*step
[1]];
135 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
136 && this.board
[sq2
[0]][sq2
[1]] != VariantRules
.EMPTY
137 && this.getColor(sq2
[0],sq2
[1]) == color
)
140 const sq1
= [m
.end
.x
+step
[0],m
.end
.y
+step
[1]];
141 if (this.board
[sq1
[0]][sq1
[1]] != VariantRules
.EMPTY
142 && this.getColor(sq1
[0],sq1
[1]) == oppCol
)
144 const piece1
= this.getPiece(sq1
[0],sq1
[1]);
145 if (!byChameleon
|| piece1
== VariantRules
.PAWN
)
147 m
.vanish
.push(new PiPo({
161 getPotentialPawnMoves([x
,y
])
163 let moves
= super.getPotentialRookMoves([x
,y
]);
164 this.addPawnCaptures(moves
);
168 addRookCaptures(moves
, byChameleon
)
170 const color
= this.turn
;
171 const oppCol
= this.getOppCol(color
);
172 const kp
= this.kingPos
[color
];
174 // Check piece-king rectangle (if any) corners for enemy pieces
175 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1])
176 return; //"flat rectangle"
177 const corner1
= [Math
.max(m
.end
.x
,kp
[0]), Math
.min(m
.end
.y
,kp
[1])];
178 const corner2
= [Math
.min(m
.end
.x
,kp
[0]), Math
.max(m
.end
.y
,kp
[1])];
179 for (let [i
,j
] of [corner1
,corner2
])
181 if (this.board
[i
][j
] != VariantRules
.EMPTY
&& this.getColor(i
,j
) == oppCol
)
183 const piece
= this.getPiece(i
,j
);
184 if (!byChameleon
|| piece
== VariantRules
.ROOK
)
186 m
.vanish
.push( new PiPo({
199 getPotentialRookMoves(sq
)
201 let moves
= super.getPotentialQueenMoves(sq
);
202 this.addRookCaptures(moves
);
207 getKnightCaptures(startSquare
, byChameleon
)
209 // Look in every direction for captures
210 const V
= VariantRules
;
211 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
212 const [sizeX
,sizeY
] = V
.size
;
213 const color
= this.turn
;
214 const oppCol
= this.getOppCol(color
);
216 const [x
,y
] = [startSquare
[0],startSquare
[1]];
217 const piece
= this.getPiece(x
,y
); //might be a chameleon!
219 for (let step
of steps
)
221 let [i
,j
] = [x
+step
[0], y
+step
[1]];
222 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]==V
.EMPTY
)
227 if (i
<0 || i
>=sizeX
|| j
<0 || j
>=sizeY
|| this.getColor(i
,j
)==color
228 || (!!byChameleon
&& this.getPiece(i
,j
)!=V
.KNIGHT
))
232 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
233 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
234 // add move until cur square; if cur is occupied then stop if !!byChameleon and
235 // the square not occupied by a leaper.
237 let cur
= [i
+step
[0],j
+step
[1]];
238 let vanished
= [ new PiPo({x:x
,y:y
,c:color
,p:piece
}) ];
239 while (cur
[0]>=0 && cur
[0]<sizeX
&& cur
[1]>=0 && cur
[1]<sizeY
)
241 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
)
243 const oppPiece
= this.getPiece(last
[0],last
[1]);
244 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
)
247 vanished
.push( new PiPo({x:last
[0],y:last
[1],c:oppCol
,p:oppPiece
}) );
249 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
)
251 if (this.getColor(cur
[0],cur
[1]) == color
252 || this.board
[last
[0]][last
[1]] != V
.EMPTY
) //TODO: redundant test
259 moves
.push(new Move({
260 appear: [ new PiPo({x:cur
[0],y:cur
[1],c:color
,p:piece
}) ],
261 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
263 end: {x:cur
[0],y:cur
[1]}
266 last
= [last
[0]+step
[0],last
[1]+step
[1]];
267 cur
= [cur
[0]+step
[0],cur
[1]+step
[1]];
274 getPotentialKnightMoves(sq
)
276 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
279 getPotentialBishopMoves(sq
)
281 let moves
= super.getPotentialQueenMoves(sq
)
282 .concat(this.getKnightCaptures(sq
,"asChameleon"));
283 // NOTE: no "addKingCaptures" because the king isn't captured
284 this.addPawnCaptures(moves
, "asChameleon");
285 this.addRookCaptures(moves
, "asChameleon");
286 this.addQueenCaptures(moves
, "asChameleon");
287 // Post-processing: merge similar moves, concatenating vanish arrays
288 let mergedMoves
= {};
289 const [sizeX
,sizeY
] = VariantRules
.size
;
291 const key
= m
.end
.x
+ sizeX
* m
.end
.y
;
292 if (!mergedMoves
[key
])
293 mergedMoves
[key
] = m
;
296 for (let i
=1; i
<m
.vanish
.length
; i
++)
297 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
300 // Finally return an array
302 Object
.keys(mergedMoves
).forEach(k
=> { moves
.push(mergedMoves
[k
]); });
307 addQueenCaptures(moves
, byChameleon
)
309 if (moves
.length
== 0)
311 const [x
,y
] = [moves
[0].start
.x
,moves
[0].start
.y
];
312 const V
= VariantRules
;
313 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
314 let capturingDirections
= [];
315 const color
= this.turn
;
316 const oppCol
= this.getOppCol(color
);
317 const [sizeX
,sizeY
] = V
.size
;
318 adjacentSteps
.forEach(step
=> {
319 const [i
,j
] = [x
+step
[0],y
+step
[1]];
320 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
321 && this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == oppCol
322 && (!byChameleon
|| this.getPiece(i
,j
) == V
.QUEEN
))
324 capturingDirections
.push(step
);
329 m
.end
.x
!=x
? (m
.end
.x
-x
)/Math
.abs(m
.end
.x
-x
) : 0,
330 m
.end
.y
!=y
? (m
.end
.y
-y
)/Math
.abs(m
.end
.y
-y
) : 0
332 // NOTE: includes() and even _.isEqual() functions fail...
333 // TODO: this test should be done only once per direction
334 if (capturingDirections
.some(dir
=>
335 { return (dir
[0]==-step
[0] && dir
[1]==-step
[1]); }))
337 const [i
,j
] = [x
-step
[0],y
-step
[1]];
338 m
.vanish
.push(new PiPo({
341 p:this.getPiece(i
,j
),
348 getPotentialQueenMoves(sq
)
350 let moves
= super.getPotentialQueenMoves(sq
);
351 this.addQueenCaptures(moves
);
355 getPotentialImmobilizerMoves(sq
)
357 // Immobilizer doesn't capture
358 return super.getPotentialQueenMoves(sq
);
361 getPotentialKingMoves(sq
)
363 const V
= VariantRules
;
364 return this.getSlideNJumpMoves(sq
,
365 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
368 // isAttacked() is OK because the immobilizer doesn't take
370 isAttackedByPawn([x
,y
], colors
)
372 // Square (x,y) must be surrounded by two enemy pieces,
373 // and one of them at least should be a pawn.
374 const dirs
= [ [1,0],[0,1],[1,1],[-1,1] ];
375 const [sizeX
,sizeY
] = VariantRules
.size
;
376 for (let dir
of dirs
)
378 const [i1
,j1
] = [x
-dir
[0],y
-dir
[1]]; //"before"
379 const [i2
,j2
] = [x
+dir
[0],y
+dir
[1]]; //"after"
380 if (i1
>=0 && i1
<sizeX
&& i2
>=0 && i2
<sizeX
381 && j1
>=0 && j1
<sizeY
&& j2
>=0 && j2
<sizeY
382 && this.board
[i1
][j1
]!=VariantRules
.EMPTY
383 && this.board
[i2
][j2
]!=VariantRules
.EMPTY
384 && colors
.includes(this.getColor(i1
,j1
))
385 && colors
.includes(this.getColor(i2
,j2
))
386 && [this.getPiece(i1
,j1
),this.getPiece(i2
,j2
)].includes(VariantRules
.PAWN
))
394 isAttackedByRook([x
,y
], colors
)
396 const [sizeX
,sizeY
] = VariantRules
.size
;
397 // King must be on same column and a rook on same row (or reverse)
398 if (x
== this.kingPos
[colors
[0]][0]) //using colors[0], only element in this case
400 // Look for enemy rook on this column
401 for (let i
=0; i
<sizeY
; i
++)
403 if (this.board
[x
][i
] != VariantRules
.EMPTY
404 && colors
.includes(this.getColor(x
,i
))
405 && this.getPiece(x
,i
) == VariantRules
.ROOK
)
411 else if (y
== this.kingPos
[colors
[0]][1])
413 // Look for enemy rook on this row
414 for (let i
=0; i
<sizeX
; i
++)
416 if (this.board
[i
][y
] != VariantRules
.EMPTY
417 && colors
.includes(this.getColor(i
,y
))
418 && this.getPiece(i
,y
) == VariantRules
.ROOK
)
427 isAttackedByKnight([x
,y
], colors
)
429 // Square (x,y) must be on same line as a knight,
430 // and there must be empty square(s) behind.
431 const V
= VariantRules
;
432 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
433 const [sizeX
,sizeY
] = V
.size
;
434 for (let step
of steps
)
436 const [i0
,j0
] = [x
+step
[0],y
+step
[1]];
437 if (i0
>=0 && i0
<sizeX
&& j0
>=0 && j0
<sizeY
&& this.board
[i0
][j0
] == V
.EMPTY
)
439 // Try in opposite direction:
440 let [i
,j
] = [x
-step
[0],y
-step
[1]];
441 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
] == V
.EMPTY
)
446 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& colors
.includes(this.getColor(i
,j
))
447 && this.getPiece(i
,j
) == V
.KNIGHT
)
456 isAttackedByBishop([x
,y
], colors
)
458 // We cheat a little here: since this function is used exclusively for king,
459 // it's enough to check the immediate surrounding of the square.
460 const V
= VariantRules
;
461 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
462 const [sizeX
,sizeY
] = V
.size
;
463 for (let step
of adjacentSteps
)
465 const [i
,j
] = [x
+step
[0],y
+step
[1]];
466 if (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
&& this.board
[i
][j
]!=V
.EMPTY
467 && colors
.includes(this.getColor(i
,j
)) && this.getPiece(i
,j
) == V
.BISHOP
)
475 isAttackedByQueen([x
,y
], colors
)
477 // Square (x,y) must be adjacent to a queen, and the queen must have
478 // some free space in the opposite direction from (x,y)
479 const V
= VariantRules
;
480 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
481 const [sizeX
,sizeY
] = V
.size
;
482 for (let step
of adjacentSteps
)
484 const sq2
= [x
+2*step
[0],y
+2*step
[1]];
485 if (sq2
[0]>=0 && sq2
[0]<sizeX
&& sq2
[1]>=0 && sq2
[1]<sizeY
486 && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
)
488 const sq1
= [x
+step
[0],y
+step
[1]];
489 if (this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
490 && colors
.includes(this.getColor(sq1
[0],sq1
[1]))
491 && this.getPiece(sq1
[0],sq1
[1]) == V
.QUEEN
)
500 updateVariables(move)
502 // Just update king position
503 const piece
= this.getPiece(move.start
.x
,move.start
.y
);
504 const c
= this.getColor(move.start
.x
,move.start
.y
);
505 if (piece
== VariantRules
.KING
&& move.appear
.length
> 0)
507 this.kingPos
[c
][0] = move.appear
[0].x
;
508 this.kingPos
[c
][1] = move.appear
[0].y
;
514 // No valid move: game is lost (stalemate is a win)
515 return this.turn
== "w" ? "0-1" : "1-0";
518 static get VALUES() { //TODO: totally experimental!
530 static get SEARCH_DEPTH() { return 2; } //TODO?
532 static GenRandInitFen()
534 let pieces
= { "w": new Array(8), "b": new Array(8) };
535 // Shuffle pieces on first and last rank
536 for (let c
of ["w","b"])
538 let positions
= _
.range(8);
539 // Get random squares for every piece, totally freely
541 let randIndex
= _
.random(7);
542 const bishop1Pos
= positions
[randIndex
];
543 positions
.splice(randIndex
, 1);
545 randIndex
= _
.random(6);
546 const bishop2Pos
= positions
[randIndex
];
547 positions
.splice(randIndex
, 1);
549 randIndex
= _
.random(5);
550 const knight1Pos
= positions
[randIndex
];
551 positions
.splice(randIndex
, 1);
553 randIndex
= _
.random(4);
554 const knight2Pos
= positions
[randIndex
];
555 positions
.splice(randIndex
, 1);
557 randIndex
= _
.random(3);
558 const queenPos
= positions
[randIndex
];
559 positions
.splice(randIndex
, 1);
561 randIndex
= _
.random(2);
562 const kingPos
= positions
[randIndex
];
563 positions
.splice(randIndex
, 1);
565 randIndex
= _
.random(1);
566 const rookPos
= positions
[randIndex
];
567 positions
.splice(randIndex
, 1);
568 const immobilizerPos
= positions
[0];
570 pieces
[c
][bishop1Pos
] = 'b';
571 pieces
[c
][bishop2Pos
] = 'b';
572 pieces
[c
][knight1Pos
] = 'n';
573 pieces
[c
][knight2Pos
] = 'n';
574 pieces
[c
][queenPos
] = 'q';
575 pieces
[c
][kingPos
] = 'k';
576 pieces
[c
][rookPos
] = 'r';
577 pieces
[c
][immobilizerPos
] = 'm';
579 return pieces
["b"].join("") +
580 "/pppppppp/8/8/8/8/PPPPPPPP/" +
581 pieces
["w"].join("").toUpperCase() +
582 " 0000"; //TODO: flags?!
587 return "0000"; //TODO: or "-" ?
592 if (move.appear
.length
== 0)
595 String
.fromCharCode(97 + move.start
.y
) + (VariantRules
.size
[0]-move.start
.x
);
596 return "^" + startSquare
; //suicide
598 return super.getNotation(move);