A stochastic SIR/SIRS epidemic simulator that runs as a cellular automaton on a toroidal grid and renders live to your terminal — or headless, for reproducible batch statistics.
Most epidemic demos use the textbook SIR differential equations, which
model a well-mixed population and hide all spatial structure. PlagueField
instead simulates the epidemic locally: every cell is a person standing next
to eight neighbors, and infection spreads only through contact. That single
change reproduces phenomena the mean-field equations miss for free —
wavefronts of infection rippling across the grid, patches that stay
untouched because they got lucky, and (with --waning) spatial reinfection
waves in an SIRS model instead of a single smooth peak.
It's a single self-contained .java file with no dependencies — Java's
single-file source-launcher (java Foo.java) runs it straight from source,
no build step required.
Requires a JRE (Java 11+; developed against OpenJDK 21). No javac, no
build tool, no external libraries.
java PlagueField.javaThat launches a live animated outbreak in your terminal with default parameters (60x24 grid, 3 seed infections, runs until the outbreak burns out or 2000 ticks pass). Press Ctrl+C to stop early.
--width N grid width (default 60)
--height N grid height (default 24)
--initial-infected N seed infections (default 3)
--seed N RNG seed (default random)
--beta F per-neighbor infect chance, 0..1 (default 0.22)
--duration N ticks infected before recovery (default 6)
--waning N ticks until immunity fades, 0=permanent SIR (default 0)
--ticks N run exactly N ticks headless, print summary only
--delay MS ms between frames in live mode (default 90)
--headless no animation, print only the final summary
--no-color disable ANSI color in rendered frames
--help show all flags
$ java PlagueField.java --headless --ticks 80 --seed 42 \
--width 40 --height 20 --initial-infected 4 --beta 0.25 --duration 5
---- SIMULATION COMPLETE ----
seed=42 ticks=80 grid=40x20 beta=0.250 duration=5 waning=0
final: S=0 I=0 R=800 (total=800, conserved=true)
peak infected: 367 at tick 16
ever infected: 800 (100.0% of population)Same seed, same result, every time — see examples/sample-run.txt for a
saved copy of this exact run. Try --waning 20 to switch from a one-shot SIR
outbreak to an SIRS model that can sustain multiple reinfection waves.
- The grid wraps toroidally (edges connect to the opposite edge), so no cell has fewer than 8 neighbors and outbreaks aren't artificially dampened at the border.
- Each tick, every Susceptible cell counts its infected Moore neighbors
kand becomes infected with probability1 - (1 - beta)^k— one independent transmission roll per infected neighbor. - Each Infected cell tracks how many ticks it's been sick and flips to
Recovered once it hits
--duration. - Each Recovered cell stays immune forever (classic SIR) unless
--waning Nis set, in which case it reverts to Susceptible afterNticks (SIRS — enables sustained/oscillating epidemics instead of a single burnout). - All state transitions for a tick are computed from a frozen snapshot of the previous tick (no read-your-own-writes order dependence), so results only depend on the RNG seed, not on iteration order.
- A sticky
everInfectedflag per cell (independent of current state) keeps cumulative "ever infected" statistics correct even under SIRS, where a cell's current state can cycle back to Susceptible.
test.sh is a smoke test: it runs the simulation twice with a fixed seed and
checks (a) both runs produce byte-identical output, (b) the population count
is conserved every tick, (c) the outbreak actually spreads beyond its seed
infections, and (d) a classic-SIR run burns out to extinction.
./test.shMIT — see LICENSE.