A command-line tool for selecting an item at random from a list, with a spinning text display that decelerates to rest.
Note
For Craig Robinson, on the occasion of his retirement from Red Hat. An engineer across more than one domain, and proof that the most useful perspective sometimes comes from working at the boundaries between them. His warmth, straight talking, and dependability made the work better and the people around him better for it.
A common requirement in both recreational and practical settings is to choose a single item from a finite set in a manner that is fair and unpredictable. Prize wheels, raffles, and team allocation exercises all reduce to this problem. The approach taken here is to model the physical behaviour of a spinning wheel, and to implement that model as a composable Unix command-line program.
The physics draws on the treatment of rotational dynamics and friction found in standard texts at GCSE and A-Level123, where a uniform disc subject to a constant friction torque undergoes uniform angular deceleration. The computer science draws on the modular arithmetic and pseudorandom number theory covered in introductory algorithm courses45, together with the Unix pipeline conventions established by the C programming tradition6.
Consider a uniform disc of mass
The moment of inertia of a uniform disc about its central axis
is12
Note that
Mass does, however, affect the initial angular velocity for a given
applied impulse. If a tangential force
A heavier wheel starts more slowly for the same push, but once spinning, it decelerates at the same rate under Coulomb friction alone. It therefore covers fewer total revolutions before coming to rest.
In practice, a spinning wheel is also subject to aerodynamic drag, which
produces a retarding torque proportional to the angular velocity23.
Writing
Combining Coulomb friction and drag, the equation of motion becomes
where
The velocity decays exponentially (governed by drag) towards a negative
offset (governed by Coulomb friction). The wheel stops when
The inclusion of drag resolves an otherwise counter-intuitive consequence
of the pure Coulomb model, in which a lighter wheel, pushed with the
same force, spins for arbitrarily longer because mass cancels from the
deceleration. With drag, the lighter wheel's higher initial velocity is
met with proportionally greater aerodynamic braking, producing behaviour
consistent with everyday experience: heavy flywheels coast, and light
wheels stop. When
In the programme, the continuous rotation of the wheel is represented by
a sequence of discrete item positions. If there are
The position traversed during a tick starting at velocity
The inter-item delay
The delays increase monotonically with
When
which the programme uses in this case for efficiency.
The winning item is selected before the display begins, using the
cryptographically secure pseudorandom number generator (CSPRNG) provided
by the operating system (crypto/rand in Go, which reads from
/dev/urandom on Unix systems). This source produces uniformly
distributed integers using rejection sampling over the full range of a
math/big.Int4, ensuring that no item is favoured by modular bias.
The display that follows is a deterministic rendering of this result.
The items are arranged in a circular sequence of length
When the starting position is chosen automatically, it is set so that
the animation lands on the winner:
The programme follows the Unix convention7, established by the tools
described in Kernighan and Ritchie's The C Programming Language6
and formalised in the POSIX (Portable Operating System Interface)
standard, of writing diagnostic output to the standard error stream and
results to the standard output stream. This allows spin to participate
in pipelines:
spin -f nominees.txt | xargs notify-send
The animation appears on the terminal (via stderr) while only the selected item passes through the pipe (via stdout).
A Monte Carlo simulation consists of repeated independent random trials
used to estimate a quantity that may be difficult to compute
analytically45. Here, the quantity of interest is the probability
distribution over the item set. Since each trial selects uniformly at
random from
The --monte-carlo flag runs
The Monte Carlo simulation is an embarrassingly parallel problem: each trial is an independent random selection with no data dependency on any other4. The programme exploits this by distributing trials across all available processor cores using a fan-out/fan-in pattern.
Each worker goroutine maintains its own pseudorandom number generator (PRNG), seeded from the operating system's CSPRNG (section 2.4). The PRNG uses the ChaCha8 stream cipher5, which provides statistical randomness suitable for Monte Carlo simulation without the kernel overhead and lock contention of the CSPRNG itself. This separation is standard practice in computational simulation: the CSPRNG provides an unpredictable seed, and the PRNG provides throughput.
Workers accumulate results in local frequency maps and send them to a collector in batches of 10,000, reducing inter-goroutine communication by four orders of magnitude compared with per-trial transmission. The collector merges each batch into the global histogram and periodically renders the display.
The following wall-clock measurements were taken on an Apple M3 Pro
(12 cores), selecting from five items with --fast-forward:
| Trials | Sequential | Parallel | Speedup |
|---|---|---|---|
| 22 ms | 5 ms | 4x | |
| 181 ms | 9 ms | 20x | |
| 1.74 s | 26 ms | 67x | |
| 17.5 s | 220 ms | 80x |
The speedup increases with scale because the fixed overhead of goroutine creation and channel setup is amortised over a larger number of trials.
brew install cloudygreybeard/tap/spingo install github.com/cloudygreybeard/spin@latestgit clone https://github.com/cloudygreybeard/spin.git
cd spin
make installItems may be provided as positional arguments, piped from standard input, or read from a file:
spin apple banana cherry
echo "red green blue" | spin
spin -f items.txt
spin -f items.txt -s ","Example 1. A team of five is to be selected to present first. Using the default parameters:
spin Alice Bob Carol Dave EveThe wheel displays approximately 27 items over 1.5 seconds before settling on the result.
Example 2. The same selection, but the wheel is to start at the third position in the list:
spin --start 3 Alice Bob Carol Dave EveThe animation begins at "Carol" and proceeds through the list
cyclically. The start value wraps via modulo, so --start 8 in a list
of five items begins at item 3 (since
Example 3. Investigating the effect of friction. A well-oiled axle
bearing (
spin --friction 0.05 red green blue yellow
spin --friction 0.5 red green blue yellowThe reader may verify that doubling the friction coefficient
approximately halves the number of displayed items, in agreement with the
inverse relationship between
Example 4. Disabling drag to use the pure Coulomb friction model, or increasing it to simulate a wheel in a viscous medium:
spin --drag 0 red green blue yellow
spin --drag 0.5 red green blue yellowExample 5. Selecting without the wheel display, printing only the result:
spin --fast-forward Alice Bob Carol Dave EveExample 6. Running a Monte Carlo simulation of 1,000 trials to verify the uniformity of the distribution. The histogram updates live on the terminal; the final frequency table is written to stdout:
spin --monte-carlo 1000 --fast-forward a b c d eWith five items and 1,000 trials, each item should appear approximately
200 times. The --fast-forward flag is recommended for large trial
counts.
Example 7. Ranking the histogram by frequency, most-selected first:
spin --monte-carlo 1000 --fast-forward --rank a b c d e
spin --monte-carlo 1000 --fast-forward --sort=count-asc a b c d eThe --rank flag is shorthand for --sort=count (descending). The
--sort flag accepts original (default), count[-desc|-asc], and
name[-asc|-desc]. The bare forms count and name default to
descending and ascending respectively.
Example 8. Running 100 million trials and capturing the frequency table in tab-separated values (TSV) format for further analysis:
spin --monte-carlo 100000000 --fast-forward a b c d e > results.tsvThe TSV output contains one row per item with the count and percentage,
suitable for piping to sort, awk, or a plotting tool. At this scale,
the parallel implementation completes in under 250 ms on a modern
multi-core processor.
| Flag | Default | Physical meaning |
|---|---|---|
--force |
1.0 | Magnitude of the initial push (impulse) |
--mass |
1.0 | Mass of the wheel (affects inertia and drag braking) |
--friction |
0.2 | Coefficient of kinetic friction at the axle |
--drag |
0.1 | Aerodynamic drag coefficient (0 = pure Coulomb) |
--max-delay |
500ms | Delay threshold at which the wheel stops |
--start |
random | Starting position (1-indexed, wraps mod |
The relationships between these parameters and the display are summarised in the following table:
| Increase in parameter | Effect on |
Effect on |
Effect on |
Effect on total items |
|---|---|---|---|---|
| Force | Increases | No change | No change | Increases |
| Mass | Decreases | No change | Decreases | Decreases (but drag helps) |
| Friction | No change | Increases | No change | Decreases |
| Drag | No change | No change | Increases | Decreases |
-f, --file string path to input file
-s, --separator string item separator regex (default: whitespace)
--start string starting position (1-indexed, wraps via modulo) or "random" (default "random")
--force float spin force (default 1)
--mass float wheel mass (default 1)
--friction float coefficient of kinetic friction (default 0.2)
--drag float aerodynamic drag coefficient (default 0.1)
-m, --max-delay duration delay threshold at which the wheel stops (default 500ms)
-n, --monte-carlo int run N trials and display a frequency histogram
-q, --fast-forward skip the wheel animation (print result only)
--sort string histogram sort: original, count[-desc|-asc], name[-asc|-desc] (default "original")
--rank shorthand for --sort=count (most frequent first)
-h, --help help for spin
make build # Build the binary
make test # Run tests with race detector
make lint # Run golangci-lint
make clean # Remove build artifacts
make snapshot # Build a snapshot releaseApache 2.0. See LICENSE.
Footnotes
-
Duncan, T. (1995) GCSE Physics, 3rd edn. London: John Murray. ↩ ↩2
-
Muncaster, R. (1993) A-Level Physics, 4th edn. Cheltenham: Stanley Thornes. ↩ ↩2 ↩3 ↩4
-
Nelkon, M. and Parker, P. (1995) Advanced Level Physics, 7th edn. Oxford: Heinemann. ↩ ↩2
-
Aho, A.V., Hopcroft, J.E. and Ullman, J.D. (1983) Data Structures and Algorithms. Reading: Addison-Wesley. ↩ ↩2 ↩3 ↩4 ↩5
-
Brookshear, J.G. (1997) Computer Science: An Overview, 5th edn. Reading: Addison-Wesley. ↩ ↩2 ↩3 ↩4
-
Kernighan, B.W. and Ritchie, D.M. (1988) The C Programming Language, 2nd edn. Englewood Cliffs: Prentice Hall. ↩ ↩2
-
Tanenbaum, A.S. (1992) Modern Operating Systems. Englewood Cliffs: Prentice Hall. ↩