Created
April 20, 2025 02:02
-
-
Save elbeno/b021a2176adc177ad7a2917739b1de7c to your computer and use it in GitHub Desktop.
dice API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <array> | |
#include <concepts> | |
#include <cstddef> | |
#include <random> | |
#include <type_traits> | |
#include <utility> | |
namespace detail { | |
template <template <auto...> typename TList, auto K, std::size_t N> | |
consteval auto repeat_sequence() { | |
return [&]<std::size_t... Is>(std::index_sequence<Is...>) { | |
return TList<(static_cast<void>(Is), K)...>{}; | |
}(std::make_index_sequence<N>{}); | |
} | |
template <template <auto...> typename TList, auto K, std::size_t N> | |
using repeated_sequence_t = decltype(repeat_sequence<TList, K, N>()); | |
template <template <class T> class D> | |
concept distribution = requires(std::random_device &rng, D<int> &d) { | |
{ D<int>{0, 1} }; | |
{ d(rng) } -> std::same_as<int>; | |
}; | |
template <char... Cs> consteval auto num_dice() -> std::size_t { | |
constexpr auto base = 10; | |
std::size_t n{}; | |
return ((n *= base, n += Cs - '0'), ...); | |
} | |
template <template <typename T> typename Dist, auto... Sides> | |
requires distribution<Dist> | |
struct dice { | |
constexpr static auto num_dice = sizeof...(Sides); | |
template <typename T> struct result_t : std::array<T, num_dice> { | |
constexpr static auto size = | |
std::integral_constant<std::size_t, num_dice>{}; | |
}; | |
template <typename T = unsigned int> | |
auto roll(std::uniform_random_bit_generator auto &gen) const | |
-> result_t<T> { | |
auto roll = [&]<auto N>() { | |
Dist<T> d{1, N}; | |
return d(gen); | |
}; | |
return { roll.template operator()<Sides>()... }; | |
} | |
}; | |
} // namespace detail | |
template <auto... Sides> struct dice { | |
using type = detail::dice<std::uniform_int_distribution, Sides...>; | |
}; | |
template <auto... Sides> using dice_t = typename dice<Sides...>::type; | |
namespace literals { | |
template <char... Cs> consteval auto operator""_d4() { | |
return | |
typename detail::repeated_sequence_t<dice, 4, | |
detail::num_dice<Cs...>()>::type{}; | |
} | |
template <char... Cs> consteval auto operator""_d6() { | |
return | |
typename detail::repeated_sequence_t<dice, 6, | |
detail::num_dice<Cs...>()>::type{}; | |
} | |
template <char... Cs> consteval auto operator""_d8() { | |
return | |
typename detail::repeated_sequence_t<dice, 8, | |
detail::num_dice<Cs...>()>::type{}; | |
} | |
template <char... Cs> consteval auto operator""_d10() { | |
return | |
typename detail::repeated_sequence_t<dice, 10, | |
detail::num_dice<Cs...>()>::type{}; | |
} | |
template <char... Cs> consteval auto operator""_d12() { | |
return | |
typename detail::repeated_sequence_t<dice, 12, | |
detail::num_dice<Cs...>()>::type{}; | |
}template <char... Cs> consteval auto operator""_d20() { | |
return | |
typename detail::repeated_sequence_t<dice, 20, | |
detail::num_dice<Cs...>()>::type{}; | |
} | |
} // namespace literals |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <catch2/catch_test_macros.hpp> | |
#include <concepts> | |
#include <cstdint> | |
#include <iterator> | |
#include <limits> | |
#include <random> | |
#include <type_traits> | |
#include <utility> | |
#include <dice.hpp> | |
template <auto Value> struct fixed { | |
template <typename T> struct dist { | |
dist(T, T = std::numeric_limits<T>::max) {} | |
auto operator()(std::uniform_random_bit_generator auto &) { | |
return T{Value}; | |
} | |
}; | |
}; | |
template <auto Value, auto... Sides> | |
using test_dice = detail::dice<fixed<Value>::template dist, Sides...>; | |
namespace { | |
std::random_device rng{}; | |
} | |
TEST_CASE("roll one d8", "[dice]") { | |
constexpr auto sides = 8u; | |
auto const die = test_dice<42, sides>{}; | |
auto const result = die.roll(rng); | |
REQUIRE(1 == std::size(result)); | |
CHECK(42 == result[0]); | |
} | |
TEST_CASE("roll one die and specify the result type", "[dice]") { | |
auto const die = test_dice<42, 8u>{}; | |
using roll_t = decltype(die.roll<std::uint8_t>(rng)); | |
static_assert(roll_t::size() == 1); | |
static_assert(std::is_same_v<std::uint8_t, typename roll_t::value_type>); | |
} | |
TEST_CASE("roll three dice", "[dice]") { | |
auto const die = test_dice<42, 6, 8, 10>{}; | |
auto const result = die.roll(rng); | |
REQUIRE(3 == std::size(result)); | |
CHECK(result[0] == 42); | |
CHECK(result[1] == 42); | |
CHECK(result[2] == 42); | |
} | |
TEST_CASE("use UDL (one die)", "[dice]") { | |
using namespace literals; | |
auto const x = 1_d6; | |
static_assert(std::is_same_v<decltype(x), dice_t<6> const>); | |
using roll_t = decltype(x.roll(rng)); | |
static_assert(roll_t::size() == 1); | |
} | |
TEST_CASE("use UDL (three dice)", "[dice]") { | |
using namespace literals; | |
auto const x = 3_d10; | |
static_assert( | |
std::is_same_v<decltype(x), dice_t<10, 10, 10> const>); | |
using roll_t = decltype(x.roll(rng)); | |
static_assert(roll_t::size() == 3); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment