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 60 — Rock Paper Scissors (Always-Win Version)

This is project 60 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 player's answers are piped in:

p
s
r
x
q

Run it with:

ek9 rock-paper-scissors-always-win.ek9 < input.txt

It prints:

Rock, Paper, Scissors, by Al Sweigart al@inventwithpython.com
- Rock beats scissors.
- Paper beats rocks.
- Scissors beats paper.

0 Wins, 0 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
> 
PAPER versus...
1...
2...
3...
ROCK
You win!
1 Wins, 0 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
> 
SCISSORS versus...
1...
2...
3...
PAPER
You win!
2 Wins, 0 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
> 
ROCK versus...
1...
2...
3...
SCISSORS
You win!
3 Wins, 0 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
> 
Type one of R, P, S, or Q.
3 Wins, 0 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
> 
Thanks for playing!

The program

#!ek9
defines module bigbook.rpsalwayswin

  defines constant

    //The book's pauses for suspense, as in Rock Paper Scissors (59): one before the count, one
    //between counts, one after the reveal.
    RevealPause <- 500ms
    CountPause <- 250ms
    CountTo <- 3

  defines type

    //A closed set of moves is an enumeration, as in Rock Paper Scissors (59).
    Move
      Rock
      Paper
      Scissors

  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.
    //It also keeps the book's pauses: they are how the game is shown, not what it decides.
    Console
      stdout as Stdout: Stdout()
      input as LineInput: LineInput()
      os as OS: OS()
      say as Say: Say("en")

      Console()
        -> words as Say
        say: words

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

      //The player's next move. Unset when they quit or the input ends: either way there is no move.
      //The score is shown before every prompt, so a rejected letter shows it again, as in the book.
      nextMove()
        -> wins as Integer
        <- rtn as Move: Move()
        quitting <- false
        while not rtn? and not quitting and not input.ended()
          stdout.println(say.score(wins))
          stdout.println(say.askMove())
          stdout.println(say.prompt())
          if entry <- input.next()
            letter <- entry.lowerCase()
            if letter == say.quit()
              quitting: true
            else
              rtn: moveOf(letter)
              if not rtn?
                stdout.println(say.notAMove())

      //Count to three with dramatic pauses. os.delay prints nothing, so the golden is the same
      //with or without them.
      showCountdown()
        -> move as Move
        stdout.println(say.versus(moveWord(move)))
        os.delay(RevealPause)
        for count in 1 ... CountTo
          stdout.println(say.counted(count))
          os.delay(CountPause)

      showComputerMove()
        -> move as Move
        stdout.println(moveWord(move))
        os.delay(RevealPause)

      showWon()
        stdout.println(say.won())

      showGoodbye()
        stdout.println(say.goodbye())

      ended() as pure
        <- rtn as Boolean: input.ended()

      private moveWord()
        -> move as Move
        <- rtn as String: move == Move.Rock <- say.rock() : move == Move.Paper <- say.paper() : say.scissors()

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

  defines function

    //THE GAME: its rules, with no input or output.

    //A letter the player typed, as a Move; anything else is an UNSET Move, which the caller
    //tests with `?` instead of comparing strings.
    moveOf() as pure
      -> letter as String
      <- rtn as Move: letter == "r" <- Move.Rock : letter == "p" <- Move.Paper : letter == "s" <- Move.Scissors : Move()

    //The rules of the game, in one place: which move each move beats. Every Move must be a case
    //(E07310) and the default is required too (E07320): it is the branch an unset Move takes.
    beaten() as pure
      -> move as Move
      <- rtn as Move?
      switch move
        case Move.Rock
          rtn: Move.Scissors
        case Move.Paper
          rtn: Move.Rock
        case Move.Scissors
          rtn: Move.Paper
        default
          rtn: Move()

    //The gag of this program: the computer always plays the move that LOSES to yours.
    computerMove() as pure
      -> move as Move
      <- rtn as Move: beaten(move)

  defines function

    //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, with the same names as Rock Paper Scissors (59) uses.
  defines text for "en"

    Say

      introduction()
        `Rock, Paper, Scissors, by Al Sweigart al@inventwithpython.com
- Rock beats scissors.
- Paper beats rocks.
- Scissors beats paper.
`

      //The always-win version never loses or ties, and the book's score line says so.
      score()
        -> wins as Integer
        `${wins} Wins, 0 Losses, 0 Ties`

      askMove()
        "Enter your move: (R)ock (P)aper (S)cissors or (Q)uit"

      prompt()
        "> "

      quit()
        "q"

      notAMove()
        "Type one of R, P, S, or Q."

      //The book prints moves in capitals.
      rock()
        "ROCK"

      paper()
        "PAPER"

      scissors()
        "SCISSORS"

      versus()
        -> moveName as String
        `${moveName} versus...`

      counted()
        -> count as Integer
        `${count}...`

      won()
        "You win!"

      goodbye()
        "Thanks for playing!"

  defines program

    //Rock, Paper, Scissors (Always-Win Version) (Big Book project 60). The computer always plays
    //the move that loses to yours, so you always win. The game decides; the Console asks and
    //tells, in the player's words.
    RockPaperScissors()
      //A language with no words of its own is unset: ?: falls back to English.
      console <- Console(Say(language()) ?: Say("en"))

      console.showIntroduction()
      wins <- 0
      running <- true
      while running and not console.ended()
        if move <- console.nextMove(wins)
          console.showCountdown(move)
          console.showComputerMove(computerMove(move))
          console.showWon()
          wins += 1
        else
          running: false
      console.showGoodbye()

About this port