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 59 — Rock Paper Scissors
This is project 59 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=5. The player's answers are piped in:
r
p
x
s
q
Run it with:
BIGBOOK_SEED=5 ek9 rockpaperscissors.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
ROCK versus...
1...
2...
3...
SCISSORS
You win!
1 Wins, 0 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
PAPER versus...
1...
2...
3...
SCISSORS
You lose!
1 Wins, 1 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
Type one of R, P, S, or Q.
1 Wins, 1 Losses, 0 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
SCISSORS versus...
1...
2...
3...
SCISSORS
It's a tie!
1 Wins, 1 Losses, 1 Ties
Enter your move: (R)ock (P)aper (S)cissors or (Q)uit
Thanks for playing!
The program
#!ek9 defines module bigbook.rockpaperscissors defines constant //The book's pauses for suspense: 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: iterable in this order, and `$` gives the name. Move Rock Paper Scissors Outcome Win Lose Tie defines class //THE GAME's state: the score so far, how many games the player has won, lost and tied. Tally wins as Integer: 0 losses as Integer: 0 ties as Integer: 0 record() -> outcome as Outcome if outcome == Outcome.Win wins += 1 else if outcome == Outcome.Lose losses += 1 else if outcome == Outcome.Tie ties += 1 //Copies, because `+= 1` changes a count in place. winCount() as pure <- rtn as Integer: Integer(wins) lossCount() as pure <- rtn as Integer: Integer(losses) tieCount() as pure <- rtn as Integer: Integer(ties) default operator ? //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() -> tally as Tally <- rtn as Move: Move() quitting <- false while not rtn? and not quitting and not input.ended() stdout.println(say.score(tally.winCount(), tally.lossCount(), tally.tieCount())) stdout.println(say.askMove()) 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) showOutcome() -> outcome as Outcome stdout.println(outcome == Outcome.Win <- say.won() : outcome == Outcome.Lose <- say.lost() : say.tied()) 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 computer draws from the moves the type declares, so the choices cannot drift from it. computerMove() -> rng as Random <- rtn as Move: Move() moves <- cat Move | collect as List of Move rtn: moves.getOrDefault(rng.next(0, length moves - 1), Move()) //The rules of the game, in one place: which move each move beats. 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() outcomeOf() as pure -> player as Move computer as Move <- rtn as Outcome: player == computer <- Outcome.Tie : beaten(player) == computer <- Outcome.Win : Outcome.Lose 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. defines text for "en" Say introduction() `Rock, Paper, Scissors, by Al Sweigart al@inventwithpython.com - Rock beats scissors. - Paper beats rocks. - Scissors beats paper. ` score() -> wins as Integer losses as Integer ties as Integer `${wins} Wins, ${losses} Losses, ${ties} Ties` askMove() "Enter your move: (R)ock (P)aper (S)cissors or (Q)uit" 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!" lost() "You lose!" tied() "It's a tie!" goodbye() "Thanks for playing!" defines program //Rock Paper Scissors (Big Book project 59). Play rock, paper, scissors against the computer, //which chooses at random. The game decides; the Console asks and tells, in the player's //words. The golden replays a seeded game (seed.txt). RockPaperScissors() //A language with no words of its own is unset: ?: falls back to English. console <- Console(Say(language()) ?: Say("en")) rng <- randomSource() console.showIntroduction() tally <- Tally() running <- true while running and not console.ended() if move <- console.nextMove(tally) console.showCountdown(move) computer <- computerMove(rng) console.showComputerMove(computer) outcome <- outcomeOf(move, computer) console.showOutcome(outcome) tally.record(outcome) else running: false console.showGoodbye()
About this port
- Quitting:
nextMovereturns an unsetMovewhen the player types Q or the input ends, and the program's guard (if move <- console.nextMove(tally)) ends the game loop. The program then reaches its last line ("Thanks for playing!") instead of callingsys.exit().