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;
50 const color
= this.turn
;
52 const oppCol
= V
.GetOppCol(color
);
53 if (this.isAttacked(this.kingPos
[color
], oppCol
))
54 squares
.push(JSON
.parse(JSON
.stringify(this.kingPos
[color
])));
55 for (let i
=0; i
<V
.size
.x
; i
++) {
56 for (let j
=0; j
<V
.size
.y
; j
++) {
58 this.getColor(i
, j
) == color
&&
59 this.getPiece(i
, j
) == V
.QUEEN
&&
60 this.isAttacked([i
, j
], oppCol
)
69 static GenRandInitFen(randomness
) {
71 // Castle flags here indicate pieces positions (if can castle)
72 return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 adehadeh -";
74 let pieces
= { w: new Array(8), b: new Array(8) };
76 for (let c
of ["w", "b"]) {
77 if (c
== 'b' && randomness
== 1) {
78 pieces
['b'] = pieces
['w'];
83 // Get random squares for king and queen between b and g files
84 let randIndex
= randInt(6) + 1;
85 let kingPos
= randIndex
;
86 randIndex
= randInt(5) + 1;
87 if (randIndex
>= kingPos
) randIndex
++;
88 let queenPos
= randIndex
;
90 // Get random squares for rooks to the left and right of the queen
91 // and king: not all squares of the same colors (for bishops).
92 const minQR
= Math
.min(kingPos
, queenPos
);
93 const maxQR
= Math
.max(kingPos
, queenPos
);
94 let rook1Pos
= randInt(minQR
);
95 let rook2Pos
= 7 - randInt(7 - maxQR
);
97 // Now, if we are unlucky all these 4 pieces may be on the same color.
98 const rem2
= [kingPos
, queenPos
, rook1Pos
, rook2Pos
].map(pos
=> pos
% 2);
99 if (rem2
.every(r
=> r
== 0) || rem2
.every(r
=> r
== 1)) {
100 // Shift a random of these pieces to the left or right
101 switch (randInt(4)) {
103 if (rook1Pos
== 0) rook1Pos
++;
107 if (Math
.random() < 0.5) kingPos
++;
111 if (Math
.random() < 0.5) queenPos
++;
115 if (rook2Pos
== 7) rook2Pos
--;
120 let bishop1Options
= { 0: true, 2: true, 4: true, 6: true };
121 let bishop2Options
= { 1: true, 3: true, 5: true, 7: true };
122 [kingPos
, queenPos
, rook1Pos
, rook2Pos
].forEach(pos
=> {
123 if (!!bishop1Options
[pos
]) delete bishop1Options
[pos
];
124 else if (!!bishop2Options
[pos
]) delete bishop2Options
[pos
];
126 const bishop1Pos
= parseInt(sample(Object
.keys(bishop1Options
), 1)[0]);
127 const bishop2Pos
= parseInt(sample(Object
.keys(bishop2Options
), 1)[0]);
129 // Knights' positions are now determined
131 kingPos
, queenPos
, rook1Pos
, rook2Pos
, bishop1Pos
, bishop2Pos
133 const [knight1Pos
, knight2Pos
] =
134 ArrayFun
.range(8).filter(pos
=> !forbidden
.includes(pos
));
136 pieces
[c
][rook1Pos
] = "r";
137 pieces
[c
][knight1Pos
] = "n";
138 pieces
[c
][bishop1Pos
] = "b";
139 pieces
[c
][queenPos
] = "q";
140 pieces
[c
][kingPos
] = "k";
141 pieces
[c
][bishop2Pos
] = "b";
142 pieces
[c
][knight2Pos
] = "n";
143 pieces
[c
][rook2Pos
] = "r";
144 flags
+= [rook1Pos
, queenPos
, kingPos
, rook2Pos
]
145 .sort().map(V
.CoordToColumn
).join("");
147 // Add turn + flags + enpassant
149 pieces
["b"].join("") +
150 "/pppppppp/8/8/8/8/PPPPPPPP/" +
151 pieces
["w"].join("").toUpperCase() +
152 " w 0 " + flags
+ " -"
157 // white pieces positions, then black pieces positions
158 this.castleFlags
= { w: [...Array(4)], b: [...Array(4)] };
159 for (let i
= 0; i
< 8; i
++) {
160 this.castleFlags
[i
< 4 ? "w" : "b"][i
% 4] =
161 V
.ColumnToCoord(fenflags
.charAt(i
))
165 getPotentialQueenMoves(sq
) {
166 return super.getPotentialQueenMoves(sq
).concat(this.getCastleMoves(sq
));
169 getCastleMoves([x
, y
]) {
170 const c
= this.getColor(x
, y
);
172 x
!= (c
== "w" ? V
.size
.x
- 1 : 0) ||
173 !this.castleFlags
[c
].slice(1, 3).includes(y
)
175 // x isn't first rank, or piece moved
178 const castlingPiece
= this.getPiece(x
, y
);
180 // Relative position of the selected piece: left or right ?
181 // If left: small castle left, large castle right.
182 // If right: usual situation.
183 const relPos
= (this.castleFlags
[c
][1] == y
? "left" : "right");
186 const oppCol
= V
.GetOppCol(c
);
189 // Castling piece, then rook:
190 const finalSquares
= {
191 0: (relPos
== "left" ? [1, 2] : [2, 3]),
192 3: (relPos
== "right" ? [6, 5] : [5, 4])
195 // Left, then right castle:
196 castlingCheck: for (let castleSide
of [0, 3]) {
197 if (this.castleFlags
[c
][castleSide
] >= 8) continue;
199 // Rook and castling piece are on initial position
200 const rookPos
= this.castleFlags
[c
][castleSide
];
202 // Nothing on the path of the king ? (and no checks)
203 const finDist
= finalSquares
[castleSide
][0] - y
;
204 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
208 this.isAttacked([x
, i
], oppCol
) ||
209 (this.board
[x
][i
] != V
.EMPTY
&&
210 // NOTE: next check is enough, because of chessboard constraints
211 (this.getColor(x
, i
) != c
||
212 ![castlingPiece
, V
.ROOK
].includes(this.getPiece(x
, i
))))
214 continue castlingCheck
;
217 } while (i
!= finalSquares
[castleSide
][0]);
219 // Nothing on the path to the rook?
220 step
= castleSide
== 0 ? -1 : 1;
221 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
222 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
225 // Nothing on final squares, except maybe castling piece and rook?
226 for (i
= 0; i
< 2; i
++) {
228 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
229 ![y
, rookPos
].includes(finalSquares
[castleSide
][i
])
231 continue castlingCheck
;
235 // If this code is reached, castle is valid
241 y: finalSquares
[castleSide
][0],
247 y: finalSquares
[castleSide
][1],
253 new PiPo({ x: x
, y: y
, p: castlingPiece
, c: c
}),
254 new PiPo({ x: x
, y: rookPos
, p: V
.ROOK
, c: c
})
256 // In this variant, always castle by playing onto the rook
257 end: { x: x
, y: rookPos
}
266 const oppCol
= V
.GetOppCol(color
);
267 if (this.isAttacked(this.kingPos
[color
], oppCol
)) return true;
268 for (let i
=0; i
<V
.size
.x
; i
++) {
269 for (let j
=0; j
<V
.size
.y
; j
++) {
271 this.getColor(i
, j
) == color
&&
272 this.getPiece(i
, j
) == V
.QUEEN
&&
273 this.isAttacked([i
, j
], oppCol
)
282 // "twoKings" arg for the similar Twokings variant.
283 updateCastleFlags(move, piece
, twoKings
) {
284 const c
= V
.GetOppCol(this.turn
);
285 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
286 // Update castling flags if castling pieces moved or were captured
287 const oppCol
= V
.GetOppCol(c
);
288 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
289 if (move.start
.x
== firstRank
) {
290 if (piece
== V
.KING
|| (!twoKings
&& piece
== V
.QUEEN
)) {
291 if (this.castleFlags
[c
][1] == move.start
.y
)
292 this.castleFlags
[c
][1] = 8;
293 else if (this.castleFlags
[c
][2] == move.start
.y
)
294 this.castleFlags
[c
][2] = 8;
295 // Else: the flag is already turned off
299 move.start
.x
== firstRank
&& //our rook moves?
300 [this.castleFlags
[c
][0], this.castleFlags
[c
][3]].includes(move.start
.y
)
302 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 3);
303 this.castleFlags
[c
][flagIdx
] = 8;
305 move.end
.x
== oppFirstRank
&& //we took opponent rook?
306 [this.castleFlags
[oppCol
][0], this.castleFlags
[oppCol
][3]]
307 .includes(move.end
.y
)
309 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 3);
310 this.castleFlags
[oppCol
][flagIdx
] = 8;
314 // NOTE: do not set queen value to 1000 or so, because there may be several.
316 static get SEARCH_DEPTH() {
321 if (move.appear
.length
== 2) {
322 // Castle: determine the right notation
323 const color
= move.appear
[0].c
;
324 let symbol
= (move.appear
[0].p
== V
.QUEEN
? "Q" : "") + "0-0";
327 this.castleFlags
[color
][1] == move.vanish
[0].y
&&
328 move.end
.y
> move.start
.y
332 this.castleFlags
[color
][2] == move.vanish
[0].y
&&
333 move.end
.y
< move.start
.y
340 return super.getNotation(move);