Card 1 / 5
Modern C++ random numbers separate the engine (raw random bits) from the distribution (the shape you want). Write roll_sum: roll a fair six-sided die n times with a std::mt19937 engine seeded by seed, and return the sum.
seed |
n |
result |
|---|---|---|
42 |
5 |
the same total every run — a fixed seed makes the sequence reproducible |
7 |
10 |
some value in [10, 60] |
1 |
0 |
0 |
1 vs 2 |
50 |
different totals — a different seed, a different sequence |
Two objects, then a loop:
std::mt19937 rng(seed); // engine: Mersenne Twister — same seed, same sequence
std::uniform_int_distribution<int> die(1, 6); // distribution: shapes raw bits into 1..6, without bias
int total = 0;
for (int i = 0; i < n; ++i) {
total += die(rng); // each call advances the engine by one step
}Why not rng() % 6 + 1? The engine's range is not a multiple of 6, so % makes the low faces slightly more likely; the distribution corrects for that.
For a real game you would seed from std::random_device{}() instead of a constant — the tests use a fixed seed because they need the same rolls every run.
// Roll a fair six-sided die `n` times and return the sum. Use a Mersenne
// Twister engine (std::mt19937) seeded with `seed` — same seed, same rolls,
// every run — and a std::uniform_int_distribution<int> to map the engine's
// raw 32-bit output onto 1..6 without bias.
int roll_sum(unsigned seed, int n) {
std::mt19937 rng(seed); // the engine: raw random bits, reproducible
std::uniform_int_distribution<int> die(1, 6); // the distribution: shapes bits into 1..6
int total = 0;
for (int i = 0; i < n; ++i) total += die(rng); // each call advances the engine
return total;
}Card 2 / 5
Write shuffled(n, seed): the numbers 1..n in a random order produced by std::shuffle driven by a std::mt19937 seeded with seed. std::shuffle takes the engine explicitly; the old std::random_shuffle used a hidden global one and was removed in C++17.
n |
seed |
result |
|---|---|---|
6 |
123 |
some ordering of {1, 2, 3, 4, 5, 6} |
10 |
5 |
the same ordering every run |
10 |
6 |
a different ordering from seed 5 |
0 |
1 |
{} |
1 |
1 |
{1} |
std::vector<int> v(n); std::iota(v.begin(), v.end(), 1); fills 1..n (std::iota writes consecutive values starting from the third argument). Then std::mt19937 rng(seed); std::shuffle(v.begin(), v.end(), rng); return v;. std::shuffle implements Fisher–Yates: it walks the range once, swapping each element with a random one at or after it, so every permutation is equally likely.
// Return the numbers 1..n in a random order, shuffled with a Mersenne
// Twister seeded by `seed`. std::shuffle takes the engine explicitly; the
// old std::random_shuffle (removed in C++17) used a hidden global one.
std::vector<int> shuffled(int n, unsigned seed) {
std::vector<int> v(n);
std::iota(v.begin(), v.end(), 1); // fill with 1, 2, ..., n
std::mt19937 rng(seed);
std::shuffle(v.begin(), v.end(), rng); // Fisher-Yates shuffle driven by rng
return v;
}Card 3 / 5
Write total(h, m, s): add an hours, a minutes and a seconds value and return the sum as std::chrono::seconds. Each argument is a different type, yet no cast is needed — converting a coarse unit into a finer one loses nothing, so the compiler does it implicitly. The std::chrono_literals suffixes (1h, 30min, 15s, 250ms) build these values in the tests.
h |
m |
s |
result |
|---|---|---|---|
1h |
30min |
15s |
5415s |
0h |
1min |
0s |
60s |
2h |
0min |
0s |
7200s |
0h |
0min |
0s |
0s |
return h + m + s; — hours + minutes yields minutes, minutes + seconds yields seconds; the compiler picks the finest unit involved and returns a typed duration, so t == 125000ms and t > 2min compare correctly without you converting anything. Only the reverse direction (seconds → minutes) needs an explicit duration_cast.
// Add hours, minutes and seconds and return the total as std::chrono::seconds.
// Each argument is a DIFFERENT duration type; because converting a coarse
// unit into a finer one loses nothing, the compiler does it implicitly —
// no duration_cast, no `* 60`.
std::chrono::seconds total(std::chrono::hours h, std::chrono::minutes m, std::chrono::seconds s) {
return h + m + s; // hours+minutes -> minutes; minutes+seconds -> seconds; all implicit
}Card 4 / 5
std::chrono durations carry their unit in the type, so the compiler converts between them for you — but only when nothing is lost. Going from a fine unit to a coarser one can lose the remainder, so it must be explicit. Write whole_minutes: how many complete minutes fit in s.
s |
result | note |
|---|---|---|
60s |
1 |
|
600s |
10 |
|
59s |
0 |
truncated toward zero, not rounded |
90s |
1 |
the 30 leftover seconds are dropped |
2min (a minutes value) |
2 |
minutes → seconds happens implicitly at the call |
return std::chrono::duration_cast<std::chrono::minutes>(s).count(); — duration_cast is the explicit, truncating conversion; .count() unwraps the plain number. If you want rounding instead of truncation, C++17 adds std::chrono::floor, ceil and round with the same shape.
// Return the number of WHOLE minutes in `s`, dropping any leftover seconds.
// Going from a fine unit (seconds) to a coarser one (minutes) loses
// information, so C++ refuses to do it implicitly: you must ask for it
// with std::chrono::duration_cast (which truncates toward zero).
long whole_minutes(std::chrono::seconds s) {
return std::chrono::duration_cast<std::chrono::minutes>(s).count(); // 90s -> 1min
}Card 5 / 5
Write elapsed_ms(work): run the callable work and return how many whole milliseconds it took. Use std::chrono::steady_clock — a monotonic stopwatch clock that never jumps backwards, unlike system_clock, which follows the wall clock and can be adjusted mid-measurement.
work |
result |
|---|---|
| sleeps 20 ms | at least 20 |
| does nothing | about 0 (below 100) |
| sleeps 60 ms vs sleeps 5 ms | the 60 ms run measures longer |
auto start = std::chrono::steady_clock::now(); gives a time_point; call work();, take now() again, and subtract — time_point - time_point is a duration in the clock's native unit (nanoseconds here). std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() truncates it to whole milliseconds. std::function<void()> is the parameter type: it can hold any callable with no arguments and no result — a lambda, a function pointer, a functor.
// Return how many whole milliseconds `work()` takes to run. Use
// std::chrono::steady_clock — a monotonic clock that never jumps backwards
// (system_clock can, when the wall clock is adjusted).
long long elapsed_ms(const std::function<void()>& work) {
auto start = std::chrono::steady_clock::now(); // a time_point
work();
auto end = std::chrono::steady_clock::now();
// end - start is a duration in the clock's native unit (nanoseconds);
// duration_cast truncates it to whole milliseconds.
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}