Skip to content

Instantly share code, notes, and snippets.

@elbeno
Created April 20, 2025 02:02
Show Gist options
  • Save elbeno/b021a2176adc177ad7a2917739b1de7c to your computer and use it in GitHub Desktop.
Save elbeno/b021a2176adc177ad7a2917739b1de7c to your computer and use it in GitHub Desktop.
dice API
#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
#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