7abb77d47776add03ef39119de8ee56651b9e4c9
1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { shuffle
} from "@/utils/alea";
5 export class BaroqueRules
extends ChessRules
{
7 static get HasFlags() {
11 static get HasEnpassant() {
16 return ChessRules
.PIECES
.concat([V
.IMMOBILIZER
]);
21 //'m' for Immobilizer (I is too similar to 1)
22 return "Baroque/" + b
;
23 return b
; //usual piece
26 // No castling, but checks, so keep track of kings
27 setOtherVariables(fen
) {
28 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
29 const fenParts
= fen
.split(" ");
30 const position
= fenParts
[0].split("/");
31 for (let i
= 0; i
< position
.length
; i
++) {
33 for (let j
= 0; j
< position
[i
].length
; j
++) {
34 switch (position
[i
].charAt(j
)) {
36 this.kingPos
["b"] = [i
, k
];
39 this.kingPos
["w"] = [i
, k
];
42 const num
= parseInt(position
[i
].charAt(j
), 10);
43 if (!isNaN(num
)) k
+= num
- 1;
51 static get IMMOBILIZER() {
54 // Although other pieces keep their names here for coding simplicity,
56 // - a "rook" is a coordinator, capturing by coordinating with the king
57 // - a "knight" is a long-leaper, capturing as in draughts
58 // - a "bishop" is a chameleon, capturing as its prey
59 // - a "queen" is a withdrawer, capturing by moving away from pieces
61 // Is piece on square (x,y) immobilized?
62 isImmobilized([x
, y
]) {
63 const piece
= this.getPiece(x
, y
);
64 const color
= this.getColor(x
, y
);
65 const oppCol
= V
.GetOppCol(color
);
66 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
67 for (let step
of adjacentSteps
) {
68 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
71 this.board
[i
][j
] != V
.EMPTY
&&
72 this.getColor(i
, j
) == oppCol
74 const oppPiece
= this.getPiece(i
, j
);
75 if (oppPiece
== V
.IMMOBILIZER
) {
76 // Moving is possible only if this immobilizer is neutralized
77 for (let step2
of adjacentSteps
) {
78 const [i2
, j2
] = [i
+ step2
[0], j
+ step2
[1]];
79 if (i2
== x
&& j2
== y
) continue; //skip initial piece!
82 this.board
[i2
][j2
] != V
.EMPTY
&&
83 this.getColor(i2
, j2
) == color
85 if ([V
.BISHOP
, V
.IMMOBILIZER
].includes(this.getPiece(i2
, j2
)))
89 return true; //immobilizer isn't neutralized
91 // Chameleons can't be immobilized twice,
92 // because there is only one immobilizer
93 if (oppPiece
== V
.BISHOP
&& piece
== V
.IMMOBILIZER
) return true;
99 getPotentialMovesFrom([x
, y
]) {
100 // Pre-check: is thing on this square immobilized?
101 if (this.isImmobilized([x
, y
])) return [];
102 switch (this.getPiece(x
, y
)) {
104 return this.getPotentialImmobilizerMoves([x
, y
]);
106 return super.getPotentialMovesFrom([x
, y
]);
110 canTake([x1
, y1
], [x2
, y2
]) {
112 this.getPiece(x1
, y1
) == V
.KING
&&
113 this.getColor(x1
, y1
) != this.getColor(x2
, y2
)
117 // Modify capturing moves among listed pawn moves
118 addPawnCaptures(moves
, byChameleon
) {
119 const steps
= V
.steps
[V
.ROOK
];
120 const color
= this.turn
;
121 const oppCol
= V
.GetOppCol(color
);
123 if (!!byChameleon
&& m
.start
.x
!= m
.end
.x
&& m
.start
.y
!= m
.end
.y
)
124 // Chameleon not moving as pawn
126 // Try capturing in every direction
127 for (let step
of steps
) {
128 const sq2
= [m
.end
.x
+ 2 * step
[0], m
.end
.y
+ 2 * step
[1]];
130 V
.OnBoard(sq2
[0], sq2
[1]) &&
131 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
132 this.getColor(sq2
[0], sq2
[1]) == color
135 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
137 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
138 this.getColor(sq1
[0], sq1
[1]) == oppCol
140 const piece1
= this.getPiece(sq1
[0], sq1
[1]);
141 if (!byChameleon
|| piece1
== V
.PAWN
) {
158 getPotentialPawnMoves([x
, y
]) {
159 let moves
= super.getPotentialRookMoves([x
, y
]);
160 this.addPawnCaptures(moves
);
164 addRookCaptures(moves
, byChameleon
) {
165 const color
= this.turn
;
166 const oppCol
= V
.GetOppCol(color
);
167 const kp
= this.kingPos
[color
];
169 // Check piece-king rectangle (if any) corners for enemy pieces
170 if (m
.end
.x
== kp
[0] || m
.end
.y
== kp
[1]) return; //"flat rectangle"
171 const corner1
= [m
.end
.x
, kp
[1]];
172 const corner2
= [kp
[0], m
.end
.y
];
173 for (let [i
, j
] of [corner1
, corner2
]) {
174 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == oppCol
) {
175 const piece
= this.getPiece(i
, j
);
176 if (!byChameleon
|| piece
== V
.ROOK
) {
192 getPotentialRookMoves(sq
) {
193 let moves
= super.getPotentialQueenMoves(sq
);
194 this.addRookCaptures(moves
);
198 getKnightCaptures(startSquare
, byChameleon
) {
199 // Look in every direction for captures
200 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
201 const color
= this.turn
;
202 const oppCol
= V
.GetOppCol(color
);
204 const [x
, y
] = [startSquare
[0], startSquare
[1]];
205 const piece
= this.getPiece(x
, y
); //might be a chameleon!
206 outerLoop: for (let step
of steps
) {
207 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
208 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
214 this.getColor(i
, j
) == color
||
215 (!!byChameleon
&& this.getPiece(i
, j
) != V
.KNIGHT
)
219 // last(thing), cur(thing) : stop if "cur" is our color,
220 // or beyond board limits, or if "last" isn't empty and cur neither.
221 // Otherwise, if cur is empty then add move until cur square;
222 // if cur is occupied then stop if !!byChameleon and the square not
223 // occupied by a leaper.
225 let cur
= [i
+ step
[0], j
+ step
[1]];
226 let vanished
= [new PiPo({ x: x
, y: y
, c: color
, p: piece
})];
227 while (V
.OnBoard(cur
[0], cur
[1])) {
228 if (this.board
[last
[0]][last
[1]] != V
.EMPTY
) {
229 const oppPiece
= this.getPiece(last
[0], last
[1]);
230 if (!!byChameleon
&& oppPiece
!= V
.KNIGHT
) continue outerLoop
;
233 new PiPo({ x: last
[0], y: last
[1], c: oppCol
, p: oppPiece
})
236 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
) {
238 this.getColor(cur
[0], cur
[1]) == color
||
239 this.board
[last
[0]][last
[1]] != V
.EMPTY
241 //TODO: redundant test
247 appear: [new PiPo({ x: cur
[0], y: cur
[1], c: color
, p: piece
})],
248 vanish: JSON
.parse(JSON
.stringify(vanished
)), //TODO: required?
249 start: { x: x
, y: y
},
250 end: { x: cur
[0], y: cur
[1] }
254 last
= [last
[0] + step
[0], last
[1] + step
[1]];
255 cur
= [cur
[0] + step
[0], cur
[1] + step
[1]];
262 getPotentialKnightMoves(sq
) {
263 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
267 getPotentialBishopMoves([x
, y
]) {
269 .getPotentialQueenMoves([x
, y
])
270 .concat(this.getKnightCaptures([x
, y
], "asChameleon"));
271 // No "king capture" because king cannot remain under check
272 this.addPawnCaptures(moves
, "asChameleon");
273 this.addRookCaptures(moves
, "asChameleon");
274 this.addQueenCaptures(moves
, "asChameleon");
275 // Post-processing: merge similar moves, concatenating vanish arrays
276 let mergedMoves
= {};
278 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
279 if (!mergedMoves
[key
]) mergedMoves
[key
] = m
;
281 for (let i
= 1; i
< m
.vanish
.length
; i
++)
282 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
285 return Object
.values(mergedMoves
);
288 addQueenCaptures(moves
, byChameleon
) {
289 if (moves
.length
== 0) return;
290 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
291 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
292 let capturingDirections
= [];
293 const color
= this.turn
;
294 const oppCol
= V
.GetOppCol(color
);
295 adjacentSteps
.forEach(step
=> {
296 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
299 this.board
[i
][j
] != V
.EMPTY
&&
300 this.getColor(i
, j
) == oppCol
&&
301 (!byChameleon
|| this.getPiece(i
, j
) == V
.QUEEN
)
303 capturingDirections
.push(step
);
308 m
.end
.x
!= x
? (m
.end
.x
- x
) / Math
.abs(m
.end
.x
- x
) : 0,
309 m
.end
.y
!= y
? (m
.end
.y
- y
) / Math
.abs(m
.end
.y
- y
) : 0
311 // NOTE: includes() and even _.isEqual() functions fail...
312 // TODO: this test should be done only once per direction
314 capturingDirections
.some(dir
=> {
315 return dir
[0] == -step
[0] && dir
[1] == -step
[1];
318 const [i
, j
] = [x
- step
[0], y
- step
[1]];
323 p: this.getPiece(i
, j
),
332 getPotentialQueenMoves(sq
) {
333 let moves
= super.getPotentialQueenMoves(sq
);
334 this.addQueenCaptures(moves
);
338 getPotentialImmobilizerMoves(sq
) {
339 // Immobilizer doesn't capture
340 return super.getPotentialQueenMoves(sq
);
343 // isAttacked() is OK because the immobilizer doesn't take
345 isAttackedByPawn([x
, y
], color
) {
346 // Square (x,y) must be surroundable by two enemy pieces,
347 // and one of them at least should be a pawn (moving).
352 const steps
= V
.steps
[V
.ROOK
];
353 for (let dir
of dirs
) {
354 const [i1
, j1
] = [x
- dir
[0], y
- dir
[1]]; //"before"
355 const [i2
, j2
] = [x
+ dir
[0], y
+ dir
[1]]; //"after"
356 if (V
.OnBoard(i1
, j1
) && V
.OnBoard(i2
, j2
)) {
359 this.board
[i1
][j1
] != V
.EMPTY
&&
360 this.getColor(i1
, j1
) == color
&&
361 this.board
[i2
][j2
] == V
.EMPTY
365 this.board
[i2
][j2
] != V
.EMPTY
&&
366 this.getColor(i2
, j2
) == color
&&
367 this.board
[i1
][j1
] == V
.EMPTY
370 // Search a movable enemy pawn landing on the empty square
371 for (let step
of steps
) {
372 let [ii
, jj
] = this.board
[i1
][j1
] == V
.EMPTY
? [i1
, j1
] : [i2
, j2
];
373 let [i3
, j3
] = [ii
+ step
[0], jj
+ step
[1]];
374 while (V
.OnBoard(i3
, j3
) && this.board
[i3
][j3
] == V
.EMPTY
) {
380 this.getColor(i3
, j3
) == color
&&
381 this.getPiece(i3
, j3
) == V
.PAWN
&&
382 !this.isImmobilized([i3
, j3
])
393 isAttackedByRook([x
, y
], color
) {
394 // King must be on same column or row,
395 // and a rook should be able to reach a capturing square
396 const sameRow
= x
== this.kingPos
[color
][0];
397 const sameColumn
= y
== this.kingPos
[color
][1];
398 if (sameRow
|| sameColumn
) {
399 // Look for the enemy rook (maximum 1)
400 for (let i
= 0; i
< V
.size
.x
; i
++) {
401 for (let j
= 0; j
< V
.size
.y
; j
++) {
403 this.board
[i
][j
] != V
.EMPTY
&&
404 this.getColor(i
, j
) == color
&&
405 this.getPiece(i
, j
) == V
.ROOK
407 if (this.isImmobilized([i
, j
]))
408 // Because only one rook:
410 // Can it reach a capturing square? Easy but quite suboptimal way
411 // (TODO: generate all moves (turn is OK))
412 const moves
= this.getPotentialMovesFrom([i
, j
]);
413 for (let move of moves
) {
415 (sameRow
&& move.end
.y
== y
) ||
416 (sameColumn
&& move.end
.x
== x
)
428 isAttackedByKnight([x
, y
], color
) {
429 // Square (x,y) must be on same line as a knight,
430 // and there must be empty square(s) behind.
431 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
432 outerLoop: for (let step
of steps
) {
433 const [i0
, j0
] = [x
+ step
[0], y
+ step
[1]];
434 if (V
.OnBoard(i0
, j0
) && this.board
[i0
][j0
] == V
.EMPTY
) {
435 // Try in opposite direction:
436 let [i
, j
] = [x
- step
[0], y
- step
[1]];
437 while (V
.OnBoard(i
, j
)) {
438 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
442 if (V
.OnBoard(i
, j
)) {
443 if (this.getColor(i
, j
) == color
) {
445 this.getPiece(i
, j
) == V
.KNIGHT
&&
446 !this.isImmobilized([i
, j
])
453 // could be captured *if there was an empty space*
454 if (this.board
[i
+ step
[0]][j
+ step
[1]] != V
.EMPTY
)
465 isAttackedByBishop([x
, y
], color
) {
466 // We cheat a little here: since this function is used exclusively for
467 // the king, it's enough to check the immediate surrounding of the square.
468 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
469 for (let step
of adjacentSteps
) {
470 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
473 this.board
[i
][j
] != V
.EMPTY
&&
474 this.getColor(i
, j
) == color
&&
475 this.getPiece(i
, j
) == V
.BISHOP
&&
476 !this.isImmobilized([i
, j
])
484 isAttackedByQueen([x
, y
], color
) {
485 // Square (x,y) must be adjacent to a queen, and the queen must have
486 // some free space in the opposite direction from (x,y)
487 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
488 for (let step
of adjacentSteps
) {
489 const sq2
= [x
+ 2 * step
[0], y
+ 2 * step
[1]];
490 if (V
.OnBoard(sq2
[0], sq2
[1]) && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
) {
491 const sq1
= [x
+ step
[0], y
+ step
[1]];
493 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
494 this.getColor(sq1
[0], sq1
[1]) == color
&&
495 this.getPiece(sq1
[0], sq1
[1]) == V
.QUEEN
&&
496 !this.isImmobilized(sq1
)
505 isAttackedByKing([x
, y
], color
) {
506 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
507 for (let step
of steps
) {
508 let rx
= x
+ step
[0],
512 this.getPiece(rx
, ry
) === V
.KING
&&
513 this.getColor(rx
, ry
) == color
&&
514 !this.isImmobilized([rx
, ry
])
522 static GenRandInitFen(options
) {
523 if (options
.randomness
== 0)
525 return "rnbkqbnm/pppppppp/8/8/8/8/PPPPPPPP/MNBQKBNR w 0";
527 let pieces
= { w: new Array(8), b: new Array(8) };
528 // Shuffle pieces on first and last rank
529 for (let c
of ["w", "b"]) {
530 if (c
== 'b' && options
.randomness
== 1) {
531 pieces
['b'] = pieces
['w'];
535 // Get random squares for every piece, totally freely
536 let positions
= shuffle(ArrayFun
.range(8));
537 const composition
= ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'm'];
538 for (let i
= 0; i
< 8; i
++) pieces
[c
][positions
[i
]] = composition
[i
];
541 pieces
["b"].join("") +
542 "/pppppppp/8/8/8/8/PPPPPPPP/" +
543 pieces
["w"].join("").toUpperCase() +
548 static get VALUES() {
560 static get SEARCH_DEPTH() {
565 const initialSquare
= V
.CoordsToSquare(move.start
);
566 const finalSquare
= V
.CoordsToSquare(move.end
);
567 let notation
= undefined;
568 if (move.appear
[0].p
== V
.PAWN
) {
569 // Pawn: generally ambiguous short notation, so we use full description
570 notation
= "P" + initialSquare
+ finalSquare
;
571 } else if (move.appear
[0].p
== V
.KING
)
572 notation
= "K" + (move.vanish
.length
> 1 ? "x" : "") + finalSquare
;
573 else notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
574 // Add a capture mark (not describing what is captured...):
575 if (move.vanish
.length
> 1 && move.appear
[0].p
!= V
.KING
) notation
+= "X";