Zig standard library does not like global state. It does not have global allocator or global buffered stdout writer. It also does not have global pseudorandom number generator. Library functions should accept a parameter and let the main() choose the source of randomness.
D&D Character exercise on Zig track does not offer an opportunity to comply with Zig standard library style. The function does not accept std.Random parameter and students are forced to declare a global variable one way or another. Even the .meta/example.zig declares a global variable.
We should redesign the API. For example, init() and ability() may take a parameter:
pub fn ability(random: std.Random) i8 {
...
}
pub const Character = struct {
...
pub fn init(random: std.Random) Character {
...
}
};
Reproducibility. The tests should use std.testing.random_seed:
test "random character is valid" {
var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);
const random = prng.random();
for (0..20) |_| {
const character: Character = .init(random);
try testing.expect(isValid(character));
}
}
The tests claim to inspect randomly generated values but most solutions always return the same value or the same sequence. We could test it too. An example that may need to be split into four different tests:
test "random ability is within range" {
var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);
const random = prng.random();
var actuals: [20]i8 = undefined;
for (&actuals) |*actual| {
actual.* = dnd_character.ability(random);
try testing.expect(isValidAbilityScore(actual.*));
}
// the returned values are not all the same
try testing.expect(std.mem.min(i8, &actuals) != std.mem.max(i8, &actuals));
// the sequence does not repeat itself
for (&actuals) |expected| {
const actual = dnd_character.ability(random);
if (actual != expected) break;
} else return error.ValuesAreNotRandom;
// the same seed results in the same sequence of values
prng.seed(std.testing.random_seed);
for (&actuals) |expected| {
const actual = dnd_character.ability(random);
try testing.expectEqual(expected, actual);
}
}
Old solutions will break. I think this is good because they are all bad solutions.