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
{
7 static IsGoodPosition(position
) {
8 if (!ChessRules
.IsGoodPosition(position
)) return false;
9 const rows
= position
.split("/");
10 // Check that at least one queen of each color is there:
12 for (let row
of rows
) {
13 for (let i
= 0; i
< row
.length
; i
++)
14 if (['Q','q'].includes(row
[i
])) queens
[row
[i
]] = true;
16 if (Object
.keys(queens
).length
!= 2) return false;
20 static IsGoodFlags(flags
) {
21 return !!flags
.match(/^[a-z]{8,8}$/);
24 // Scanning king position for faster updates is still interesting.
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;
51 m
.vanish
.length
== 2 &&
52 m
.appear
.length
== 2 &&
53 m
.vanish
[0].p
== V
.QUEEN
55 // Large castle: show castle symbol
56 return "Coregal/castle";
58 return super.getPPpath(m
);
62 const color
= this.turn
;
64 const oppCol
= V
.GetOppCol(color
);
65 if (this.isAttacked(this.kingPos
[color
], oppCol
))
66 squares
.push(JSON
.parse(JSON
.stringify(this.kingPos
[color
])));
67 for (let i
=0; i
<V
.size
.x
; i
++) {
68 for (let j
=0; j
<V
.size
.y
; j
++) {
70 this.getColor(i
, j
) == color
&&
71 this.getPiece(i
, j
) == V
.QUEEN
&&
72 this.isAttacked([i
, j
], oppCol
)
81 static GenRandInitFen(options
) {
82 if (options
.randomness
== 0)
83 // Castle flags here indicate pieces positions (if can castle)
84 return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 adehadeh -";
86 let pieces
= { w: new Array(8), b: new Array(8) };
88 for (let c
of ["w", "b"]) {
89 if (c
== 'b' && options
.randomness
== 1) {
90 pieces
['b'] = pieces
['w'];
95 // Get random squares for king and queen between b and g files
96 let randIndex
= randInt(6) + 1;
97 let kingPos
= randIndex
;
98 randIndex
= randInt(5) + 1;
99 if (randIndex
>= kingPos
) randIndex
++;
100 let queenPos
= randIndex
;
102 // Get random squares for rooks to the left and right of the queen
103 // and king: not all squares of the same colors (for bishops).
104 const minQR
= Math
.min(kingPos
, queenPos
);
105 const maxQR
= Math
.max(kingPos
, queenPos
);
106 let rook1Pos
= randInt(minQR
);
107 let rook2Pos
= 7 - randInt(7 - maxQR
);
109 // Now, if we are unlucky all these 4 pieces may be on the same color.
110 const rem2
= [kingPos
, queenPos
, rook1Pos
, rook2Pos
].map(pos
=> pos
% 2);
111 if (rem2
.every(r
=> r
== 0) || rem2
.every(r
=> r
== 1)) {
112 // Shift a random of these pieces to the left or right
113 switch (randInt(4)) {
115 if (rook1Pos
== 0) rook1Pos
++;
119 if (Math
.random() < 0.5) kingPos
++;
123 if (Math
.random() < 0.5) queenPos
++;
127 if (rook2Pos
== 7) rook2Pos
--;
132 let bishop1Options
= { 0: true, 2: true, 4: true, 6: true };
133 let bishop2Options
= { 1: true, 3: true, 5: true, 7: true };
134 [kingPos
, queenPos
, rook1Pos
, rook2Pos
].forEach(pos
=> {
135 if (!!bishop1Options
[pos
]) delete bishop1Options
[pos
];
136 else if (!!bishop2Options
[pos
]) delete bishop2Options
[pos
];
139 parseInt(sample(Object
.keys(bishop1Options
), 1)[0], 10);
141 parseInt(sample(Object
.keys(bishop2Options
), 1)[0], 10);
143 // Knights' positions are now determined
145 kingPos
, queenPos
, rook1Pos
, rook2Pos
, bishop1Pos
, bishop2Pos
147 const [knight1Pos
, knight2Pos
] =
148 ArrayFun
.range(8).filter(pos
=> !forbidden
.includes(pos
));
150 pieces
[c
][rook1Pos
] = "r";
151 pieces
[c
][knight1Pos
] = "n";
152 pieces
[c
][bishop1Pos
] = "b";
153 pieces
[c
][queenPos
] = "q";
154 pieces
[c
][kingPos
] = "k";
155 pieces
[c
][bishop2Pos
] = "b";
156 pieces
[c
][knight2Pos
] = "n";
157 pieces
[c
][rook2Pos
] = "r";
158 flags
+= [rook1Pos
, queenPos
, kingPos
, rook2Pos
]
159 .sort().map(V
.CoordToColumn
).join("");
161 // Add turn + flags + enpassant
163 pieces
["b"].join("") +
164 "/pppppppp/8/8/8/8/PPPPPPPP/" +
165 pieces
["w"].join("").toUpperCase() +
166 " w 0 " + flags
+ " -"
171 // white pieces positions, then black pieces positions
172 this.castleFlags
= { w: [...Array(4)], b: [...Array(4)] };
173 for (let i
= 0; i
< 8; i
++) {
174 this.castleFlags
[i
< 4 ? "w" : "b"][i
% 4] =
175 V
.ColumnToCoord(fenflags
.charAt(i
))
179 getPotentialQueenMoves([x
, y
]) {
180 let moves
= super.getPotentialQueenMoves([x
, y
]);
181 const c
= this.getColor(x
, y
);
182 if (this.castleFlags
[c
].slice(1, 3).includes(y
))
183 moves
= moves
.concat(this.getCastleMoves([x
, y
]));
187 getPotentialKingMoves([x
, y
]) {
188 let moves
= this.getSlideNJumpMoves(
189 [x
, y
], V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1);
190 const c
= this.getColor(x
, y
);
191 if (this.castleFlags
[c
].slice(1, 3).includes(y
))
192 moves
= moves
.concat(this.getCastleMoves([x
, y
]));
196 getCastleMoves([x
, y
]) {
197 // Relative position of the selected piece: left or right ?
198 // If left: small castle left, large castle right.
199 // If right: usual situation.
200 const c
= this.getColor(x
, y
);
201 const relPos
= (this.castleFlags
[c
][1] == y
? "left" : "right");
203 const finalSquares
= [
204 relPos
== "left" ? [1, 2] : [2, 3],
205 relPos
== "right" ? [6, 5] : [5, 4]
207 const saveFlags
= JSON
.stringify(this.castleFlags
[c
]);
208 // Alter flags to follow base_rules semantic
209 this.castleFlags
[c
] = [0, 3].map(i
=> this.castleFlags
[c
][i
]);
210 const moves
= super.getCastleMoves([x
, y
], finalSquares
);
211 this.castleFlags
[c
] = JSON
.parse(saveFlags
);
216 const oppCol
= V
.GetOppCol(color
);
217 if (this.isAttacked(this.kingPos
[color
], oppCol
)) return true;
218 for (let i
=0; i
<V
.size
.x
; i
++) {
219 for (let j
=0; j
<V
.size
.y
; j
++) {
221 this.getColor(i
, j
) == color
&&
222 this.getPiece(i
, j
) == V
.QUEEN
&&
223 this.isAttacked([i
, j
], oppCol
)
232 // "twoKings" arg for the similar Twokings variant.
233 updateCastleFlags(move, piece
, twoKings
) {
234 const c
= V
.GetOppCol(this.turn
);
235 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
236 // Update castling flags if castling pieces moved or were captured
237 const oppCol
= V
.GetOppCol(c
);
238 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
239 if (move.start
.x
== firstRank
) {
240 if (piece
== V
.KING
|| (!twoKings
&& piece
== V
.QUEEN
)) {
241 if (this.castleFlags
[c
][1] == move.start
.y
)
242 this.castleFlags
[c
][1] = 8;
243 else if (this.castleFlags
[c
][2] == move.start
.y
)
244 this.castleFlags
[c
][2] = 8;
245 // Else: the flag is already turned off
249 move.start
.x
== firstRank
&& //our rook moves?
250 [this.castleFlags
[c
][0], this.castleFlags
[c
][3]].includes(move.start
.y
)
252 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 3);
253 this.castleFlags
[c
][flagIdx
] = 8;
255 move.end
.x
== oppFirstRank
&& //we took opponent rook?
256 [this.castleFlags
[oppCol
][0], this.castleFlags
[oppCol
][3]]
257 .includes(move.end
.y
)
259 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 3);
260 this.castleFlags
[oppCol
][flagIdx
] = 8;
264 // NOTE: do not set queen value to 1000 or so, because there may be several.
266 static get SEARCH_DEPTH() {
271 if (move.appear
.length
== 2) {
272 // Castle: determine the right notation
273 const color
= move.appear
[0].c
;
274 let symbol
= (move.appear
[0].p
== V
.QUEEN
? "Q" : "") + "0-0";
277 this.castleFlags
[color
][1] == move.vanish
[0].y
&&
278 move.end
.y
> move.start
.y
282 this.castleFlags
[color
][2] == move.vanish
[0].y
&&
283 move.end
.y
< move.start
.y
290 return super.getNotation(move);