const std = @import("std"); const Chess = @import("Chess.zig"); const Board = @import("Board.zig"); pub const Attacks = struct { // leaper pieces pawn_attacks: [2][64]Board.BoardType = genPawnAttacks(), king_attacks: [64]Board.BoardType = genKingAttacks(), knight_attacks: [64]Board.BoardType = genKnightAttacks(), // rider pieces 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 }; const bishop_masks = genBishopMasks(); const rook_masks = genRookMasks(); const bishop_relevant_bits = genRelevantOccupancy(.bishop); const rook_relevant_bits = genRelevantOccupancy(.rook); var bishop_attacks: [64][512]Board.BoardType = undefined; var rook_attacks: [64][4096]Board.BoardType = undefined; pub fn init() @This() { var attacks: @This() = .{}; // queen attacks are generated by merging bishop and rook attacks initSliderAttacks(.bishop); initSliderAttacks(.rook); return attacks; } fn initSliderAttacks(piece: Chess.Pieces) void { for (std.enums.values(Board.Square)) |square| { const square_int = @intFromEnum(square); const attack_mask = switch (piece) { .bishop => bishop_masks[square_int], .rook => rook_masks[square_int], else => unreachable, }; // init relevant occupancy bit count const relevant_bits = @popCount(attack_mask); const occupancy_idx = @as(Board.BoardType, 1) << @as(Board.SquareType, @intCast(relevant_bits)); var idx: usize = 0; while (idx < occupancy_idx) : (idx += 1) { switch (piece) { .bishop => { const occupancy = setOccupancy(idx, relevant_bits, attack_mask); const res = @mulWithOverflow(occupancy, bishop_magic[square_int])[0]; const magic_index = res >> @as(Board.SquareType, @intCast(@as(u7, 64) - bishop_relevant_bits[square_int])); bishop_attacks[square_int][magic_index] = attackWithBlocker(occupancy, square, .bishop); }, .rook => { const occupancy = setOccupancy(idx, relevant_bits, attack_mask); const res = @mulWithOverflow(occupancy, rook_magic[@intFromEnum(square)])[0]; const magic_index = res >> @as(Board.SquareType, @intCast(@as(u7, 64) - rook_relevant_bits[square_int])); rook_attacks[square_int][magic_index] = attackWithBlocker(occupancy, square, .rook); }, else => unreachable, } } } } pub fn getBishopAttacks(self: @This(), square: Board.SquareType, occupancy: Board.BoardType) *Board.BoardType { _ = self; var mut_occupancy: Board.BoardType = occupancy; mut_occupancy &= bishop_masks[square]; mut_occupancy = @mulWithOverflow(mut_occupancy, bishop_magic[square])[0]; mut_occupancy >>= @as(Board.SquareType, @intCast(@as(u7, 64) - bishop_relevant_bits[square])); return &bishop_attacks[square][mut_occupancy]; } pub fn getRookAttacks(self: @This(), square: Board.SquareType, occupancy: Board.BoardType) *Board.BoardType { _ = self; var mut_occupancy: Board.BoardType = occupancy; mut_occupancy &= rook_masks[square]; mut_occupancy = @mulWithOverflow(mut_occupancy, rook_magic[square])[0]; mut_occupancy >>= @as(Board.SquareType, @intCast(@as(u7, 64) - rook_relevant_bits[square])); return &rook_attacks[square][mut_occupancy]; } pub fn getQueenAttacks(self: @This(), square: Board.SquareType, occupancy: Board.BoardType) Board.BoardType { return getBishopAttacks(self, square, occupancy).* | getRookAttacks(self, square, occupancy).*; } }; test "getQueenAttacks" { var attack = Attacks.init(); var occupancy = Board.BitBoard{}; occupancy.setSlice(&[_]Board.Square{ .d7, .f2, .c3, .f4 }); const got = attack.getQueenAttacks( @intFromEnum(Board.Square.d4), @as(Board.BoardType, @bitCast(occupancy)), ); 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 }); try std.testing.expectEqual(@as(Board.BoardType, @bitCast(expected)), got); } 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; for (std.enums.values(Chess.Colors)) |color| { for (std.enums.values(Board.Square)) |square| { ret[@intFromEnum(color)][@intFromEnum(square)] = genSteps(square, .pawn, color); } } return ret; } test "PawnAttacks" { const pa = genPawnAttacks(); { // White { var bb = Board.BitBoard{}; bb.setSlice(&[2]Board.Square{ .c5, .e5 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), pa[@intFromEnum(Chess.Colors.white)][@intFromEnum(Board.Square.d4)], ); } { // no left attack var bb = Board.BitBoard{}; bb.b5 = true; try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), pa[@intFromEnum(Chess.Colors.white)][@intFromEnum(Board.Square.a4)], ); } { // no right attack var bb = Board.BitBoard{}; bb.g5 = true; try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), pa[@intFromEnum(Chess.Colors.white)][@intFromEnum(Board.Square.h4)], ); } } { // Black { var bb = Board.BitBoard{}; bb.setSlice(&[2]Board.Square{ .c3, .e3 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), pa[@intFromEnum(Chess.Colors.black)][@intFromEnum(Board.Square.d4)], ); } { // no left attack var bb = Board.BitBoard{}; bb.b3 = true; try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), pa[@intFromEnum(Chess.Colors.black)][@intFromEnum(Board.Square.a4)], ); } { // no right attack var bb = Board.BitBoard{}; bb.g3 = true; try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), pa[@intFromEnum(Chess.Colors.black)][@intFromEnum(Board.Square.h4)], ); } } } fn genKnightAttacks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType { var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64; for (std.enums.values(Board.Square)) |square| { ret[@intFromEnum(square)] = genSteps(square, .knight, .white); } return ret; } test "KnightAttacks" { const ka = genKnightAttacks(); { // full move var bb = Board.BitBoard{}; bb.setSlice(&[8]Board.Square{ .c7, .e7, .b6, .f6, .c3, .e3, .b4, .f4 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.d5)], ); } { // only 6 move var bb = Board.BitBoard{}; bb.setSlice(&[6]Board.Square{ .f7, .h7, .e6, .e4, .f3, .h3 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.g5)], ); } { // only 4 move var bb = Board.BitBoard{}; bb.setSlice(&[4]Board.Square{ .b7, .c6, .c4, .b3 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.a5)], ); } } fn genKingAttacks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType { var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64; for (std.enums.values(Board.Square)) |square| { ret[@intFromEnum(square)] = genSteps(square, .king, .white); } return ret; } test "KingAttacks" { const ka = genKingAttacks(); { // full move var bb = Board.BitBoard{}; bb.setSlice(&[8]Board.Square{ .c5, .d5, .e5, .c3, .d3, .e3, .c4, .e4 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.d4)], ); } { // sides - 5 moves var bb = Board.BitBoard{}; bb.setSlice(&[5]Board.Square{ .a5, .a3, .b5, .b4, .b3 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.a4)], ); } { // sides - 5 moves var bb = Board.BitBoard{}; bb.setSlice(&[5]Board.Square{ .c8, .e8, .c7, .d7, .e7 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.d8)], ); } { // corner - 3 moves var bb = Board.BitBoard{}; bb.setSlice(&[3]Board.Square{ .g1, .g2, .h2 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ka[@intFromEnum(Board.Square.h1)], ); } } fn genBishopMasks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType { var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64; for (std.enums.values(Board.Square)) |square| { ret[@intFromEnum(square)] = genSteps(square, .bishop, .white); } return ret; } test "BishopMasks" { const ba = genBishopMasks(); { var bb = Board.BitBoard{}; bb.setSlice(&[_]Board.Square{ .b2, .c3, .d4, .e5, .f6, .g7 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ba[@intFromEnum(Board.Square.a1)], ); } } fn genRookMasks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType { var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64; for (std.enums.values(Board.Square)) |square| { ret[@intFromEnum(square)] = genSteps(square, .rook, .white); } return ret; } test "RookMasks" { const ra = genRookMasks(); { var bb = Board.BitBoard{}; bb.setSlice(&[_]Board.Square{ .b1, .c1, .d1, .e1, .f1, .g2, .g3, .g4, .g5, .g6, .g7 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ra[@intFromEnum(Board.Square.g1)], ); } { var bb = Board.BitBoard{}; bb.setSlice(&[_]Board.Square{ .b1, .c1, .d1, .e1, .f1, .g1, .h2, .h3, .h4, .h5, .h6, .h7 }); try std.testing.expectEqual( @as(Board.BoardType, @bitCast(bb)), ra[@intFromEnum(Board.Square.h1)], ); } } fn genSteps(square: Board.Square, piece: Chess.Pieces, color: Chess.Colors) Board.BoardType { const rank = @as(isize, @intCast(square.rank())); const file = @as(isize, @intCast(square.file())); const rider = switch (piece) { .pawn => false, .knight => false, .bishop => true, .rook => true, .king => false, .queen => unreachable, }; // TODO: I repeat some moves here to make zig happy, but ugly. const moves = switch (piece) { .pawn => switch (color) { .white => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ -1, -1 }, .{ -1, 1 }, .{ -1, -1 }, .{ -1, 1 }, .{ -1, -1 }, .{ -1, 1 } }, .black => [_][2]i3{ .{ 1, -1 }, .{ 1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ 1, -1 }, .{ 1, 1 } }, }, .knight => [_][2]i3{ .{ -2, -1 }, .{ -2, 1 }, .{ 2, -1 }, .{ 2, 1 }, .{ -1, -2 }, .{ 1, 2 }, .{ -1, 2 }, .{ 1, -2 } }, .bishop => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 } }, .king => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ -1, 0 }, .{ 0, 1 }, .{ 1, 0 }, .{ 0, -1 } }, .rook => [_][2]i3{ .{ -1, 0 }, .{ 1, 0 }, .{ 0, -1 }, .{ 0, 1 }, .{ -1, 0 }, .{ 1, 0 }, .{ 0, -1 }, .{ 0, 1 } }, .queen => unreachable, }; var attacks: Board.BoardType = 0; @setEvalBranchQuota(10_000); var step: i5 = 1; while (step < Board.SIZE - 1) : (step += 1) { for (moves) |m| { const dr = rank + m[0] * step; const df = file + m[1] * step; switch (rider) { true => { if (m[0] > 0 and dr > 6) continue; if (m[0] < 0 and dr < 1) continue; if (m[1] > 0 and df > 6) continue; if (m[1] < 0 and df < 1) continue; }, false => { if (dr < 0 or dr > 7) continue; if (df < 0 or df > 7) continue; }, } const s = @as(Board.SquareType, @intCast(dr * Board.SIZE + df)); attacks |= @as(Board.BoardType, 1) << s; } if (!rider) break; } return attacks; } fn attackWithBlocker(blocks: Board.BoardType, square: Board.Square, piece: Chess.Pieces) Board.BoardType { const rank = @as(isize, @intCast(square.rank())); const file = @as(isize, @intCast(square.file())); const moves = switch (piece) { .bishop => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 }, }, .rook => [_][2]i3{ .{ -1, 0 }, .{ 1, 0 }, .{ 0, -1 }, .{ 0, 1 }, }, else => unreachable, }; var attacks: Board.BoardType = 0; for (moves) |m| { var step: i5 = 1; while (step < Board.SIZE) : (step += 1) { const dr = rank + m[0] * step; const df = file + m[1] * step; if (m[0] > 0 and dr > 7) break; if (m[0] < 0 and dr < 0) break; if (m[1] > 0 and df > 7) break; if (m[1] < 0 and df < 0) break; const s = @as(Board.SquareType, @intCast(dr * Board.SIZE + df)); const mask = @as(Board.BoardType, 1) << s; attacks |= mask; // stop when we hit a blocker if (mask & blocks != 0) break; } } return attacks; } test "attackWithBlocker" { { // bishop d4 var blocks = Board.BitBoard{}; blocks.setSlice(&[_]Board.Square{ .c3, .b6, .f2 }); const got = attackWithBlocker(@as(Board.BoardType, @bitCast(blocks)), .d4, .bishop); var bb = Board.BitBoard{}; bb.setSlice(&[_]Board.Square{ .c3, .f2, .e3, .c5, .b6, .e5, .f6, .g7, .h8 }); try std.testing.expectEqual(bb, @as(Board.BitBoard, @bitCast(got))); } { // rook d4 var blocks = Board.BitBoard{}; blocks.setSlice(&[_]Board.Square{ .b4, .d3, .d5 }); const got = attackWithBlocker(@as(Board.BoardType, @bitCast(blocks)), .d4, .rook); var bb = Board.BitBoard{}; bb.setSlice(&[_]Board.Square{ .c4, .b4, .d3, .d5, .e4, .f4, .g4, .h4 }); try std.testing.expectEqual(bb, @as(Board.BitBoard, @bitCast(got))); } { // rook h8 var blocks = Board.BitBoard{}; blocks.setSlice(&[_]Board.Square{.b8}); const got = attackWithBlocker(@as(Board.BoardType, @bitCast(blocks)), .h8, .rook); var bb = Board.BitBoard{}; bb.setSlice(&[_]Board.Square{ .b8, .c8, .d8, .e8, .f8, .g8, .h7, .h6, .h5, .h4, .h3, .h2, .h1 }); try std.testing.expectEqual(bb, @as(Board.BitBoard, @bitCast(got))); } } fn setOccupancy(index: usize, bits_in_mask: Board.BoardType, attack_mask: Board.BoardType) Board.BoardType { var occupancy: Board.BoardType = 0; var attack: Board.BoardType = attack_mask; var count: Board.SquareType = 0; while (count < bits_in_mask) : (count += 1) { const square = @as(Board.SquareType, @intCast(@ctz(attack))); // pop square bit // if (attack & @as(Board.BoardType, 1) << square != 0) attack ^= @as(Board.BoardType, 1) << square; if (index & @as(Board.BoardType, 1) << count != 0) { occupancy |= @as(Board.BoardType, 1) << square; } } return occupancy; } test "occupancy bits" { const attack_mask = genSteps(.a1, .rook, .white); var occupancy: Board.BoardType = 0; occupancy = setOccupancy(4095, @popCount(attack_mask), attack_mask); var expected: Board.BitBoard = .{}; expected.setSlice(&[_]Board.Square{ .a2, .a3, .a4, .a5, .a6, .a7, .b1, .c1, .d1, .e1, .f1, .g1 }); try std.testing.expectEqual(expected, @as(Board.BitBoard, @bitCast(occupancy))); } fn genRelevantOccupancy(piece: Chess.Pieces) [@typeInfo(Board.Square).Enum.fields.len]u4 { var ret: [64]u4 = [_]u4{0} ** 64; for (std.enums.values(Board.Square)) |square| { const count = @popCount(genSteps(square, piece, .white)); ret[@intFromEnum(square)] = @as(u4, @intCast(count)); } return ret; } test "genRelevantOccupancy" { { // bishop const bishop = genRelevantOccupancy(.bishop); // zig fmt: off const expected = [64]u4{ 6, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 5, 5, 5, 5, 7, 9, 9, 7, 5, 5, 5, 5, 7, 9, 9, 7, 5, 5, 5, 5, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 6, }; // zig fmt: on try std.testing.expectEqualSlices(u4, &expected, &bishop); } { // rook const rook = genRelevantOccupancy(.rook); // zig fmt: off const expected = [64]u4{ 12, 11, 11, 11, 11, 11, 11, 12, 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, 12, 11, 11, 11, 11, 11, 11, 12, }; // zig fmt: on try std.testing.expectEqualSlices(u4, &expected, &rook); } } fn findMagicNumber(square: Board.Square, relevant: u4, piece: Chess.Pieces) Board.BoardType { // this is for rooks, bishop needs less var occupancies: [4096]Board.BoardType = undefined; var attacks: [4096]Board.BoardType = undefined; var used: [4096]Board.BoardType = undefined; // attack mask for a current piece const attack_mask = genSteps(square, piece, .white); const occupancy_idx = @as(Board.BoardType, 1) << relevant; var idx: usize = 0; while (idx < occupancy_idx) : (idx += 1) { occupancies[idx] = setOccupancy(idx, relevant, attack_mask); attacks[idx] = attackWithBlocker(occupancies[idx], square, piece); } var rnd = std.rand.DefaultPrng.init(0); // test the generated number var loop: usize = 0; while (loop < 100_000_000) : (loop += 1) { const magic = rnd.next() & rnd.next() & rnd.next(); // skip inappropriate numbers FIXME: ugly const tmp1 = @mulWithOverflow(attack_mask, magic)[0]; if (@popCount(tmp1 & 0xFF00000000000000) < 6) continue; used = [_]Board.BoardType{0} ** 4096; var index: usize = 0; var fail: bool = false; while (!fail and index < occupancy_idx) : (index += 1) { const tmp2 = @mulWithOverflow(occupancies[index], magic)[0]; var magic_index: usize = @as(usize, @intCast(tmp2 >> @as(Board.SquareType, @intCast(@as(u7, 64) - relevant)))); if (used[magic_index] == 0) { // magic index works used[magic_index] = attacks[index]; } else if (used[magic_index] != attacks[index]) { // magic index does not work fail = true; } } if (!fail) { return magic; } } unreachable; } test "BishopMagicNumbers" { if (true) return error.SkipZigTest; // slow var bishop_magic: [64]Board.BoardType = undefined; for (std.enums.values(Board.Square), 0..) |square, idx| { bishop_magic[idx] = findMagicNumber( square, genRelevantOccupancy(.bishop)[@intFromEnum(square)], .bishop, ); } 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 }; try std.testing.expectEqualSlices(Board.BoardType, &expected_bishop_magic, &bishop_magic); } test "RookMagicNumbers" { if (true) return error.SkipZigTest; // slow var rook_magic: [64]Board.BoardType = undefined; for (std.enums.values(Board.Square), 0..) |square, idx| { rook_magic[idx] = findMagicNumber( square, genRelevantOccupancy(.rook)[@intFromEnum(square)], .rook, ); } 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 }; try std.testing.expectEqualSlices(Board.BoardType, &expected_rook_magic, &rook_magic); }