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: offa8, 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: onpub 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: offa8: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: onfn 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 occupancyoccupancies: [@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.?)elsetry 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 ranksvar 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 => { // uppercaseself.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 => { // lowercaseself.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..], " ");// sideconst 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,}// castlingconst 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,}}// enpassantconst 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 pawnsconst 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 knightconst 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 kingconst 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 bishopconst 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 rookconst 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 queenconst 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 movesswitch (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/overflowswitch (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) andsource_square <= @enumToInt(Square.h7),.black => source_square >= @enumToInt(Square.a2) andsource_square <= @enumToInt(Square.h2),};// generate quiet movesif (self.occupBoth() & (@as(BoardType, 1) << target_square) == 0) {const double_step_condition = switch (self.side) {.white => source_square >= @enumToInt(Square.a2) andsource_square <= @enumToInt(Square.h2) andself.occupBoth() & (@as(BoardType, 1) << (target_square - 8)) == 0,.black => source_square >= @enumToInt(Square.a7) andsource_square <= @enumToInt(Square.h7) andself.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 workif (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 attacksattacks =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 bitattacks ^= @as(BoardType, 1) << target_square;}// generate enpassant capturesif (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 bitboard ^= @as(BoardType, 1) << source_square;}}fn castlingMoves(self: *@This(), piece: PE) !void {{ // king-side castlingconst 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 emptryfor (king_side) |square| {if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;}// check that castling squares are not attackedfor (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 castlingconst 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 emptryfor (queen_side) |square| {if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;}// check that castling squares are not attackedfor (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: offa8, 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: onpub 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: offa8: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: onfn 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 occupancyoccupancies: [@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.?)elsetry 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 ranksvar 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 => { // uppercaseself.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 => { // lowercaseself.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..], " ");// sideconst 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,}// castlingconst 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,}}// enpassantconst 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 pawnsconst 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 knightconst 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 kingconst 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 bishopconst 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 rookconst 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 queenconst 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 movesswitch (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/overflowswitch (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) andsource_square <= @enumToInt(Square.h7),.black => source_square >= @enumToInt(Square.a2) andsource_square <= @enumToInt(Square.h2),};// generate quiet movesif (self.occupBoth() & (@as(BoardType, 1) << target_square) == 0) {const double_step_condition = switch (self.side) {.white => source_square >= @enumToInt(Square.a2) andsource_square <= @enumToInt(Square.h2) andself.occupBoth() & (@as(BoardType, 1) << (target_square - 8)) == 0,.black => source_square >= @enumToInt(Square.a7) andsource_square <= @enumToInt(Square.h7) andself.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 workif (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 attacksattacks =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 bitattacks ^= @as(BoardType, 1) << target_square;}// generate enpassant capturesif (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 bitboard ^= @as(BoardType, 1) << source_square;}}fn castlingMoves(self: *@This(), piece: Chess.PE) !void {{ // king-side castlingconst 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 emptryfor (king_side) |square| {if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;}// check that castling squares are not attackedfor (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 castlingconst 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 emptryfor (queen_side) |square| {if (self.occupBoth() & @as(BoardType, 1) << square.int() != 0) break :blk false;}// check that castling squares are not attackedfor (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));}