TB4YBE4CMWCLSKJ43QF6IU5HVYUUO33BLXVG7XDRLJS3IFIBQLYAC
AILDUV72D7FNB56O2UESPLFWQSHBYO4QZENSK2QSHR3UR52GLVFQC
BL3ZR4OWJM54HFXUNMUZKB5YQYVBT7ETFIXCOXWL6S5SZFM6IFDQC
WTTQGET65BQV7NSFOI75JGCTWFS6R73OFZE4V63JYWAODNIOUAHQC
7SQSGQOZRCHN3GGWPNFI36K2R7YZMOPXZP3FP4PYDDOZML5YIYOQC
7T5U3ARTEEAORKCSJY763X5TJRGHCEQK3OW323YO5CZIM77EH77AC
IBUD4SUGA4IS6HUQIHVJJOFS4CMTWJMTMXS4LWGFVK4PFUFKE5LAC
YCH5WGGP3PPG2PUNVJIJ6SQXDPYU7NGCVQEJYN27B6FYV5JYQOMQC
ET37VV7TCQDN6RWF46EUJB6EPNSDOX5TI25NW4P4H5HI4VOTARZQC
GZPVUGLJGD3VGLIK7JDABQ24E5SWN5CYRDGI6PIAFZ2BIMRDMANQC
TDHJSFFSJZBEZFMDHPLRSVB6CIVJ7AWGGNIC3WUCADQLQSIQKXDAC
YVNKHIU4ENET52Y5BD5WFA23EVTQ4ZQQLIAZRRA4AN44TXUTNRCQC
JPK7SJDQWLCNDXJQEQQHXBN4Y677BPQ5QBL55GRZ55RSP4GK4L4AC
FR6N5DIUYYWT5BBOIBOX3GCSEKZIINSGT4F4CWM6YA2XLQEOU4ZQC
3MEBL2ZUY2LI5LW6MK6B32F62TSVF46QVNIDB3Y4OWLAN4NT7KTAC
QYOCQUIWNSZNLCPDJCKERKC6BXBUNTE6AJCFYL4WDRU32LFX37XQC
SNMQLFZFCV5XTPRLSD7SV4RPVOGUVJOYCZAKI5ATCEPSI5JMGVAAC
EHBLDMND5ASGZTTLDLEA5RGQIAZTXZN4HBPGD5PB6FDGZGTJ33SQC
O6734I3LMVTKXE5Y7FR3EDGSVIJQHFATUPJJCMRYXD7KLAN6E4UQC
XCZABYMDLHLHXNTHZFCXPNP6VOCNYUROAQACE5SWBL5FJK2EB7WQC
A46B5KNQFPTZEIL2JZKD2CQELGU3COE6FGTV2ABS5U7DVTVDGEBQC
pawn_attacks: [2][64]Chess.BoardType = genPawnAttacks(),
king_attacks: [64]Chess.BoardType = genKingAttacks(),
knight_attacks: [64]Chess.BoardType = genKnightAttacks(),
pawn_attacks: [2][64]Board.BoardType = genPawnAttacks(),
king_attacks: [64]Board.BoardType = genKingAttacks(),
knight_attacks: [64]Board.BoardType = genKnightAttacks(),
const bishop_magic: [64]Chess.BoardType = .{ 9241390835893862432, 9234710240556097536, 1227239730261824640, 577604527957049348, 6918659473418780672, 351888113804529668, 2328924575914001472, 4614013701991826049, 2331927815258656, 9016004274033184, 2305860618585916944, 648597650767120384, 577061163230053010, 579277706207166480, 4614220409926336512, 580964498095089664, 4616199239888937985, 1126041776325888, 616996207142899796, 10141929783173120, 5630608183136385, 36873230555873632, 181275456685879328, 576743335520240152, 2603115769260982529, 2380875882396914688, 2289189183030272, 171145581949894664, 1153203048319827969, 2305913930985283584, 72657961755345152, 1127003718435328, 148904939935000608, 282608955041792, 1153062518049211400, 10378688279935975552, 290279660069376, 290279660069376, 6922619801915492428, 6922619801915492428, 1158067358212916246, 2328924575914001472, 36319360414908928, 4611703757044990977, 1224980203021800448, 4521260535005248, 290561443953836160, 2310347987528229122, 2328924575914001472, 564053894369288, 1585408924206502016, 4675139272704, 144185593598050818, 7440091971840, 4612816883383795840, 9234710240556097536, 4614013701991826049, 580964498095089664, 83334186434036744, 648597650767120384, 17592766972164, 9439545093914103944, 2331927815258656, 9241390835893862432 };
const rook_magic: [64]Chess.BoardType = .{ 9259401108760043536, 594475563268280320, 144124055069409314, 72068610631143424, 72076285769418752, 108090789187420288, 324265770315940096, 9367487500631408770, 2305983747248373760, 14051934662285148160, 4612249107028804096, 72198881416318976, 9223653529145639440, 4648277836300289160, 1153204079162360320, 167055404054872320, 4791830278402211904, 13511073762115648, 10090880114674926608, 18332157638541321, 4902733343504729120, 141287311278592, 876517475657892112, 573945342918788, 35804994879488, 9223688714458236480, 72906490033217536, 844463587201024, 290486580455932032, 144117389246857344, 18295937913061888, 5406589228312428673, 9223442407754825769, 4503874543034369, 48431846559596576, 9295438429141602308, 18018869587216385, 3377768473690120, 2233852801025, 4785075694604450, 36030189125550082, 9227875774059790342, 9223407221495332992, 18332157638541321, 1155182100580696192, 35201820918080, 4612820723034226768, 45041495719411716, 603668391936256, 18084767791612032, 4612249107028804096, 15276649741245483264, 288371148000592256, 14628817498200408128, 1127068272296960, 4657003602455658752, 88510690829569, 2535612326510617, 144124055069409314, 2377918195506874625, 4611967527763988485, 1181631959278293026, 9232412290735146116, 261213468561048706 };
const bishop_magic: [64]Board.BoardType = .{ 9241390835893862432, 9234710240556097536, 1227239730261824640, 577604527957049348, 6918659473418780672, 351888113804529668, 2328924575914001472, 4614013701991826049, 2331927815258656, 9016004274033184, 2305860618585916944, 648597650767120384, 577061163230053010, 579277706207166480, 4614220409926336512, 580964498095089664, 4616199239888937985, 1126041776325888, 616996207142899796, 10141929783173120, 5630608183136385, 36873230555873632, 181275456685879328, 576743335520240152, 2603115769260982529, 2380875882396914688, 2289189183030272, 171145581949894664, 1153203048319827969, 2305913930985283584, 72657961755345152, 1127003718435328, 148904939935000608, 282608955041792, 1153062518049211400, 10378688279935975552, 290279660069376, 290279660069376, 6922619801915492428, 6922619801915492428, 1158067358212916246, 2328924575914001472, 36319360414908928, 4611703757044990977, 1224980203021800448, 4521260535005248, 290561443953836160, 2310347987528229122, 2328924575914001472, 564053894369288, 1585408924206502016, 4675139272704, 144185593598050818, 7440091971840, 4612816883383795840, 9234710240556097536, 4614013701991826049, 580964498095089664, 83334186434036744, 648597650767120384, 17592766972164, 9439545093914103944, 2331927815258656, 9241390835893862432 };
const rook_magic: [64]Board.BoardType = .{ 9259401108760043536, 594475563268280320, 144124055069409314, 72068610631143424, 72076285769418752, 108090789187420288, 324265770315940096, 9367487500631408770, 2305983747248373760, 14051934662285148160, 4612249107028804096, 72198881416318976, 9223653529145639440, 4648277836300289160, 1153204079162360320, 167055404054872320, 4791830278402211904, 13511073762115648, 10090880114674926608, 18332157638541321, 4902733343504729120, 141287311278592, 876517475657892112, 573945342918788, 35804994879488, 9223688714458236480, 72906490033217536, 844463587201024, 290486580455932032, 144117389246857344, 18295937913061888, 5406589228312428673, 9223442407754825769, 4503874543034369, 48431846559596576, 9295438429141602308, 18018869587216385, 3377768473690120, 2233852801025, 4785075694604450, 36030189125550082, 9227875774059790342, 9223407221495332992, 18332157638541321, 1155182100580696192, 35201820918080, 4612820723034226768, 45041495719411716, 603668391936256, 18084767791612032, 4612249107028804096, 15276649741245483264, 288371148000592256, 14628817498200408128, 1127068272296960, 4657003602455658752, 88510690829569, 2535612326510617, 144124055069409314, 2377918195506874625, 4611967527763988485, 1181631959278293026, 9232412290735146116, 261213468561048706 };
_ = @mulWithOverflow(Chess.BoardType, mut_occupancy, bishop_magic[square.int()], &mut_occupancy);
mut_occupancy >>= @intCast(Chess.SquareType, @as(u7, 64) - bishop_relevant_bits[square.int()]);
_ = @mulWithOverflow(Board.BoardType, mut_occupancy, bishop_magic[square.int()], &mut_occupancy);
mut_occupancy >>= @intCast(Board.SquareType, @as(u7, 64) - bishop_relevant_bits[square.int()]);
_ = @mulWithOverflow(Chess.BoardType, mut_occupancy, rook_magic[square.int()], &mut_occupancy);
mut_occupancy >>= @intCast(Chess.SquareType, @as(u7, 64) - rook_relevant_bits[square.int()]);
_ = @mulWithOverflow(Board.BoardType, mut_occupancy, rook_magic[square.int()], &mut_occupancy);
mut_occupancy >>= @intCast(Board.SquareType, @as(u7, 64) - rook_relevant_bits[square.int()]);
var expected = Chess.BitBoard{};
expected.setSlice(&[_]Chess.Square{ .h8, .a7, .d7, .g7, .b6, .d6, .f6, .c5, .d5, .e5, .a4, .b4, .c4, .e4, .f4, .c3, .d3, .e3, .d2, .f2, .d1 });
var expected = Board.BitBoard{};
expected.setSlice(&[_]Board.Square{ .h8, .a7, .d7, .g7, .b6, .d6, .f6, .c5, .d5, .e5, .a4, .b4, .c4, .e4, .f4, .c3, .d3, .e3, .d2, .f2, .d1 });
fn genPawnAttacks() [@typeInfo(Chess.Colors).Enum.fields.len][@typeInfo(Chess.Square).Enum.fields.len]Chess.BoardType {
var ret: [2][64]Chess.BoardType = [_][64]Chess.BoardType{[_]Chess.BoardType{0} ** 64} ** 2;
fn genPawnAttacks() [@typeInfo(Chess.Colors).Enum.fields.len][@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
var ret: [2][64]Board.BoardType = [_][64]Board.BoardType{[_]Board.BoardType{0} ** 64} ** 2;
fn genKnightAttacks() [@typeInfo(Chess.Square).Enum.fields.len]Chess.BoardType {
var ret: [64]Chess.BoardType = [_]Chess.BoardType{0} ** 64;
fn genKnightAttacks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;
fn genKingAttacks() [@typeInfo(Chess.Square).Enum.fields.len]Chess.BoardType {
var ret: [64]Chess.BoardType = [_]Chess.BoardType{0} ** 64;
fn genKingAttacks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;
fn genBishopMasks() [@typeInfo(Chess.Square).Enum.fields.len]Chess.BoardType {
var ret: [64]Chess.BoardType = [_]Chess.BoardType{0} ** 64;
fn genBishopMasks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;
fn genRookMasks() [@typeInfo(Chess.Square).Enum.fields.len]Chess.BoardType {
var ret: [64]Chess.BoardType = [_]Chess.BoardType{0} ** 64;
fn genRookMasks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;
var bb = Chess.BitBoard{};
bb.setSlice(&[_]Chess.Square{ .b1, .c1, .d1, .e1, .f1, .g2, .g3, .g4, .g5, .g6, .g7 });
var bb = Board.BitBoard{};
bb.setSlice(&[_]Board.Square{ .b1, .c1, .d1, .e1, .f1, .g2, .g3, .g4, .g5, .g6, .g7 });
var bb = Chess.BitBoard{};
bb.setSlice(&[_]Chess.Square{ .b1, .c1, .d1, .e1, .f1, .g1, .h2, .h3, .h4, .h5, .h6, .h7 });
var bb = Board.BitBoard{};
bb.setSlice(&[_]Board.Square{ .b1, .c1, .d1, .e1, .f1, .g1, .h2, .h3, .h4, .h5, .h6, .h7 });
const s = @intCast(Chess.SquareType, dr * Chess.SIZE + df);
const mask = @as(Chess.BoardType, 1) << s;
const s = @intCast(Board.SquareType, dr * Board.SIZE + df);
const mask = @as(Board.BoardType, 1) << s;
var bb = Chess.BitBoard{};
bb.setSlice(&[_]Chess.Square{ .c3, .f2, .e3, .c5, .b6, .e5, .f6, .g7, .h8 });
var bb = Board.BitBoard{};
bb.setSlice(&[_]Board.Square{ .c3, .f2, .e3, .c5, .b6, .e5, .f6, .g7, .h8 });
var bb = Chess.BitBoard{};
bb.setSlice(&[_]Chess.Square{ .c4, .b4, .d3, .d5, .e4, .f4, .g4, .h4 });
var bb = Board.BitBoard{};
bb.setSlice(&[_]Board.Square{ .c4, .b4, .d3, .d5, .e4, .f4, .g4, .h4 });
var bb = Chess.BitBoard{};
bb.setSlice(&[_]Chess.Square{ .b8, .c8, .d8, .e8, .f8, .g8, .h7, .h6, .h5, .h4, .h3, .h2, .h1 });
var bb = Board.BitBoard{};
bb.setSlice(&[_]Board.Square{ .b8, .c8, .d8, .e8, .f8, .g8, .h7, .h6, .h5, .h4, .h3, .h2, .h1 });
fn setOccupancy(index: usize, bits_in_mask: Chess.BoardType, attack_mask: Chess.BoardType) Chess.BoardType {
var occupancy: Chess.BoardType = 0;
fn setOccupancy(index: usize, bits_in_mask: Board.BoardType, attack_mask: Board.BoardType) Board.BoardType {
var occupancy: Board.BoardType = 0;
var expected: Chess.BitBoard = .{};
expected.setSlice(&[_]Chess.Square{ .a2, .a3, .a4, .a5, .a6, .a7, .b1, .c1, .d1, .e1, .f1, .g1 });
var expected: Board.BitBoard = .{};
expected.setSlice(&[_]Board.Square{ .a2, .a3, .a4, .a5, .a6, .a7, .b1, .c1, .d1, .e1, .f1, .g1 });
var tmp2: Chess.BoardType = undefined; // FIXME: ugly
_ = @mulWithOverflow(Chess.BoardType, occupancies[index], magic, &tmp2);
var magic_index: usize = @intCast(usize, tmp2 >> @intCast(Chess.SquareType, @as(u7, 64) - relevant));
var tmp2: Board.BoardType = undefined; // FIXME: ugly
_ = @mulWithOverflow(Board.BoardType, occupancies[index], magic, &tmp2);
var magic_index: usize = @intCast(usize, tmp2 >> @intCast(Board.SquareType, @as(u7, 64) - relevant));
const expected_bishop_magic: [64]Chess.BoardType = .{ 9241390835893862432, 9234710240556097536, 1227239730261824640, 577604527957049348, 6918659473418780672, 351888113804529668, 2328924575914001472, 4614013701991826049, 2331927815258656, 9016004274033184, 2305860618585916944, 648597650767120384, 577061163230053010, 579277706207166480, 4614220409926336512, 580964498095089664, 4616199239888937985, 1126041776325888, 616996207142899796, 10141929783173120, 5630608183136385, 36873230555873632, 181275456685879328, 576743335520240152, 2603115769260982529, 2380875882396914688, 2289189183030272, 171145581949894664, 1153203048319827969, 2305913930985283584, 72657961755345152, 1127003718435328, 148904939935000608, 282608955041792, 1153062518049211400, 10378688279935975552, 290279660069376, 290279660069376, 6922619801915492428, 6922619801915492428, 1158067358212916246, 2328924575914001472, 36319360414908928, 4611703757044990977, 1224980203021800448, 4521260535005248, 290561443953836160, 2310347987528229122, 2328924575914001472, 564053894369288, 1585408924206502016, 4675139272704, 144185593598050818, 7440091971840, 4612816883383795840, 9234710240556097536, 4614013701991826049, 580964498095089664, 83334186434036744, 648597650767120384, 17592766972164, 9439545093914103944, 2331927815258656, 9241390835893862432 };
const expected_bishop_magic: [64]Board.BoardType = .{ 9241390835893862432, 9234710240556097536, 1227239730261824640, 577604527957049348, 6918659473418780672, 351888113804529668, 2328924575914001472, 4614013701991826049, 2331927815258656, 9016004274033184, 2305860618585916944, 648597650767120384, 577061163230053010, 579277706207166480, 4614220409926336512, 580964498095089664, 4616199239888937985, 1126041776325888, 616996207142899796, 10141929783173120, 5630608183136385, 36873230555873632, 181275456685879328, 576743335520240152, 2603115769260982529, 2380875882396914688, 2289189183030272, 171145581949894664, 1153203048319827969, 2305913930985283584, 72657961755345152, 1127003718435328, 148904939935000608, 282608955041792, 1153062518049211400, 10378688279935975552, 290279660069376, 290279660069376, 6922619801915492428, 6922619801915492428, 1158067358212916246, 2328924575914001472, 36319360414908928, 4611703757044990977, 1224980203021800448, 4521260535005248, 290561443953836160, 2310347987528229122, 2328924575914001472, 564053894369288, 1585408924206502016, 4675139272704, 144185593598050818, 7440091971840, 4612816883383795840, 9234710240556097536, 4614013701991826049, 580964498095089664, 83334186434036744, 648597650767120384, 17592766972164, 9439545093914103944, 2331927815258656, 9241390835893862432 };
const expected_rook_magic: [64]Chess.BoardType = .{ 9259401108760043536, 594475563268280320, 144124055069409314, 72068610631143424, 72076285769418752, 108090789187420288, 324265770315940096, 9367487500631408770, 2305983747248373760, 14051934662285148160, 4612249107028804096, 72198881416318976, 9223653529145639440, 4648277836300289160, 1153204079162360320, 167055404054872320, 4791830278402211904, 13511073762115648, 10090880114674926608, 18332157638541321, 4902733343504729120, 141287311278592, 876517475657892112, 573945342918788, 35804994879488, 9223688714458236480, 72906490033217536, 844463587201024, 290486580455932032, 144117389246857344, 18295937913061888, 5406589228312428673, 9223442407754825769, 4503874543034369, 48431846559596576, 9295438429141602308, 18018869587216385, 3377768473690120, 2233852801025, 4785075694604450, 36030189125550082, 9227875774059790342, 9223407221495332992, 18332157638541321, 1155182100580696192, 35201820918080, 4612820723034226768, 45041495719411716, 603668391936256, 18084767791612032, 4612249107028804096, 15276649741245483264, 288371148000592256, 14628817498200408128, 1127068272296960, 4657003602455658752, 88510690829569, 2535612326510617, 144124055069409314, 2377918195506874625, 4611967527763988485, 1181631959278293026, 9232412290735146116, 261213468561048706 };
const expected_rook_magic: [64]Board.BoardType = .{ 9259401108760043536, 594475563268280320, 144124055069409314, 72068610631143424, 72076285769418752, 108090789187420288, 324265770315940096, 9367487500631408770, 2305983747248373760, 14051934662285148160, 4612249107028804096, 72198881416318976, 9223653529145639440, 4648277836300289160, 1153204079162360320, 167055404054872320, 4791830278402211904, 13511073762115648, 10090880114674926608, 18332157638541321, 4902733343504729120, 141287311278592, 876517475657892112, 573945342918788, 35804994879488, 9223688714458236480, 72906490033217536, 844463587201024, 290486580455932032, 144117389246857344, 18295937913061888, 5406589228312428673, 9223442407754825769, 4503874543034369, 48431846559596576, 9295438429141602308, 18018869587216385, 3377768473690120, 2233852801025, 4785075694604450, 36030189125550082, 9227875774059790342, 9223407221495332992, 18332157638541321, 1155182100580696192, 35201820918080, 4612820723034226768, 45041495719411716, 603668391936256, 18084767791612032, 4612249107028804096, 15276649741245483264, 288371148000592256, 14628817498200408128, 1127068272296960, 4657003602455658752, 88510690829569, 2535612326510617, 144124055069409314, 2377918195506874625, 4611967527763988485, 1181631959278293026, 9232412290735146116, 261213468561048706 };
};
}
};
const Castling = packed struct {
WK: bool = false,
WQ: bool = false,
BK: bool = false,
BQ: bool = false,
};
pub const SquareType = u6;
pub const Square = enum(SquareType) {
// zig fmt: off
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1,
// zig fmt: on
pub fn int(self: @This()) SquareType {
return @intCast(SquareType, @enumToInt(self));
}
pub fn rank(self: @This()) u3 {
return @intCast(u3, self.int() / SIZE);
}
pub fn file(self: @This()) u3 {
return @intCast(u3, self.int() % SIZE);
}
};
pub const BoardType = u64;
pub const BitBoard = packed struct(BoardType) {
// zig fmt: off
a8:bool=false, b8:bool=false, c8:bool=false, d8:bool=false, e8:bool=false, f8:bool=false, g8:bool=false, h8:bool=false,
a7:bool=false, b7:bool=false, c7:bool=false, d7:bool=false, e7:bool=false, f7:bool=false, g7:bool=false, h7:bool=false,
a6:bool=false, b6:bool=false, c6:bool=false, d6:bool=false, e6:bool=false, f6:bool=false, g6:bool=false, h6:bool=false,
a5:bool=false, b5:bool=false, c5:bool=false, d5:bool=false, e5:bool=false, f5:bool=false, g5:bool=false, h5:bool=false,
a4:bool=false, b4:bool=false, c4:bool=false, d4:bool=false, e4:bool=false, f4:bool=false, g4:bool=false, h4:bool=false,
a3:bool=false, b3:bool=false, c3:bool=false, d3:bool=false, e3:bool=false, f3:bool=false, g3:bool=false, h3:bool=false,
a2:bool=false, b2:bool=false, c2:bool=false, d2:bool=false, e2:bool=false, f2:bool=false, g2:bool=false, h2:bool=false,
a1:bool=false, b1:bool=false, c1:bool=false, d1:bool=false, e1:bool=false, f1:bool=false, g1:bool=false, h1:bool=false,
// zig fmt: on
fn set(self: *@This(), s: SquareType) void {
var ret: BoardType = @bitCast(BoardType, self.*);
ret |= @as(BoardType, 1) << s;
self.* = @bitCast(BitBoard, ret);
}
pub fn setSlice(self: *@This(), squares: []const Square) void {
var ret = @bitCast(BoardType, self.*);
for (squares) |square| {
ret |= @as(BoardType, 1) << square.int();
}
self.* = @bitCast(BitBoard, ret);
}
pub fn show(self: @This()) void {
std.debug.print("\n", .{});
std.debug.print("{s:>18}\n", .{"0 1 2 3 4 5 6 7"});
std.debug.print("{s:>18}\n", .{"---------------"});
var rank: usize = 0;
while (rank < SIZE) : (rank += 1) {
var file: usize = 0;
while (file < SIZE) : (file += 1) {
if (file == 0) std.debug.print("{d}| ", .{rank});
const square = @intCast(SquareType, rank * SIZE + file);
const mask: usize = if (@bitCast(BoardType, self) & (@as(BoardType, 1) << square) != 0) 1 else 0;
std.debug.print("{d} ", .{mask});
}
std.debug.print("|{d}\n", .{SIZE - rank});
}
std.debug.print("{s:>18}\n", .{"---------------"});
std.debug.print("{s:>18}\n", .{"a b c d e f g h"});
std.debug.print("\nBitBoard: {d}\n", .{@bitCast(BoardType, self)});
}
};
const GameState = struct {
bitboards: [12]BitBoard = blk: {
var bbs: [12]BitBoard = undefined;
for (bbs) |*bb| {
bb.* = BitBoard{};
}
break :blk bbs;
},
// TODO: try with separate both occupancy
occupancies: [@typeInfo(Colors).Enum.fields.len]BoardType = [_]BoardType{0} ** 2,
attacks: Attacks,
side: Colors = .white,
enemy: Colors = .black,
enpassant: ?Square = null,
castling: Castling = .{},
moves: std.BoundedArray(BitMove, 64) = std.BoundedArray(BitMove, 64).init(0) catch unreachable,
fn reset(self: *@This()) void {
for (self.bitboards) |*bb| {
bb.* = .{};
}
self.castling = .{};
self.occupancies = [_]BoardType{0} ** 2;
self.moves.resize(0) catch unreachable;
}
fn init(FEN: ?Str) !@This() {
var gs = GameState{ .attacks = Attacks.init() };
if (FEN != null)
try gs.parseFEN(FEN.?)
else
try gs.parseFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
return gs;
}
fn backup(self: @This()) @This() {
var new_gs: GameState = undefined;
for (self.bitboards) |bb, idx| {
new_gs.bitboards[idx] = bb;
}
for (self.occupancies) |occ, idx| {
new_gs.occupancies[idx] = occ;
}
new_gs.side = self.side;
new_gs.enpassant = self.enpassant;
new_gs.castling = self.castling;
return new_gs;
}
fn restore(self: *@This(), bck: @This()) void {
for (bck.bitboards) |bb, idx| {
self.bitboards[idx] = bb;
}
for (bck.occupancies) |occ, idx| {
self.occupancies[idx] = occ;
}
self.side = bck.side;
self.enpassant = bck.enpassant;
self.castling = bck.castling;
}
fn occupBoth(self: @This()) BoardType {
return self.occupancies[@enumToInt(Colors.white)] | self.occupancies[@enumToInt(Colors.black)];
}
fn show(self: @This()) void {
std.debug.print("\n", .{});
var rank: usize = 0;
while (rank < SIZE) : (rank += 1) {
var file: usize = 0;
FILE: while (file < SIZE) : (file += 1) {
if (file == 0) std.debug.print("{d}| ", .{SIZE - rank});
const square = @intCast(SquareType, rank * SIZE + file);
for (self.bitboards) |bb, idx| {
if (@bitCast(BoardType, bb) & (@as(BoardType, 1) << square) != 0) {
std.debug.print("{c} ", .{@intToEnum(PE, idx).char()});
continue :FILE;
}
}
std.debug.print(". ", .{});
}
std.debug.print("\n", .{});
}
std.debug.print("{s:>18}\n", .{"---------------"});
std.debug.print("{s:>18}\n", .{"a b c d e f g h"});
std.debug.print("Side to move: {any}\n", .{self.side});
std.debug.print("Enpassant squares: {any}\n", .{self.enpassant});
std.debug.print("Castling: {any}\n", .{self.castling});
std.debug.print("Possible moves counter: {d}\n", .{self.moves.len});
}
fn parseFEN(self: *@This(), in: []const u8) !void {
self.reset();
// std.debug.print("in: {s}\n", .{in});
// parse ranks
var rank: SquareType = 0;
var file: SquareType = 0;
var idx: usize = 0;
while (idx < in.len) : ({
idx += 1;
if (in[idx - 1] != '/') file += 1;
}) {
// std.debug.print("{c} {d}\n", .{ in[idx], rank * SIZE + file });
const ch = in[idx];
switch (ch) {
'/' => {
rank += 1;
file = 0;
},
'0'...'9' => {
file += try std.fmt.parseUnsigned(SquareType, in[idx .. idx + 1], 10) - 1;
},
65...90 => { // uppercase
self.occupancies[@enumToInt(Colors.white)] |= @as(BoardType, 1) << (rank * SIZE + file);
switch (ch) {
'P' => self.bitboards[PE.P.int()].set(rank * SIZE + file),
'N' => self.bitboards[PE.N.int()].set(rank * SIZE + file),
'B' => self.bitboards[PE.B.int()].set(rank * SIZE + file),
'R' => self.bitboards[PE.R.int()].set(rank * SIZE + file),
'Q' => self.bitboards[PE.Q.int()].set(rank * SIZE + file),
'K' => self.bitboards[PE.K.int()].set(rank * SIZE + file),
else => unreachable,
}
},
97...122 => { // lowercase
self.occupancies[@enumToInt(Colors.black)] |= @as(BoardType, 1) << (rank * SIZE + file);
switch (ch) {
'p' => self.bitboards[PE.p.int()].set(rank * SIZE + file),
'n' => self.bitboards[PE.n.int()].set(rank * SIZE + file),
'b' => self.bitboards[PE.b.int()].set(rank * SIZE + file),
'r' => self.bitboards[PE.r.int()].set(rank * SIZE + file),
'q' => self.bitboards[PE.q.int()].set(rank * SIZE + file),
'k' => self.bitboards[PE.k.int()].set(rank * SIZE + file),
else => unreachable,
}
},
' ' => break,
else => unreachable,
}
}
// parse rest
// std.debug.print("rest: {s}\n", .{in[idx..]});
var parts = std.mem.tokenize(u8, in[idx..], " ");
// side
const side = parts.next().?[0];
// std.debug.print("side: {c}\n", .{side});
switch (side) {
'w' => {
self.side = .white;
self.enemy = .black;
},
'b' => {
self.side = .black;
self.enemy = .white;
},
else => unreachable,
}
// castling
const castling = parts.next().?;
// std.debug.print("castling: {s}\n", .{castling});
for (castling) |ch| {
switch (ch) {
'K' => self.castling.WK = true,
'Q' => self.castling.WQ = true,
'k' => self.castling.BK = true,
'q' => self.castling.BQ = true,
'-' => break,
else => unreachable,
}
}
// enpassant
const enpassant = parts.next().?;
// std.debug.print("enpassant: {s}\n", .{enpassant});
if (enpassant[0] != '-') {
const f = enpassant[0] - 'a';
const r = SIZE - @intCast(SquareType, try std.fmt.parseUnsigned(u3, enpassant[1..], 10));
self.enpassant = @intToEnum(Square, r * SIZE + f);
}
return;
}
fn isSquareAttacked(self: @This(), square: Square, color: Colors) bool {
switch (color) {
.white => {
// attacked by white pawns
const pa = self.attacks.pawn_attacks[@enumToInt(Colors.black)][square.int()];
if (pa & @bitCast(BoardType, self.bitboards[PE.P.int()]) != 0) return true;
},
.black => {
const pa = self.attacks.pawn_attacks[@enumToInt(Colors.white)][square.int()];
if (pa & @bitCast(BoardType, self.bitboards[PE.p.int()]) != 0) return true;
},
}
// attacked by knight
const na = self.attacks.knight_attacks[square.int()];
const nbb = switch (color) {
.white => self.bitboards[PE.N.int()],
.black => self.bitboards[PE.n.int()],
};
if (na & @bitCast(BoardType, nbb) != 0) return true;
// attacked by king
const ka = self.attacks.king_attacks[square.int()];
const kbb = switch (color) {
.white => self.bitboards[PE.K.int()],
.black => self.bitboards[PE.k.int()],
};
if (ka & @bitCast(BoardType, kbb) != 0) return true;
// attacked by bishop
const ba = self.attacks.getBishopAttacks(square, self.occupBoth());
const bbb = switch (color) {
.white => self.bitboards[PE.B.int()],
.black => self.bitboards[PE.b.int()],
};
if (ba & @bitCast(BoardType, bbb) != 0) return true;
// attacked by rook
const ra = self.attacks.getRookAttacks(square, self.occupBoth());
const rbb = switch (color) {
.white => self.bitboards[PE.R.int()],
.black => self.bitboards[PE.r.int()],
};
if (ra & @bitCast(BoardType, rbb) != 0) return true;
// attacked by queen
const qa = self.attacks.getQueenAttacks(square, self.occupBoth());
const qbb = switch (color) {
.white => self.bitboards[PE.Q.int()],
.black => self.bitboards[PE.q.int()],
};
if (qa & @bitCast(BoardType, qbb) != 0) return true;
return false;
}
fn sideAttacks(self: @This(), color: Colors) BoardType {
var ret: BoardType = 0;
for (std.enums.values(Square)) |square| {
if (isSquareAttacked(self, square, color)) ret |= @as(BoardType, 1) << square.int();
}
return ret;
}
fn generateMoves(self: *@This()) !void {
const pieces = switch (self.side) {
.white => &[_]PE{ .P, .N, .B, .R, .Q, .K },
.black => &[_]PE{ .p, .n, .b, .r, .q, .k },
};
for (pieces) |piece| {
const board = @bitCast(BoardType, self.bitboards[piece.int()]);
// generate pawn and castling moves
switch (piece) {
.P, .p => try self.pawnMoves(board, piece),
.K, .k => {
try self.castlingMoves(piece);
try self.genMoves(board, piece);
},
.N, .n => try self.genMoves(board, piece),
.B, .b => try self.genMoves(board, piece),
.R, .r => try self.genMoves(board, piece),
.Q, .q => try self.genMoves(board, piece),
.none => unreachable,
}
}
}
fn pawnMoves(self: *@This(), board_in: BoardType, piece: PE) !void {
var source_square: SquareType = undefined;
var target_square: SquareType = undefined;
var attacks: BoardType = undefined;
const promote_options = switch (self.side) {
.white => [_]PE{ .Q, .R, .B, .N },
.black => [_]PE{ .q, .r, .b, .n },
var board = board_in;
while (board != 0) {
source_square = @intCast(SquareType, @ctz(board));
// std.debug.print("pawn: {any}\n", .{@intToEnum(Square, source_square)});
// early exit on underflow/overflow
switch (self.side) {
.white => if (@subWithOverflow(SquareType, source_square, 8, &target_square)) continue,
.black => if (@addWithOverflow(SquareType, source_square, 8, &target_square)) continue,
}
const promote_condition = switch (self.side) {
.white => source_square >= @enumToInt(Square.a7) and
source_square <= @enumToInt(Square.h7),
.black => source_square >= @enumToInt(Square.a2) and
source_square <= @enumToInt(Square.h2),
};
// generate quiet moves
if (self.occupBoth() & (@as(BoardType, 1) << target_square) == 0) {
const double_step_condition = switch (self.side) {
.white => source_square >= @enumToInt(Square.a2) and
source_square <= @enumToInt(Square.h2) and
self.occupBoth() & (@as(BoardType, 1) << (target_square - 8)) == 0,
.black => source_square >= @enumToInt(Square.a7) and
source_square <= @enumToInt(Square.h7) and
self.occupBoth() & (@as(BoardType, 1) << (target_square + 8)) == 0,
};
if (promote_condition) {
for (promote_options) |prom| {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.prom = prom,
});
}
} else {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
});
// TODO: check: if single step is no-go, double can not work
if (double_step_condition) {
const double_target = switch (self.side) {
.white => target_square - 8,
.black => target_square + 8,
};
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, double_target),
.piece = piece,
.double = true,
});
}
}
}
// generate attacks
attacks =
self.attacks.pawn_attacks[@enumToInt(self.side)][source_square] &
self.occupancies[@enumToInt(self.enemy)];
while (attacks != 0) {
target_square = @intCast(SquareType, @ctz(attacks));
if (promote_condition) {
for (promote_options) |prom| {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.prom = prom,
.capture = true,
});
}
} else {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.capture = true,
});
}
// pop processed attack bit
attacks ^= @as(BoardType, 1) << target_square;
}
// generate enpassant captures
if (self.enpassant != null) {
const enpassant_attacks = self.attacks.pawn_attacks[@enumToInt(self.side)][source_square] &
@as(BoardType, 1) << self.enpassant.?.int();
if (enpassant_attacks != 0) {
const target_enpassant: SquareType = @intCast(u6, @ctz(enpassant_attacks));
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_enpassant),
.piece = piece,
.capture = true,
});
}
}
// pop processed board bit
board ^= @as(BoardType, 1) << source_square;
}
}
fn castlingMoves(self: *@This(), piece: PE) !void {
{ // king-side castling
const king_side = switch (self.side) {
.white => [_]Square{ .f1, .g1 },
.black => [_]Square{ .f8, .g8 },
};
const attacked_king = switch (self.side) {
.white => [_]Square{ .e1, .f1 },
.black => [_]Square{ .e8, .f8 },
};
if ((self.side == .white and self.castling.WK) or
(self.side == .black and self.castling.BK))
{
const OK = blk: {
// make sure king side is emptry
for (king_side) |square| {
if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;
}
// check that castling squares are not attacked
for (attacked_king) |square| {
if (self.isSquareAttacked(square, self.enemy)) break :blk false;
}
break :blk true;
};
if (OK) {
try self.moves.append(BitMove{
.source = attacked_king[0],
.target = king_side[1],
.piece = piece,
});
}
}
}
{ // queen-side castling
const queen_side = switch (self.side) {
.white => [_]Square{ .d1, .c1, .b1 },
.black => [_]Square{ .d8, .c8, .b8 },
};
const attacked_queen = switch (self.side) {
.white => [_]Square{ .e1, .d1 },
.black => [_]Square{ .e8, .d8 },
};
if ((self.side == .white and self.castling.WQ) or
(self.side == .black and self.castling.BQ))
{
const OK = blk: {
// make sure queen side is emptry
for (queen_side) |square| {
if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;
}
// check that castling squares are not attacked
for (attacked_queen) |square| {
if (self.isSquareAttacked(square, self.enemy)) break :blk false;
}
break :blk true;
};
if (OK) {
try self.moves.append(BitMove{
.source = attacked_queen[0],
.target = queen_side[1],
.piece = piece,
});
}
}
}
}
fn genMoves(self: *@This(), board_in: BoardType, piece: PE) !void {
var source_square: SquareType = undefined;
var target_square: SquareType = undefined;
var attacks: BoardType = undefined;
var board = board_in;
while (board != 0) {
source_square = @intCast(SquareType, @ctz(board));
const attack_mask = switch (piece) {
.N, .n => self.attacks.knight_attacks[source_square],
.K, .k => self.attacks.king_attacks[source_square],
.B, .b => self.attacks.getBishopAttacks(
@intToEnum(Square, source_square),
self.occupBoth(),
),
.R, .r => self.attacks.getRookAttacks(
@intToEnum(Square, source_square),
self.occupBoth(),
),
.Q, .q => self.attacks.getQueenAttacks(
@intToEnum(Square, source_square),
self.occupBoth(),
),
.P, .p, .none => unreachable,
};
attacks = attack_mask &
~self.occupancies[@enumToInt(self.side)];
while (attacks != 0) {
target_square = @intCast(SquareType, @ctz(attacks));
if (self.occupancies[@enumToInt(self.enemy)] &
@as(BoardType, 1) << target_square != 0)
{
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.capture = true,
});
} else {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
});
}
attacks ^= @as(BoardType, 1) << target_square;
}
board ^= @as(BoardType, 1) << source_square;
}
pub const BitMoveType = u24;
const BitMove = packed struct(BitMoveType) {
source: Square,
target: Square,
piece: PE,
prom: PE = .none,
capture: bool = false,
double: bool = false,
enpassant: bool = false,
castling: bool = false,
};
test "isSquareAttacked" {
var game = try GameState.init("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1");
var bb = BitBoard{};
const got = game.sideAttacks(.white);
bb = @bitCast(BitBoard, got);
try std.testing.expectEqual(@as(BoardType, 9149624999898064896), got);
}
test "generateMoves" {
const tricky_position = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq c6 0 1";
var gs = try GameState.init(tricky_position);
try gs.generateMoves();
try std.testing.expectEqual(@as(usize, 49), gs.moves.len);
}
test "backup and restore" {
const tricky_position = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq c6 0 1";
var gs = try GameState.init(tricky_position);
const backup = gs.backup();
gs.bitboards[PE.P.int()].b8 = true;
gs.enpassant = null;
gs.castling = .{};
gs.side = .black;
gs.restore(backup);
try std.testing.expectEqual(false, gs.bitboards[PE.P.int()].h8);
try std.testing.expectEqual(Square.c6, gs.enpassant.?);
try std.testing.expectEqual(@as(u4, 0b1111), @bitCast(u4, gs.castling));
}
const std = @import("std");
const Str = []const u8;
const Chess = @import("Chess.zig");
const Attacks = @import("attacks.zig").Attacks;
/// SIZE is row and column size of the chessboard.
pub const SIZE = 8;
pub const SquareType = u6;
pub const Square = enum(SquareType) {
// zig fmt: off
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1,
// zig fmt: on
pub fn int(self: @This()) SquareType {
return @intCast(SquareType, @enumToInt(self));
}
pub fn rank(self: @This()) u3 {
return @intCast(u3, self.int() / SIZE);
}
pub fn file(self: @This()) u3 {
return @intCast(u3, self.int() % SIZE);
}
};
pub const BoardType = u64;
pub const BitBoard = packed struct(BoardType) {
// zig fmt: off
a8:bool=false, b8:bool=false, c8:bool=false, d8:bool=false, e8:bool=false, f8:bool=false, g8:bool=false, h8:bool=false,
a7:bool=false, b7:bool=false, c7:bool=false, d7:bool=false, e7:bool=false, f7:bool=false, g7:bool=false, h7:bool=false,
a6:bool=false, b6:bool=false, c6:bool=false, d6:bool=false, e6:bool=false, f6:bool=false, g6:bool=false, h6:bool=false,
a5:bool=false, b5:bool=false, c5:bool=false, d5:bool=false, e5:bool=false, f5:bool=false, g5:bool=false, h5:bool=false,
a4:bool=false, b4:bool=false, c4:bool=false, d4:bool=false, e4:bool=false, f4:bool=false, g4:bool=false, h4:bool=false,
a3:bool=false, b3:bool=false, c3:bool=false, d3:bool=false, e3:bool=false, f3:bool=false, g3:bool=false, h3:bool=false,
a2:bool=false, b2:bool=false, c2:bool=false, d2:bool=false, e2:bool=false, f2:bool=false, g2:bool=false, h2:bool=false,
a1:bool=false, b1:bool=false, c1:bool=false, d1:bool=false, e1:bool=false, f1:bool=false, g1:bool=false, h1:bool=false,
// zig fmt: on
fn set(self: *@This(), s: SquareType) void {
var ret: BoardType = @bitCast(BoardType, self.*);
ret |= @as(BoardType, 1) << s;
self.* = @bitCast(BitBoard, ret);
}
pub fn setSlice(self: *@This(), squares: []const Square) void {
var ret = @bitCast(BoardType, self.*);
for (squares) |square| {
ret |= @as(BoardType, 1) << square.int();
}
self.* = @bitCast(BitBoard, ret);
}
pub fn show(self: @This()) void {
std.debug.print("\n", .{});
std.debug.print("{s:>18}\n", .{"0 1 2 3 4 5 6 7"});
std.debug.print("{s:>18}\n", .{"---------------"});
var rank: usize = 0;
while (rank < SIZE) : (rank += 1) {
var file: usize = 0;
while (file < SIZE) : (file += 1) {
if (file == 0) std.debug.print("{d}| ", .{rank});
const square = @intCast(SquareType, rank * SIZE + file);
const mask: usize = if (@bitCast(BoardType, self) & (@as(BoardType, 1) << square) != 0) 1 else 0;
std.debug.print("{d} ", .{mask});
}
std.debug.print("|{d}\n", .{SIZE - rank});
}
std.debug.print("{s:>18}\n", .{"---------------"});
std.debug.print("{s:>18}\n", .{"a b c d e f g h"});
std.debug.print("\nBitBoard: {d}\n", .{@bitCast(BoardType, self)});
}
};
pub const BitMoveType = u24;
pub const BitMove = packed struct(BitMoveType) {
source: Square,
target: Square,
piece: Chess.PE,
prom: Chess.PE = .none,
capture: bool = false,
double: bool = false,
enpassant: bool = false,
castling: bool = false,
};
const Castling = packed struct {
WK: bool = false,
WQ: bool = false,
BK: bool = false,
BQ: bool = false,
};
pub const GameState = struct {
bitboards: [12]BitBoard = blk: {
var bbs: [12]BitBoard = undefined;
for (bbs) |*bb| {
bb.* = BitBoard{};
}
break :blk bbs;
},
// TODO: try with separate both occupancy
occupancies: [@typeInfo(Chess.Colors).Enum.fields.len]BoardType = [_]BoardType{0} ** 2,
attacks: Attacks,
side: Chess.Colors = .white,
enemy: Chess.Colors = .black,
enpassant: ?Square = null,
castling: Castling = .{},
moves: std.BoundedArray(BitMove, 64) = std.BoundedArray(BitMove, 64).init(0) catch unreachable,
fn reset(self: *@This()) void {
for (self.bitboards) |*bb| {
bb.* = .{};
}
self.castling = .{};
self.occupancies = [_]BoardType{0} ** 2;
self.moves.resize(0) catch unreachable;
}
pub fn init(FEN: ?Str) !@This() {
var gs = GameState{ .attacks = Attacks.init() };
if (FEN != null)
try gs.parseFEN(FEN.?)
else
try gs.parseFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
return gs;
}
fn backup(self: @This()) @This() {
var new_gs: GameState = undefined;
for (self.bitboards) |bb, idx| {
new_gs.bitboards[idx] = bb;
}
for (self.occupancies) |occ, idx| {
new_gs.occupancies[idx] = occ;
}
new_gs.side = self.side;
new_gs.enpassant = self.enpassant;
new_gs.castling = self.castling;
return new_gs;
}
fn restore(self: *@This(), bck: @This()) void {
for (bck.bitboards) |bb, idx| {
self.bitboards[idx] = bb;
}
for (bck.occupancies) |occ, idx| {
self.occupancies[idx] = occ;
}
self.side = bck.side;
self.enpassant = bck.enpassant;
self.castling = bck.castling;
}
fn occupBoth(self: @This()) BoardType {
return self.occupancies[@enumToInt(Chess.Colors.white)] |
self.occupancies[@enumToInt(Chess.Colors.black)];
}
fn show(self: @This()) void {
std.debug.print("\n", .{});
var rank: usize = 0;
while (rank < SIZE) : (rank += 1) {
var file: usize = 0;
FILE: while (file < SIZE) : (file += 1) {
if (file == 0) std.debug.print("{d}| ", .{SIZE - rank});
const square = @intCast(SquareType, rank * SIZE + file);
for (self.bitboards) |bb, idx| {
if (@bitCast(BoardType, bb) & (@as(BoardType, 1) << square) != 0) {
std.debug.print("{c} ", .{@intToEnum(Chess.PE, idx).char()});
continue :FILE;
}
}
std.debug.print(". ", .{});
}
std.debug.print("\n", .{});
}
std.debug.print("{s:>18}\n", .{"---------------"});
std.debug.print("{s:>18}\n", .{"a b c d e f g h"});
std.debug.print("Side to move: {any}\n", .{self.side});
std.debug.print("Enpassant squares: {any}\n", .{self.enpassant});
std.debug.print("Castling: {any}\n", .{self.castling});
std.debug.print("Possible moves counter: {d}\n", .{self.moves.len});
}
fn parseFEN(self: *@This(), in: []const u8) !void {
self.reset();
// std.debug.print("in: {s}\n", .{in});
// parse ranks
var rank: SquareType = 0;
var file: SquareType = 0;
var idx: usize = 0;
while (idx < in.len) : ({
idx += 1;
if (in[idx - 1] != '/') file += 1;
}) {
// std.debug.print("{c} {d}\n", .{ in[idx], rank * SIZE + file });
const ch = in[idx];
switch (ch) {
'/' => {
rank += 1;
file = 0;
},
'0'...'9' => {
file += try std.fmt.parseUnsigned(SquareType, in[idx .. idx + 1], 10) - 1;
},
65...90 => { // uppercase
self.occupancies[@enumToInt(Chess.Colors.white)] |=
@as(BoardType, 1) << (rank * SIZE + file);
switch (ch) {
'P' => self.bitboards[Chess.PE.P.int()].set(rank * SIZE + file),
'N' => self.bitboards[Chess.PE.N.int()].set(rank * SIZE + file),
'B' => self.bitboards[Chess.PE.B.int()].set(rank * SIZE + file),
'R' => self.bitboards[Chess.PE.R.int()].set(rank * SIZE + file),
'Q' => self.bitboards[Chess.PE.Q.int()].set(rank * SIZE + file),
'K' => self.bitboards[Chess.PE.K.int()].set(rank * SIZE + file),
else => unreachable,
}
},
97...122 => { // lowercase
self.occupancies[@enumToInt(Chess.Colors.black)] |=
@as(BoardType, 1) << (rank * SIZE + file);
switch (ch) {
'p' => self.bitboards[Chess.PE.p.int()].set(rank * SIZE + file),
'n' => self.bitboards[Chess.PE.n.int()].set(rank * SIZE + file),
'b' => self.bitboards[Chess.PE.b.int()].set(rank * SIZE + file),
'r' => self.bitboards[Chess.PE.r.int()].set(rank * SIZE + file),
'q' => self.bitboards[Chess.PE.q.int()].set(rank * SIZE + file),
'k' => self.bitboards[Chess.PE.k.int()].set(rank * SIZE + file),
else => unreachable,
}
},
' ' => break,
else => unreachable,
}
}
// parse rest
// std.debug.print("rest: {s}\n", .{in[idx..]});
var parts = std.mem.tokenize(u8, in[idx..], " ");
// side
const side = parts.next().?[0];
// std.debug.print("side: {c}\n", .{side});
switch (side) {
'w' => {
self.side = .white;
self.enemy = .black;
},
'b' => {
self.side = .black;
self.enemy = .white;
},
else => unreachable,
}
// castling
const castling = parts.next().?;
// std.debug.print("castling: {s}\n", .{castling});
for (castling) |ch| {
switch (ch) {
'K' => self.castling.WK = true,
'Q' => self.castling.WQ = true,
'k' => self.castling.BK = true,
'q' => self.castling.BQ = true,
'-' => break,
else => unreachable,
}
}
// enpassant
const enpassant = parts.next().?;
// std.debug.print("enpassant: {s}\n", .{enpassant});
if (enpassant[0] != '-') {
const f = enpassant[0] - 'a';
const r = SIZE - @intCast(
SquareType,
try std.fmt.parseUnsigned(u3, enpassant[1..], 10),
);
self.enpassant = @intToEnum(Square, r * SIZE + f);
}
return;
}
fn isSquareAttacked(self: @This(), square: Square, color: Chess.Colors) bool {
switch (color) {
.white => {
// attacked by white pawns
const pa = self.attacks.pawn_attacks[@enumToInt(Chess.Colors.black)][square.int()];
if (pa & @bitCast(BoardType, self.bitboards[Chess.PE.P.int()]) != 0) return true;
},
.black => {
const pa = self.attacks.pawn_attacks[@enumToInt(Chess.Colors.white)][square.int()];
if (pa & @bitCast(BoardType, self.bitboards[Chess.PE.p.int()]) != 0) return true;
},
}
// attacked by knight
const na = self.attacks.knight_attacks[square.int()];
const nbb = switch (color) {
.white => self.bitboards[Chess.PE.N.int()],
.black => self.bitboards[Chess.PE.n.int()],
};
if (na & @bitCast(BoardType, nbb) != 0) return true;
// attacked by king
const ka = self.attacks.king_attacks[square.int()];
const kbb = switch (color) {
.white => self.bitboards[Chess.PE.K.int()],
.black => self.bitboards[Chess.PE.k.int()],
};
if (ka & @bitCast(BoardType, kbb) != 0) return true;
// attacked by bishop
const ba = self.attacks.getBishopAttacks(square, self.occupBoth());
const bbb = switch (color) {
.white => self.bitboards[Chess.PE.B.int()],
.black => self.bitboards[Chess.PE.b.int()],
};
if (ba & @bitCast(BoardType, bbb) != 0) return true;
// attacked by rook
const ra = self.attacks.getRookAttacks(square, self.occupBoth());
const rbb = switch (color) {
.white => self.bitboards[Chess.PE.R.int()],
.black => self.bitboards[Chess.PE.r.int()],
};
if (ra & @bitCast(BoardType, rbb) != 0) return true;
// attacked by queen
const qa = self.attacks.getQueenAttacks(square, self.occupBoth());
const qbb = switch (color) {
.white => self.bitboards[Chess.PE.Q.int()],
.black => self.bitboards[Chess.PE.q.int()],
};
if (qa & @bitCast(BoardType, qbb) != 0) return true;
return false;
}
fn sideAttacks(self: @This(), color: Chess.Colors) BoardType {
var ret: BoardType = 0;
for (std.enums.values(Square)) |square| {
if (isSquareAttacked(self, square, color)) ret |= @as(BoardType, 1) << square.int();
}
return ret;
}
fn generateMoves(self: *@This()) !void {
const pieces = switch (self.side) {
.white => &[_]Chess.PE{ .P, .N, .B, .R, .Q, .K },
.black => &[_]Chess.PE{ .p, .n, .b, .r, .q, .k },
};
for (pieces) |piece| {
const board = @bitCast(BoardType, self.bitboards[piece.int()]);
// generate pawn and castling moves
switch (piece) {
.P, .p => try self.pawnMoves(board, piece),
.K, .k => {
try self.castlingMoves(piece);
try self.genMoves(board, piece);
},
.N, .n => try self.genMoves(board, piece),
.B, .b => try self.genMoves(board, piece),
.R, .r => try self.genMoves(board, piece),
.Q, .q => try self.genMoves(board, piece),
.none => unreachable,
}
}
}
fn pawnMoves(self: *@This(), board_in: BoardType, piece: Chess.PE) !void {
var source_square: SquareType = undefined;
var target_square: SquareType = undefined;
var attacks: BoardType = undefined;
const promote_options = switch (self.side) {
.white => [_]Chess.PE{ .Q, .R, .B, .N },
.black => [_]Chess.PE{ .q, .r, .b, .n },
};
var board = board_in;
while (board != 0) {
source_square = @intCast(SquareType, @ctz(board));
// early exit on underflow/overflow
switch (self.side) {
.white => if (@subWithOverflow(SquareType, source_square, 8, &target_square))
continue,
.black => if (@addWithOverflow(SquareType, source_square, 8, &target_square))
continue,
}
const promote_condition = switch (self.side) {
.white => source_square >= @enumToInt(Square.a7) and
source_square <= @enumToInt(Square.h7),
.black => source_square >= @enumToInt(Square.a2) and
source_square <= @enumToInt(Square.h2),
};
// generate quiet moves
if (self.occupBoth() & (@as(BoardType, 1) << target_square) == 0) {
const double_step_condition = switch (self.side) {
.white => source_square >= @enumToInt(Square.a2) and
source_square <= @enumToInt(Square.h2) and
self.occupBoth() & (@as(BoardType, 1) << (target_square - 8)) == 0,
.black => source_square >= @enumToInt(Square.a7) and
source_square <= @enumToInt(Square.h7) and
self.occupBoth() & (@as(BoardType, 1) << (target_square + 8)) == 0,
};
if (promote_condition) {
for (promote_options) |prom| {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.prom = prom,
});
}
} else {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
});
// TODO: check: if single step is no-go, double can not work
if (double_step_condition) {
const double_target = switch (self.side) {
.white => target_square - 8,
.black => target_square + 8,
};
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, double_target),
.piece = piece,
.double = true,
});
}
}
}
// generate attacks
attacks =
self.attacks.pawn_attacks[@enumToInt(self.side)][source_square] &
self.occupancies[@enumToInt(self.enemy)];
while (attacks != 0) {
target_square = @intCast(SquareType, @ctz(attacks));
if (promote_condition) {
for (promote_options) |prom| {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.prom = prom,
.capture = true,
});
}
} else {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.capture = true,
});
}
// pop processed attack bit
attacks ^= @as(BoardType, 1) << target_square;
}
// generate enpassant captures
if (self.enpassant != null) {
const enpassant_attacks = self.attacks.pawn_attacks[@enumToInt(self.side)][source_square] &
@as(BoardType, 1) << self.enpassant.?.int();
if (enpassant_attacks != 0) {
const target_enpassant: SquareType = @intCast(u6, @ctz(enpassant_attacks));
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_enpassant),
.piece = piece,
.capture = true,
});
}
}
// pop processed board bit
board ^= @as(BoardType, 1) << source_square;
}
}
fn castlingMoves(self: *@This(), piece: Chess.PE) !void {
{ // king-side castling
const king_side = switch (self.side) {
.white => [_]Square{ .f1, .g1 },
.black => [_]Square{ .f8, .g8 },
};
const attacked_king = switch (self.side) {
.white => [_]Square{ .e1, .f1 },
.black => [_]Square{ .e8, .f8 },
};
if ((self.side == .white and self.castling.WK) or
(self.side == .black and self.castling.BK))
{
const OK = blk: {
// make sure king side is emptry
for (king_side) |square| {
if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;
}
// check that castling squares are not attacked
for (attacked_king) |square| {
if (self.isSquareAttacked(square, self.enemy)) break :blk false;
}
break :blk true;
};
if (OK) {
try self.moves.append(BitMove{
.source = attacked_king[0],
.target = king_side[1],
.piece = piece,
});
}
}
}
{ // queen-side castling
const queen_side = switch (self.side) {
.white => [_]Square{ .d1, .c1, .b1 },
.black => [_]Square{ .d8, .c8, .b8 },
};
const attacked_queen = switch (self.side) {
.white => [_]Square{ .e1, .d1 },
.black => [_]Square{ .e8, .d8 },
};
if ((self.side == .white and self.castling.WQ) or
(self.side == .black and self.castling.BQ))
{
const OK = blk: {
// make sure queen side is emptry
for (queen_side) |square| {
if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;
}
// check that castling squares are not attacked
for (attacked_queen) |square| {
if (self.isSquareAttacked(square, self.enemy)) break :blk false;
}
break :blk true;
};
if (OK) {
try self.moves.append(BitMove{
.source = attacked_queen[0],
.target = queen_side[1],
.piece = piece,
});
}
}
}
}
fn genMoves(self: *@This(), board_in: BoardType, piece: Chess.PE) !void {
var source_square: SquareType = undefined;
var target_square: SquareType = undefined;
var attacks: BoardType = undefined;
var board = board_in;
while (board != 0) {
source_square = @intCast(SquareType, @ctz(board));
const attack_mask = switch (piece) {
.N, .n => self.attacks.knight_attacks[source_square],
.K, .k => self.attacks.king_attacks[source_square],
.B, .b => self.attacks.getBishopAttacks(
@intToEnum(Square, source_square),
self.occupBoth(),
),
.R, .r => self.attacks.getRookAttacks(
@intToEnum(Square, source_square),
self.occupBoth(),
),
.Q, .q => self.attacks.getQueenAttacks(
@intToEnum(Square, source_square),
self.occupBoth(),
),
.P, .p, .none => unreachable,
};
attacks = attack_mask &
~self.occupancies[@enumToInt(self.side)];
while (attacks != 0) {
target_square = @intCast(SquareType, @ctz(attacks));
if (self.occupancies[@enumToInt(self.enemy)] &
@as(BoardType, 1) << target_square != 0)
{
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
.capture = true,
});
} else {
try self.moves.append(BitMove{
.source = @intToEnum(Square, source_square),
.target = @intToEnum(Square, target_square),
.piece = piece,
});
}
attacks ^= @as(BoardType, 1) << target_square;
}
board ^= @as(BoardType, 1) << source_square;
}
}
};
test "isSquareAttacked" {
var game = try GameState.init("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1");
var bb = BitBoard{};
const got = game.sideAttacks(.white);
bb = @bitCast(BitBoard, got);
try std.testing.expectEqual(@as(BoardType, 9149624999898064896), got);
}
test "generateMoves" {
const tricky_position = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq c6 0 1";
var gs = try GameState.init(tricky_position);
try gs.generateMoves();
try std.testing.expectEqual(@as(usize, 49), gs.moves.len);
}
test "backup and restore" {
const tricky_position = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq c6 0 1";
var gs = try GameState.init(tricky_position);
const backup = gs.backup();
gs.bitboards[Chess.PE.P.int()].b8 = true;
gs.enpassant = null;
gs.castling = .{};
gs.side = .black;
gs.restore(backup);
try std.testing.expectEqual(false, gs.bitboards[Chess.PE.P.int()].h8);
try std.testing.expectEqual(Square.c6, gs.enpassant.?);
try std.testing.expectEqual(@as(u4, 0b1111), @bitCast(u4, gs.castling));
}