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
- Faithful to the book: a wins counter ("{wins} Wins, 0 Losses, 0 Ties"), the "(R)ock (P)aper (S)cissors or (Q)uit" prompt, the "1.../2.../3..." countdown, and the computer always playing the move that loses to yours (Rock -> Scissors, Paper -> Rock, Scissors -> Paper), then "You win!".
- Quitting:
nextMovereturns an unsetMovewhen the player types Q or the input ends, and the guardif move <- console.nextMove(wins)ends the game loop. The program then reaches its last line ("Thanks for playing!"), as 59 does, instead of callingsys.exit(); the end of the input now says goodbye too.