1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 export const VariantRules
= class BaroqueRules
extends ChessRules
{
6 static get HasFlags() {
10 static get HasCastle() {
14 static get HasEnpassant() {
19 return ChessRules
.PIECES
.concat([V
.IMMOBILIZER
]);
24 //'m' for Immobilizer (I is too similar to 1)
25 return "Baroque/" + b
;
26 return b
; //usual piece
29 // No castling, but checks, so keep track of kings
30 setOtherVariables(fen
) {
31 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
32 const fenParts
= fen
.split(" ");
33 const position
= fenParts
[0].split("/");
34 for (let i
= 0; i
< position
.length
; i
++) {
36 for (let j
= 0; j
< position
[i
].length
; j
++) {
37 switch (position
[i
].charAt(j
)) {
39 this.kingPos
["b"] = [i
, k
];
42 this.kingPos
["w"] = [i
, k
];
45 const num
= parseInt(position
[i
].charAt(j
));
46 if (!isNaN(num
)) k
+= num
- 1;
54 static get IMMOBILIZER() {
57 // Although other pieces keep their names here for coding simplicity,
59 // - a "rook" is a coordinator, capturing by coordinating with the king
60 // - a "knight" is a long-leaper, capturing as in draughts
61 // - a "bishop" is a chameleon, capturing as its prey
62 // - a "queen" is a withdrawer, capturing by moving away from pieces
64 // Is piece on square (x,y) immobilized?
65 isImmobilized([x
, y
]) {
66 const piece
= this.getPiece(x
, y
);
67 const color
= this.getColor(x
, y
);
68 const oppCol
= V
.GetOppCol(color
);
69 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
70 for (let step
of adjacentSteps
) {
71 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
74 this.board
[i
][j
] != V
.EMPTY
&&
75 this.getColor(i
, j
) == oppCol
77 const oppPiece
= this.getPiece(i
, j
);
78 if (oppPiece
== V
.IMMOBILIZER
) {
79 // Moving is possible only if this immobilizer is neutralized
80 for (let step2
of adjacentSteps
) {
81 const [i2
, j2
] = [i
+ step2
[0], j
+ step2
[1]];
82 if (i2
== x
&& j2
== y
) continue; //skip initial piece!
85 this.board
[i2
][j2
] != V
.EMPTY
&&
86 this.getColor(i2
, j2
) == color
88 if ([V
.BISHOP
, V
.IMMOBILIZER
].includes(this.getPiece(i2
, j2
)))
92 return true; //immobilizer isn't neutralized
94 // Chameleons can't be immobilized twice, because there is only one immobilizer
95 if (oppPiece
== V
.BISHOP
&& piece
== V
.IMMOBILIZER
) return true;
101 getPotentialMovesFrom([x
, y
]) {
102 // Pre-check: is thing on this square immobilized?
103 if (this.isImmobilized([x
, y
])) return [];
104 switch (this.getPiece(x
, y
)) {
106 return this.getPotentialImmobilizerMoves([x
, y
]);
108 return super.getPotentialMovesFrom([x
, y
]);
112 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
113 const piece
= this.getPiece(x
, y
);
115 outerLoop: for (let step
of steps
) {
118 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
119 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
120 if (oneStep
!== undefined) continue outerLoop
;
124 // Only king can take on occupied square:
125 if (piece
== V
.KING
&& V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
126 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
131 // Modify capturing moves among listed pawn moves
132 addPawnCaptures(moves
, byChameleon
) {
133 const steps
= V
.steps
[V
.ROOK
];
134 const color
= this.turn
;
135 const oppCol
= V
.GetOppCol(color
);
137 if (!!byChameleon
&& m
.start
.x
!= m
.end
.x
&& m
.start
.y
!= m
.end
.y
) return; //chameleon not moving as pawn
138 // Try capturing in every direction
139 for (let step
of steps
) {
140 const sq2
= [m
.end
.x
+ 2 * step
[0], m
.end
.y
+ 2 * step
[1]];
142 V
.OnBoard(sq2
[0], sq2
[1]) &&
143 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
144 this.getColor(sq2
[0], sq2
[1]) == color
147 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
149 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
150 this.getColor(sq1
[0], sq1
[1]) == oppCol
152 const piece1
= this.getPiece(sq1
[0], sq1
[1]);
153 if (!byChameleon
|| piece1
== V
.PAWN
) {
170 getPotentialPawnMoves([x
, y
]) {
171 let moves
= super.getPotentialRookMoves([x
, y
]);
172 this.addPawnCaptures(moves
);
176 addRookCaptures(moves
, byChameleon
) {
177 const color
= this.turn
;
178 const oppCol
= V
.GetOppCol(color
);
179 const kp
= this.kingPos
[color
];
181 // Check piece-king rectangle (if any) corners for enemy pieces
182 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1]) return; //"flat rectangle"
183 const corner1
= [m
.end
.x
, kp
[1]];
184 const corner2
= [kp
[0], m
.end
.y
];
185 for (let [i
, j
] of [corner1
, corner2
]) {
186 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == oppCol
) {
187 const piece
= this.getPiece(i
, j
);
188 if (!byChameleon
|| piece
== V
.ROOK
) {
204 getPotentialRookMoves(sq
) {
205 let moves
= super.getPotentialQueenMoves(sq
);
206 this.addRookCaptures(moves
);
210 getKnightCaptures(startSquare
, byChameleon
) {
211 // Look in every direction for captures
212 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
213 const color
= this.turn
;
214 const oppCol
= V
.GetOppCol(color
);
216 const [x
, y
] = [startSquare
[0], startSquare
[1]];
217 const piece
= this.getPiece(x
, y
); //might be a chameleon!
218 outerLoop: for (let step
of steps
) {
219 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
220 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
226 this.getColor(i
, j
) == color
||
227 (!!byChameleon
&& this.getPiece(i
, j
) != V
.KNIGHT
)
231 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
232 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
233 // add move until cur square; if cur is occupied then stop if !!byChameleon and
234 // the square not occupied by a leaper.
236 let cur
= [i
+ step
[0], j
+ step
[1]];
237 let vanished
= [new PiPo({ x: x
, y: y
, c: color
, p: piece
})];
238 while (V
.OnBoard(cur
[0], cur
[1])) {
239 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
) {
240 const oppPiece
= this.getPiece(last
[0], last
[1]);
241 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
) continue outerLoop
;
244 new PiPo({ x: last
[0], y: last
[1], c: oppCol
, p: oppPiece
})
247 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
) {
249 this.getColor(cur
[0], cur
[1]) == color
||
250 this.board
[last
[0]][last
[1]] != V
.EMPTY
252 //TODO: redundant test
258 appear: [new PiPo({ x: cur
[0], y: cur
[1], c: color
, p: piece
})],
259 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
260 start: { x: x
, y: y
},
261 end: { x: cur
[0], y: cur
[1] }
265 last
= [last
[0] + step
[0], last
[1] + step
[1]];
266 cur
= [cur
[0] + step
[0], cur
[1] + step
[1]];
273 getPotentialKnightMoves(sq
) {
274 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
278 getPotentialBishopMoves([x
, y
]) {
280 .getPotentialQueenMoves([x
, y
])
281 .concat(this.getKnightCaptures([x
, y
], "asChameleon"));
282 // No "king capture" because king cannot remain under check
283 this.addPawnCaptures(moves
, "asChameleon");
284 this.addRookCaptures(moves
, "asChameleon");
285 this.addQueenCaptures(moves
, "asChameleon");
286 // Post-processing: merge similar moves, concatenating vanish arrays
287 let mergedMoves
= {};
289 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
290 if (!mergedMoves
[key
]) mergedMoves
[key
] = m
;
292 for (let i
= 1; i
< m
.vanish
.length
; i
++)
293 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
296 // Finally return an array
298 Object
.keys(mergedMoves
).forEach(k
=> {
299 moves
.push(mergedMoves
[k
]);
304 addQueenCaptures(moves
, byChameleon
) {
305 if (moves
.length
== 0) return;
306 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
307 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
308 let capturingDirections
= [];
309 const color
= this.turn
;
310 const oppCol
= V
.GetOppCol(color
);
311 adjacentSteps
.forEach(step
=> {
312 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
315 this.board
[i
][j
] != V
.EMPTY
&&
316 this.getColor(i
, j
) == oppCol
&&
317 (!byChameleon
|| this.getPiece(i
, j
) == V
.QUEEN
)
319 capturingDirections
.push(step
);
324 m
.end
.x
!= x
? (m
.end
.x
- x
) / Math
.abs(m
.end
.x
- x
) : 0,
325 m
.end
.y
!= y
? (m
.end
.y
- y
) / Math
.abs(m
.end
.y
- y
) : 0
327 // NOTE: includes() and even _.isEqual() functions fail...
328 // TODO: this test should be done only once per direction
330 capturingDirections
.some(dir
=> {
331 return dir
[0] == -step
[0] && dir
[1] == -step
[1];
334 const [i
, j
] = [x
- step
[0], y
- step
[1]];
339 p: this.getPiece(i
, j
),
348 getPotentialQueenMoves(sq
) {
349 let moves
= super.getPotentialQueenMoves(sq
);
350 this.addQueenCaptures(moves
);
354 getPotentialImmobilizerMoves(sq
) {
355 // Immobilizer doesn't capture
356 return super.getPotentialQueenMoves(sq
);
359 // isAttacked() is OK because the immobilizer doesn't take
361 isAttackedByPawn([x
, y
], color
) {
362 // Square (x,y) must be surroundable by two enemy pieces,
363 // and one of them at least should be a pawn (moving).
368 const steps
= V
.steps
[V
.ROOK
];
369 for (let dir
of dirs
) {
370 const [i1
, j1
] = [x
- dir
[0], y
- dir
[1]]; //"before"
371 const [i2
, j2
] = [x
+ dir
[0], y
+ dir
[1]]; //"after"
372 if (V
.OnBoard(i1
, j1
) && V
.OnBoard(i2
, j2
)) {
375 this.board
[i1
][j1
] != V
.EMPTY
&&
376 this.getColor(i1
, j1
) == color
&&
377 this.board
[i2
][j2
] == V
.EMPTY
381 this.board
[i2
][j2
] != V
.EMPTY
&&
382 this.getColor(i2
, j2
) == color
&&
383 this.board
[i1
][j1
] == V
.EMPTY
386 // Search a movable enemy pawn landing on the empty square
387 for (let step
of steps
) {
388 let [ii
, jj
] = this.board
[i1
][j1
] == V
.EMPTY
? [i1
, j1
] : [i2
, j2
];
389 let [i3
, j3
] = [ii
+ step
[0], jj
+ step
[1]];
390 while (V
.OnBoard(i3
, j3
) && this.board
[i3
][j3
] == V
.EMPTY
) {
396 this.getColor(i3
, j3
) == color
&&
397 this.getPiece(i3
, j3
) == V
.PAWN
&&
398 !this.isImmobilized([i3
, j3
])
409 isAttackedByRook([x
, y
], color
) {
410 // King must be on same column or row,
411 // and a rook should be able to reach a capturing square
412 const sameRow
= x
== this.kingPos
[color
][0];
413 const sameColumn
= y
== this.kingPos
[color
][1];
414 if (sameRow
|| sameColumn
) {
415 // Look for the enemy rook (maximum 1)
416 for (let i
= 0; i
< V
.size
.x
; i
++) {
417 for (let j
= 0; j
< V
.size
.y
; j
++) {
419 this.board
[i
][j
] != V
.EMPTY
&&
420 this.getColor(i
, j
) == color
&&
421 this.getPiece(i
, j
) == V
.ROOK
423 if (this.isImmobilized([i
, j
])) return false; //because only one rook
424 // Can it reach a capturing square?
425 // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
426 const moves
= this.getPotentialMovesFrom([i
, j
]);
427 for (let move of moves
) {
429 (sameRow
&& move.end
.y
== y
) ||
430 (sameColumn
&& move.end
.x
== x
)
441 isAttackedByKnight([x
, y
], color
) {
442 // Square (x,y) must be on same line as a knight,
443 // and there must be empty square(s) behind.
444 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
445 outerLoop: for (let step
of steps
) {
446 const [i0
, j0
] = [x
+ step
[0], y
+ step
[1]];
447 if (V
.OnBoard(i0
, j0
) && this.board
[i0
][j0
] == V
.EMPTY
) {
448 // Try in opposite direction:
449 let [i
, j
] = [x
- step
[0], y
- step
[1]];
450 while (V
.OnBoard(i
, j
)) {
451 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
455 if (V
.OnBoard(i
, j
)) {
456 if (this.getColor(i
, j
) == color
) {
458 this.getPiece(i
, j
) == V
.KNIGHT
&&
459 !this.isImmobilized([i
, j
])
464 // [else] Our color, could be captured *if there was an empty space*
465 if (this.board
[i
+ step
[0]][j
+ step
[1]] != V
.EMPTY
)
476 isAttackedByBishop([x
, y
], color
) {
477 // We cheat a little here: since this function is used exclusively for
478 // the king, it's enough to check the immediate surrounding of the square.
479 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
480 for (let step
of adjacentSteps
) {
481 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
484 this.board
[i
][j
] != V
.EMPTY
&&
485 this.getColor(i
, j
) == color
&&
486 this.getPiece(i
, j
) == V
.BISHOP
488 return true; //bishops are never immobilized
494 isAttackedByQueen([x
, y
], color
) {
495 // Square (x,y) must be adjacent to a queen, and the queen must have
496 // some free space in the opposite direction from (x,y)
497 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
498 for (let step
of adjacentSteps
) {
499 const sq2
= [x
+ 2 * step
[0], y
+ 2 * step
[1]];
500 if (V
.OnBoard(sq2
[0], sq2
[1]) && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
) {
501 const sq1
= [x
+ step
[0], y
+ step
[1]];
503 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
504 this.getColor(sq1
[0], sq1
[1]) == color
&&
505 this.getPiece(sq1
[0], sq1
[1]) == V
.QUEEN
&&
506 !this.isImmobilized(sq1
)
515 isAttackedByKing([x
, y
], color
) {
516 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
517 for (let step
of steps
) {
518 let rx
= x
+ step
[0],
522 this.getPiece(rx
, ry
) === V
.KING
&&
523 this.getColor(rx
, ry
) == color
&&
524 !this.isImmobilized([rx
, ry
])
532 static get VALUES() {
544 static get SEARCH_DEPTH() {
548 static GenRandInitFen(randomness
) {
551 return "rnbqkbnrm/pppppppp/8/8/8/8/PPPPPPPP/MNBKQBNR w 0";
553 let pieces
= { w: new Array(8), b: new Array(8) };
554 // Shuffle pieces on first and last rank
555 for (let c
of ["w", "b"]) {
556 if (c
== 'b' && randomness
== 1) {
557 pieces
['b'] = pieces
['w'];
561 let positions
= ArrayFun
.range(8);
562 // Get random squares for every piece, totally freely
564 let randIndex
= randInt(8);
565 const bishop1Pos
= positions
[randIndex
];
566 positions
.splice(randIndex
, 1);
568 randIndex
= randInt(7);
569 const bishop2Pos
= positions
[randIndex
];
570 positions
.splice(randIndex
, 1);
572 randIndex
= randInt(6);
573 const knight1Pos
= positions
[randIndex
];
574 positions
.splice(randIndex
, 1);
576 randIndex
= randInt(5);
577 const knight2Pos
= positions
[randIndex
];
578 positions
.splice(randIndex
, 1);
580 randIndex
= randInt(4);
581 const queenPos
= positions
[randIndex
];
582 positions
.splice(randIndex
, 1);
584 randIndex
= randInt(3);
585 const kingPos
= positions
[randIndex
];
586 positions
.splice(randIndex
, 1);
588 randIndex
= randInt(2);
589 const rookPos
= positions
[randIndex
];
590 positions
.splice(randIndex
, 1);
591 const immobilizerPos
= positions
[0];
593 pieces
[c
][bishop1Pos
] = "b";
594 pieces
[c
][bishop2Pos
] = "b";
595 pieces
[c
][knight1Pos
] = "n";
596 pieces
[c
][knight2Pos
] = "n";
597 pieces
[c
][queenPos
] = "q";
598 pieces
[c
][kingPos
] = "k";
599 pieces
[c
][rookPos
] = "r";
600 pieces
[c
][immobilizerPos
] = "m";
603 pieces
["b"].join("") +
604 "/pppppppp/8/8/8/8/PPPPPPPP/" +
605 pieces
["w"].join("").toUpperCase() +
611 const initialSquare
= V
.CoordsToSquare(move.start
);
612 const finalSquare
= V
.CoordsToSquare(move.end
);
613 let notation
= undefined;
614 if (move.appear
[0].p
== V
.PAWN
) {
615 // Pawn: generally ambiguous short notation, so we use full description
616 notation
= "P" + initialSquare
+ finalSquare
;
617 } else if (move.appear
[0].p
== V
.KING
)
618 notation
= "K" + (move.vanish
.length
> 1 ? "x" : "") + finalSquare
;
619 else notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
620 // Add a capture mark (not describing what is captured...):
621 if (move.vanish
.length
> 1 && move.appear
[0].p
!= V
.KING
) notation
+= "X";