1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
, sample
} from "@/utils/alea";
5 export class CoregalRules
extends ChessRules
{
6 static IsGoodPosition(position
) {
7 if (!ChessRules
.IsGoodPosition(position
)) return false;
8 const rows
= position
.split("/");
9 // Check that at least one queen of each color is there:
11 for (let row
of rows
) {
12 for (let i
= 0; i
< row
.length
; i
++)
13 if (['Q','q'].includes(row
[i
])) queens
[row
[i
]] = true;
15 if (Object
.keys(queens
).length
!= 2) return false;
19 static IsGoodFlags(flags
) {
20 return !!flags
.match(/^[a-z]{8,8}$/);
23 // Scanning king position for faster updates is still interesting,
24 // but no need for INIT_COL_KING because it's given in castle flags.
26 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
27 const fenRows
= V
.ParseFen(fen
).position
.split("/");
28 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
29 for (let i
= 0; i
< fenRows
.length
; i
++) {
31 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
32 switch (fenRows
[i
].charAt(j
)) {
34 this.kingPos
["b"] = [i
, k
];
37 this.kingPos
["w"] = [i
, k
];
40 const num
= parseInt(fenRows
[i
].charAt(j
));
41 if (!isNaN(num
)) k
+= num
- 1;
49 getCheckSquares(color
) {
51 const oppCol
= V
.GetOppCol(color
);
52 if (this.isAttacked(this.kingPos
[color
], oppCol
))
53 squares
.push(JSON
.parse(JSON
.stringify(this.kingPos
[color
])));
54 for (let i
=0; i
<V
.size
.x
; i
++) {
55 for (let j
=0; j
<V
.size
.y
; j
++) {
57 this.getColor(i
, j
) == color
&&
58 this.getPiece(i
, j
) == V
.QUEEN
&&
59 this.isAttacked([i
, j
], oppCol
)
68 static GenRandInitFen(randomness
) {
70 // Castle flags here indicate pieces positions (if can castle)
71 return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 adehadeh -";
73 let pieces
= { w: new Array(8), b: new Array(8) };
75 for (let c
of ["w", "b"]) {
76 if (c
== 'b' && randomness
== 1) {
77 pieces
['b'] = pieces
['w'];
82 // Get random squares for king and queen between b and g files
83 let randIndex
= randInt(6) + 1;
84 let kingPos
= randIndex
;
85 randIndex
= randInt(5) + 1;
86 if (randIndex
>= kingPos
) randIndex
++;
87 let queenPos
= randIndex
;
89 // Get random squares for rooks to the left and right of the queen
90 // and king: not all squares of the same colors (for bishops).
91 const minQR
= Math
.min(kingPos
, queenPos
);
92 const maxQR
= Math
.max(kingPos
, queenPos
);
93 let rook1Pos
= randInt(minQR
);
94 let rook2Pos
= 7 - randInt(7 - maxQR
);
96 // Now, if we are unlucky all these 4 pieces may be on the same color.
97 const rem2
= [kingPos
, queenPos
, rook1Pos
, rook2Pos
].map(pos
=> pos
% 2);
98 if (rem2
.every(r
=> r
== 0) || rem2
.every(r
=> r
== 1)) {
99 // Shift a random of these pieces to the left or right
100 switch (randInt(4)) {
102 if (rook1Pos
== 0) rook1Pos
++;
106 if (Math
.random() < 0.5) kingPos
++;
110 if (Math
.random() < 0.5) queenPos
++;
114 if (rook2Pos
== 7) rook2Pos
--;
119 let bishop1Options
= { 0: true, 2: true, 4: true, 6: true };
120 let bishop2Options
= { 1: true, 3: true, 5: true, 7: true };
121 [kingPos
, queenPos
, rook1Pos
, rook2Pos
].forEach(pos
=> {
122 if (!!bishop1Options
[pos
]) delete bishop1Options
[pos
];
123 else if (!!bishop2Options
[pos
]) delete bishop2Options
[pos
];
125 const bishop1Pos
= parseInt(sample(Object
.keys(bishop1Options
), 1)[0]);
126 const bishop2Pos
= parseInt(sample(Object
.keys(bishop2Options
), 1)[0]);
128 // Knights' positions are now determined
130 kingPos
, queenPos
, rook1Pos
, rook2Pos
, bishop1Pos
, bishop2Pos
132 const [knight1Pos
, knight2Pos
] =
133 ArrayFun
.range(8).filter(pos
=> !forbidden
.includes(pos
));
135 pieces
[c
][rook1Pos
] = "r";
136 pieces
[c
][knight1Pos
] = "n";
137 pieces
[c
][bishop1Pos
] = "b";
138 pieces
[c
][queenPos
] = "q";
139 pieces
[c
][kingPos
] = "k";
140 pieces
[c
][bishop2Pos
] = "b";
141 pieces
[c
][knight2Pos
] = "n";
142 pieces
[c
][rook2Pos
] = "r";
143 flags
+= [rook1Pos
, queenPos
, kingPos
, rook2Pos
]
144 .sort().map(V
.CoordToColumn
).join("");
146 // Add turn + flags + enpassant
148 pieces
["b"].join("") +
149 "/pppppppp/8/8/8/8/PPPPPPPP/" +
150 pieces
["w"].join("").toUpperCase() +
151 " w 0 " + flags
+ " -"
156 // white pieces positions, then black pieces positions
157 this.castleFlags
= { w: [...Array(4)], b: [...Array(4)] };
158 for (let i
= 0; i
< 8; i
++) {
159 this.castleFlags
[i
< 4 ? "w" : "b"][i
% 4] =
160 V
.ColumnToCoord(fenflags
.charAt(i
))
164 getPotentialQueenMoves(sq
) {
165 return super.getPotentialQueenMoves(sq
).concat(this.getCastleMoves(sq
));
168 getCastleMoves([x
, y
]) {
169 const c
= this.getColor(x
, y
);
171 x
!= (c
== "w" ? V
.size
.x
- 1 : 0) ||
172 !this.castleFlags
[c
].slice(1, 3).includes(y
)
174 // x isn't first rank, or piece moved
177 const castlingPiece
= this.getPiece(x
, y
);
179 // Relative position of the selected piece: left or right ?
180 // If left: small castle left, large castle right.
181 // If right: usual situation.
182 const relPos
= (this.castleFlags
[c
][1] == y
? "left" : "right");
185 const oppCol
= V
.GetOppCol(c
);
188 // Castling piece, then rook:
189 const finalSquares
= {
190 0: (relPos
== "left" ? [1, 2] : [2, 3]),
191 3: (relPos
== "right" ? [6, 5] : [5, 4])
194 // Left, then right castle:
195 castlingCheck: for (let castleSide
of [0, 3]) {
196 if (this.castleFlags
[c
][castleSide
] >= 8) continue;
198 // Rook and castling piece are on initial position
199 const rookPos
= this.castleFlags
[c
][castleSide
];
201 // Nothing on the path of the king ? (and no checks)
202 const finDist
= finalSquares
[castleSide
][0] - y
;
203 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
207 this.isAttacked([x
, i
], oppCol
) ||
208 (this.board
[x
][i
] != V
.EMPTY
&&
209 // NOTE: next check is enough, because of chessboard constraints
210 (this.getColor(x
, i
) != c
||
211 ![castlingPiece
, V
.ROOK
].includes(this.getPiece(x
, i
))))
213 continue castlingCheck
;
216 } while (i
!= finalSquares
[castleSide
][0]);
218 // Nothing on the path to the rook?
219 step
= castleSide
== 0 ? -1 : 1;
220 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
221 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
224 // Nothing on final squares, except maybe castling piece and rook?
225 for (i
= 0; i
< 2; i
++) {
227 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
228 ![y
, rookPos
].includes(finalSquares
[castleSide
][i
])
230 continue castlingCheck
;
234 // If this code is reached, castle is valid
240 y: finalSquares
[castleSide
][0],
246 y: finalSquares
[castleSide
][1],
252 new PiPo({ x: x
, y: y
, p: castlingPiece
, c: c
}),
253 new PiPo({ x: x
, y: rookPos
, p: V
.ROOK
, c: c
})
255 // In this variant, always castle by playing onto the rook
256 end: { x: x
, y: rookPos
}
265 const oppCol
= V
.GetOppCol(color
);
266 if (this.isAttacked(this.kingPos
[color
], oppCol
)) return true;
267 for (let i
=0; i
<V
.size
.x
; i
++) {
268 for (let j
=0; j
<V
.size
.y
; j
++) {
270 this.getColor(i
, j
) == color
&&
271 this.getPiece(i
, j
) == V
.QUEEN
&&
272 this.isAttacked([i
, j
], oppCol
)
281 // "twoKings" arg for the similar Twokings variant.
282 updateCastleFlags(move, piece
, twoKings
) {
283 const c
= V
.GetOppCol(this.turn
);
284 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
285 // Update castling flags if castling pieces moved or were captured
286 const oppCol
= V
.GetOppCol(c
);
287 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
288 if (move.start
.x
== firstRank
) {
289 if (piece
== V
.KING
|| (!twoKings
&& piece
== V
.QUEEN
)) {
290 if (this.castleFlags
[c
][1] == move.start
.y
)
291 this.castleFlags
[c
][1] = 8;
292 else if (this.castleFlags
[c
][2] == move.start
.y
)
293 this.castleFlags
[c
][2] = 8;
294 // Else: the flag is already turned off
298 move.start
.x
== firstRank
&& //our rook moves?
299 [this.castleFlags
[c
][0], this.castleFlags
[c
][3]].includes(move.start
.y
)
301 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 3);
302 this.castleFlags
[c
][flagIdx
] = 8;
304 move.end
.x
== oppFirstRank
&& //we took opponent rook?
305 [this.castleFlags
[oppCol
][0], this.castleFlags
[oppCol
][3]]
306 .includes(move.end
.y
)
308 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 3);
309 this.castleFlags
[oppCol
][flagIdx
] = 8;
313 // NOTE: do not set queen value to 1000 or so, because there may be several.
315 static get SEARCH_DEPTH() {
320 if (move.appear
.length
== 2) {
321 // Castle: determine the right notation
322 const color
= move.appear
[0].c
;
323 let symbol
= (move.appear
[0].p
== V
.QUEEN
? "Q" : "") + "0-0";
326 this.castleFlags
[color
][1] == move.vanish
[0].y
&&
327 move.end
.y
> move.start
.y
331 this.castleFlags
[color
][2] == move.vanish
[0].y
&&
332 move.end
.y
< move.start
.y
339 return super.getNotation(move);