The Big Book of Small EK9 Programs

Adapted from Al Sweigart's The Big Book of Small Python Projects: 81 Easy Practice Programs (No Starch Press, 2021; copyright © 2021 Al Sweigart), which he makes freely available under a Creative Commons Attribution-NonCommercial-ShareAlike licence at inventwithpython.com/bigbookpython. Changes were made: each program was ported from Python to EK9 and remodelled in EK9's idioms.

Project 46 — Million Dice Roll Statistics Simulator

This is project 46 of the book, ported to EK9. Its chapter is still being written; until then, this page shows the port itself: a run of it, the whole program, and how it keeps to the original.

A sample run

The program is random, so the run fixes it by setting BIGBOOK_SEED=1.

Run it with:

BIGBOOK_SEED=1 ek9 milliondiceroll.ek9 2

It prints:

Million Dice Roll Statistics Simulator
By Al Sweigart al@inventwithpython.com
Simulating 1000000 rolls of 2 dice...
TOTAL - ROLLS - PERCENTAGE
  2 - 27778 rolls - 2.8%
  3 - 55953 rolls - 5.6%
  4 - 83379 rolls - 8.3%
  5 - 110777 rolls - 11.1%
  6 - 139502 rolls - 14.0%
  7 - 166404 rolls - 16.6%
  8 - 137901 rolls - 13.8%
  9 - 111401 rolls - 11.1%
  10 - 83361 rolls - 8.3%
  11 - 55664 rolls - 5.6%
  12 - 27880 rolls - 2.8%

The program

#!ek9
defines module bigbook.milliondiceroll

  defines constant

    Rolls <- 1000000
    Sides <- 6

  defines class

    //THE OUTPUT: everything the player sees goes through here, worded by Say. The number of
    //dice is a program argument, so there is no LineInput.
    Console
      stdout as Stdout: Stdout()
      say as Say: Say("en")

      Console()
        -> words as Say
        say: words

      showIntroduction()
        stdout.println(say.introduction())

      showTooFewDice()
        stdout.println(say.tooFewDice())

      showSimulating()
        -> dice as Integer
        stdout.println(say.simulating(dice))

      showTableHeading()
        stdout.println(say.tableHeading())

      //The numbers are the simulation's; the words around them ("rolls", "%") are Say's.
      showTableRow()
        ->
          total as Integer
          count as Integer
          percentage as Float

        stdout.println(say.tableRow(total, count, percentage))

      override operator ? as pure
        <- rtn as Boolean: true

  defines function

    //THE GAME: the simulation, with no output or words.

    lowestTotal() as pure
      -> dice as Integer
      <- rtn as Integer: Integer(dice)

    highestTotal() as pure
      -> dice as Integer
      <- rtn as Integer: dice * Sides

    //The total of one throw of every die.
    throwTotal()
      ->
        dice as Integer
        rng as Random
      <- rtn as Integer: 0

      for die in 1 ... dice
        rtn += rng.next(1, Sides)

    //How often each total came up in a million throws. Every possible total starts at zero,
    //so a total that is never thrown still has a count.
    rollCounts()
      ->
        dice as Integer
        rng as Random
      <- rtn as Dict of (Integer, Integer): Dict()

      for total in lowestTotal(dice) ... highestTotal(dice)
        rtn += DictEntry(total, 0)
      for roll in 1 ... Rolls
        total <- throwTotal(dice, rng)
        rtn += DictEntry(total, rtn.getOrDefault(total, 0) + 1)

    //The book's round(count / 10000, 1): the share of all the rolls, as a percentage.
    percentageOf() as pure
      -> count as Integer
      <- rtn as Float: Float()
      share <- Float(count) * 100.0 / Float(Rolls)
      rtn: share.round(1)

  defines function

    //THE source of randomness, copied unchanged from lib/RandomSource.ek9: SystemRandom(), or
    //SeededRandom(seed) when BIGBOOK_SEED holds a number (run-and-diff.sh sets it from seed.txt).
    randomSource()
      <- rtn as Random: SeededRandom(Integer(EnvVars().get("BIGBOOK_SEED"))) ?: SystemRandom()

    //THE language of the words, copied unchanged from lib/Language.ek9: BIGBOOK_LANG when it
    //is set (fr, zh, ...), otherwise en.
    language()
      <- rtn as String: EnvVars().get("BIGBOOK_LANG") ?: "en"

  //THE WORDS: every word the player sees. The numbers in the table are not here: the
  //simulation counts them and the Console shows them.
  defines text for "en"

    Say

      introduction()
        `Million Dice Roll Statistics Simulator
By Al Sweigart al@inventwithpython.com`

      tooFewDice()
        "Roll at least one die."

      simulating()
        -> dice as Integer
        `Simulating ${Rolls} rolls of ${dice} dice...`

      tableHeading()
        "TOTAL - ROLLS - PERCENTAGE"

      tableRow()
        ->
          total as Integer
          count as Integer
          percentage as Float
        `  ${total} - ${count} rolls - ${percentage}%`

  defines program

    //Million Dice Roll Statistics Simulator (Big Book project 46). Rolls the dice a million times
    //and shows how often each total comes up. The game decides; the Console shows, in the
    //reader's words. The golden replays a seeded run (seed.txt).
    MillionDiceRoll()
      -> dice as Integer
      //A language with no words of its own is unset: ?: falls back to English.
      console <- Console(Say(language()) ?: Say("en"))

      console.showIntroduction()
      if dice < 1
        console.showTooFewDice()
      else
        rng <- randomSource()
        console.showSimulating(dice)
        counts <- rollCounts(dice, rng)
        console.showTableHeading()
        for total in lowestTotal(dice) ... highestTotal(dice)
          count <- counts.getOrDefault(total, 0)
          console.showTableRow(total, count, percentageOf(count))

About this port