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
), 10);
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
];
127 parseInt(sample(Object
.keys(bishop1Options
), 1)[0], 10);
129 parseInt(sample(Object
.keys(bishop2Options
), 1)[0], 10);
131 // Knights' positions are now determined
133 kingPos
, queenPos
, rook1Pos
, rook2Pos
, bishop1Pos
, bishop2Pos
135 const [knight1Pos
, knight2Pos
] =
136 ArrayFun
.range(8).filter(pos
=> !forbidden
.includes(pos
));
138 pieces
[c
][rook1Pos
] = "r";
139 pieces
[c
][knight1Pos
] = "n";
140 pieces
[c
][bishop1Pos
] = "b";
141 pieces
[c
][queenPos
] = "q";
142 pieces
[c
][kingPos
] = "k";
143 pieces
[c
][bishop2Pos
] = "b";
144 pieces
[c
][knight2Pos
] = "n";
145 pieces
[c
][rook2Pos
] = "r";
146 flags
+= [rook1Pos
, queenPos
, kingPos
, rook2Pos
]
147 .sort().map(V
.CoordToColumn
).join("");
149 // Add turn + flags + enpassant
151 pieces
["b"].join("") +
152 "/pppppppp/8/8/8/8/PPPPPPPP/" +
153 pieces
["w"].join("").toUpperCase() +
154 " w 0 " + flags
+ " -"
159 // white pieces positions, then black pieces positions
160 this.castleFlags
= { w: [...Array(4)], b: [...Array(4)] };
161 for (let i
= 0; i
< 8; i
++) {
162 this.castleFlags
[i
< 4 ? "w" : "b"][i
% 4] =
163 V
.ColumnToCoord(fenflags
.charAt(i
))
167 getPotentialQueenMoves(sq
) {
168 return super.getPotentialQueenMoves(sq
).concat(this.getCastleMoves(sq
));
171 getCastleMoves([x
, y
]) {
172 const c
= this.getColor(x
, y
);
174 x
!= (c
== "w" ? V
.size
.x
- 1 : 0) ||
175 !this.castleFlags
[c
].slice(1, 3).includes(y
)
177 // x isn't first rank, or piece moved
180 const castlingPiece
= this.getPiece(x
, y
);
182 // Relative position of the selected piece: left or right ?
183 // If left: small castle left, large castle right.
184 // If right: usual situation.
185 const relPos
= (this.castleFlags
[c
][1] == y
? "left" : "right");
188 const oppCol
= V
.GetOppCol(c
);
191 // Castling piece, then rook:
192 const finalSquares
= {
193 0: (relPos
== "left" ? [1, 2] : [2, 3]),
194 3: (relPos
== "right" ? [6, 5] : [5, 4])
197 // Left, then right castle:
198 castlingCheck: for (let castleSide
of [0, 3]) {
199 if (this.castleFlags
[c
][castleSide
] >= 8) continue;
201 // Rook and castling piece are on initial position
202 const rookPos
= this.castleFlags
[c
][castleSide
];
204 // Nothing on the path of the king ? (and no checks)
205 const finDist
= finalSquares
[castleSide
][0] - y
;
206 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
210 this.isAttacked([x
, i
], oppCol
) ||
211 (this.board
[x
][i
] != V
.EMPTY
&&
212 // NOTE: next check is enough, because of chessboard constraints
213 (this.getColor(x
, i
) != c
||
214 ![castlingPiece
, V
.ROOK
].includes(this.getPiece(x
, i
))))
216 continue castlingCheck
;
219 } while (i
!= finalSquares
[castleSide
][0]);
221 // Nothing on the path to the rook?
222 step
= castleSide
== 0 ? -1 : 1;
223 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
224 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
227 // Nothing on final squares, except maybe castling piece and rook?
228 for (i
= 0; i
< 2; i
++) {
230 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
231 ![y
, rookPos
].includes(finalSquares
[castleSide
][i
])
233 continue castlingCheck
;
237 // If this code is reached, castle is valid
243 y: finalSquares
[castleSide
][0],
249 y: finalSquares
[castleSide
][1],
255 new PiPo({ x: x
, y: y
, p: castlingPiece
, c: c
}),
256 new PiPo({ x: x
, y: rookPos
, p: V
.ROOK
, c: c
})
258 // In this variant, always castle by playing onto the rook
259 end: { x: x
, y: rookPos
}
268 const oppCol
= V
.GetOppCol(color
);
269 if (this.isAttacked(this.kingPos
[color
], oppCol
)) return true;
270 for (let i
=0; i
<V
.size
.x
; i
++) {
271 for (let j
=0; j
<V
.size
.y
; j
++) {
273 this.getColor(i
, j
) == color
&&
274 this.getPiece(i
, j
) == V
.QUEEN
&&
275 this.isAttacked([i
, j
], oppCol
)
284 // "twoKings" arg for the similar Twokings variant.
285 updateCastleFlags(move, piece
, twoKings
) {
286 const c
= V
.GetOppCol(this.turn
);
287 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
288 // Update castling flags if castling pieces moved or were captured
289 const oppCol
= V
.GetOppCol(c
);
290 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
291 if (move.start
.x
== firstRank
) {
292 if (piece
== V
.KING
|| (!twoKings
&& piece
== V
.QUEEN
)) {
293 if (this.castleFlags
[c
][1] == move.start
.y
)
294 this.castleFlags
[c
][1] = 8;
295 else if (this.castleFlags
[c
][2] == move.start
.y
)
296 this.castleFlags
[c
][2] = 8;
297 // Else: the flag is already turned off
301 move.start
.x
== firstRank
&& //our rook moves?
302 [this.castleFlags
[c
][0], this.castleFlags
[c
][3]].includes(move.start
.y
)
304 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 3);
305 this.castleFlags
[c
][flagIdx
] = 8;
307 move.end
.x
== oppFirstRank
&& //we took opponent rook?
308 [this.castleFlags
[oppCol
][0], this.castleFlags
[oppCol
][3]]
309 .includes(move.end
.y
)
311 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 3);
312 this.castleFlags
[oppCol
][flagIdx
] = 8;
316 // NOTE: do not set queen value to 1000 or so, because there may be several.
318 static get SEARCH_DEPTH() {
323 if (move.appear
.length
== 2) {
324 // Castle: determine the right notation
325 const color
= move.appear
[0].c
;
326 let symbol
= (move.appear
[0].p
== V
.QUEEN
? "Q" : "") + "0-0";
329 this.castleFlags
[color
][1] == move.vanish
[0].y
&&
330 move.end
.y
> move.start
.y
334 this.castleFlags
[color
][2] == move.vanish
[0].y
&&
335 move.end
.y
< move.start
.y
342 return super.getNotation(move);