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 42 — Magic Fortune Ball

This is project 42 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=3. The player's answers are piped in:

Will this port compile?

Run it with:

BIGBOOK_SEED=3 ek9 magicfortuneball.ek9 < input.txt

It prints:

ANSWER SPHERE, GIVE US THE (VAGUE) TRUTH!
Ask a yes/no question:
You asked: Will this port compile?
Thinking...
My reply is no.

The program

#!ek9
defines module bigbook.magicfortuneball

  defines class

    //THE input idiom of this book, copied unchanged from lib/LineInput.ek9 (the reasons are
    //there): read with next(), and every input loop also stops when ended() is true.
    LineInput
      stdin as Stdin: Stdin()
      inputEnded as Boolean: false

      next()
        <- rtn as String: stdin.next()
        if not rtn?
          inputEnded: true

      ended() as pure
        <- rtn as Boolean: Boolean(inputEnded)

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

    //THE INPUT AND OUTPUT: everything the player sees or types goes through here, worded by Say.
    Console
      stdout as Stdout: Stdout()
      input as LineInput: LineInput()
      say as Say: Say("en")

      Console()
        -> words as Say
        say: words

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

      //The question the player asks; unset when the input ends before they ask one.
      nextQuestion()
        <- rtn as String: String()
        stdout.println(say.askQuestion())
        rtn: input.next()

      showQuestion()
        -> question as String
        stdout.println(say.youAsked(question))

      //The original's time.sleep dot animation is a single line here.
      showThinking()
        stdout.println(say.thinking())

      //The answer is the toy's own data, shown as it is.
      showAnswer()
        -> answer as String
        stdout.println(answer)

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

  defines function

    //THE GAME: the toy's answers and the draw, with no input or output.

    //The answers of the classic toy: data the ball draws from, not words the program says.
    sphereAnswers() as pure
      <- rtn as List of String: [
        "It is certain.",
        "It is decidedly so.",
        "Without a doubt.",
        "Yes, definitely.",
        "You may rely on it.",
        "As I see it, yes.",
        "Most likely.",
        "Outlook good.",
        "Yes.",
        "Signs point to yes.",
        "Reply hazy, try again.",
        "Ask again later.",
        "Better not tell you now.",
        "Cannot predict now.",
        "Concentrate and ask again.",
        "Don't count on it.",
        "My reply is no.",
        "My sources say no.",
        "Outlook not so good.",
        "Very doubtful."
      ]

    //Random.next(low, high) includes both ends, so the last index is the upper bound.
    sphereAnswer()
      -> rng as Random
      <- rtn as String: String()
      answers <- sphereAnswers()
      rtn: answers.getOrDefault(rng.next(0, length answers - 1), String())

  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, apart from the toy's answers.
  defines text for "en"

    Say

      introduction()
        "ANSWER SPHERE, GIVE US THE (VAGUE) TRUTH!"

      askQuestion()
        "Ask a yes/no question:"

      youAsked()
        -> question as String
        `You asked: ${question}`

      thinking()
        "Thinking..."

  defines program

    //Magic Fortune Ball (Big Book project 42). A Magic 8-Ball: ask it a yes/no question and it
    //draws an answer. The game decides; the Console asks and tells, in the player's words. The
    //golden replays a seeded game (seed.txt).
    MagicFortuneBall()
      //A language with no words of its own is unset: ?: falls back to English.
      console <- Console(Say(language()) ?: Say("en"))
      rng <- randomSource()

      console.showIntroduction()
      //The guard: the sphere answers only once a question has actually been asked, so the
      //end of input (an unset line) ends the program quietly.
      if question <- console.nextQuestion()
        console.showQuestion(question)
        console.showThinking()
        console.showAnswer(sphereAnswer(rng))

About this port