Skip to content

Instantly share code, notes, and snippets.

@stonehippo
Last active December 29, 2023 00:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stonehippo/b6a2cae0deecca1f86bc5ef316546f61 to your computer and use it in GitHub Desktop.
Save stonehippo/b6a2cae0deecca1f86bc5ef316546f61 to your computer and use it in GitHub Desktop.
Making a little dice roller in modern Python

A tiny dice roller in modern Python

I have been reading Fluent Python, and I can tell that it's going to help me be a better Python coder right away.

A while ago, I started a Javascript library for rolling common dice types, which I intended to use for various games. While reading Fluent Python, I realized that I could write the same logic in a much more compact form, thanks to listcomps, generator functions, and unpacking. In fact, the core implementation takes only four lines of code:

sides = [4, 6, 8, 10, 12, 20]
d4, d6, d8, d10, d12, d20 = [range(1, count + 1) for count in sides]

def roll(die, count=1):
    return tuple(choice(die) for _ in range(count))

I can even shave off a whole line (and a possibly unneeded assigment) like this:

d4, d6, d8, d10, d12, d20 = [range(1, sides + 1) for sides in (4, 6, 8, 10, 12, 20)]

That might even be more readable…

This is pretty small. The JS library is much larger (and depends on Ramda to make it more functional), though I suspect I could rewrite it to be more compact now (and in fact I did a more compact version later). And there's more I could do to make this Python version hardier. Nevertheless, I continue to be impressed by how smooth this language can be.

I tested this code in CPython and CircuitPython 7.3.3.

Bonus: roller.ino

I also coded a little dice roller for Arduino. That's the roller.ino file.

from random import choice
# define common dice types
sides = [4, 6, 8, 10, 12, 20]
# use ranges to efficiently store dice values
d4, d6, d8, d10, d12, d20 = [range(1, count + 1) for count in sides]
# choose to return a tuple here since roles can't be changed
def roll(die, count=1):
return tuple(choice(die) for _ in range(count))
# use % formatting to unpack results
print("d4 roll is %s" % roll(d4))
print("d20 roll is %s" % roll(d20))
# example helper for a common combo
def roll2d6():
return roll(d6, 2)
# roll the dice a lot…
rolls = tuple(sum(roll2d6()) for _ in range(100000))
# …and confirm the we have a normal distribution
counts = tuple((i, rolls.count(i)) for i in range(2, len(d6) * 2 + 1))
for count in counts:
print("%s: %s" % count)
#define DIE_MIN 1 // no die can have less that one side!
#define D2_MAX 2 // Flip a coin!
#define D4_MAX 4
#define D6_MAX 6
#define D8_MAX 8
#define D10_MAX 10
#define D12_MAX 12
#define D20_MAX 20
long counts[D6_MAX * 2 + 1];
/*
See https://arduino.stackexchange.com/questions/50671/getting-a-truly-random-number-in-arduino
for some thoughts on getting more entropy into random
*/
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0)); // This doesn't really produce a wide range of possible seeds
// roll 10000 times
for (int i = 0; i < 10000; i++) {
long temp = roll2d6();
counts[temp] = counts[temp] + 1;
}
for (int i = 2; i <= (D6_MAX * 2); i++) {
Serial.print(i);
Serial.print(": ");
Serial.println(counts[i]);
}
}
void loop() {
}
// Roll a die of some size
long roll(int size) {
return random(DIE_MIN, size + 1);
}
// roll multiple dice of some size
long rollN(int size, int count) {
long acc = 0;
for (int i = 0; i < count; i++) {
acc += roll(size);
}
return acc;
}
long roll2d6() {
return rollN(D6_MAX, 2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment