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 33 — Hacking Minigame
This is project 33 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. The player's answers are piped in:
CAPTURE
PICTURE
MACHINE
Run it with:
BIGBOOK_SEED=1 ek9 hacking-minigame.ek9 < input.txt
It prints:
Hacking Minigame, by Al Sweigart al@inventwithpython.com
Find the password. Each guess tells you how many letters are in the right place.
Candidate passwords:
DIAGRAM CAPTURE PROGRAM JOURNEY PICTURE KITCHEN MACHINE PENGUIN MONITOR LIBRARY DIAMOND TEACHER
Enter password (4 tries left):
Access Denied (2/7 correct)
Enter password (3 tries left):
Access Denied (2/7 correct)
Enter password (2 tries left):
A C C E S S G R A N T E D
The program
#!ek9 defines module bigbook.hacking defines constant Tries <- 4 WordLength <- 7 CandidateCount <- 12 defines type List of String shuffled of String 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 candidates are the game's data, shown as they are, each followed by a space. showCandidates() -> candidates as List of String stdout.println(say.candidates()) shown <- cat candidates | map with spaced | collect as String stdout.println(shown) //A guess that is one of the candidates, or unset when the input ends. nextGuess() -> triesLeft as Integer candidates as List of String <- rtn as String: String() while not rtn? and not input.ended() stdout.println(say.askPassword(triesLeft)) if entry <- input.next() guess <- entry.upperCase().trim() if isCandidate(guess, candidates) rtn: guess else stdout.println(say.notACandidate()) showGranted() stdout.println(say.granted()) showDenied() -> inPlace as Integer stdout.println(say.denied(inPlace)) showOutOfTries() -> secret as String stdout.println(say.outOfTries(secret)) override operator ? as pure <- rtn as Boolean: true defines function //THE GAME: its rules and its words to guess, with no input or output. //The game's data: the words a secret is drawn from, which the player's guess is compared with. passwords() as pure <- rtn as List of String: ["MONITOR", "CONTAIN", "PROGRAM", "LIBRARY", "NETWORK", "MACHINE", "DIAGRAM", "PENGUIN", "JOURNEY", "TEACHER", "KITCHEN", "PICTURE", "DIAMOND", "HUSBAND", "CAPTURE"] //shuffled: a new List in random order (see lib/Shuffle.ek9). Inlined because each //port is a single compilation unit. Uses the Random it is given. shuffled() of type T as open -> source as List of T rng as Random <- rtn as List of T: List() of T work as List of T: List() of T work += source for pass in source size as Integer: length work pick as Integer: rng.next(0, size - 1) remaining as List of T: List() of T cursor as Integer: 0 for element in work if cursor == pick rtn += element else remaining += element cursor += 1 work: remaining //The candidates on screen: the first few of the word list in a random order. candidatesFrom() -> words as List of String rng as Random <- rtn as List of String: List() of String shuffle <- () is shuffled of String as function rtn: cat shuffle(words, rng) | head CandidateCount | collect as List of String isCandidate() as pure -> guess as String candidates as List of String <- rtn as Boolean: candidates contains guess //The clue for a wrong guess: how many positions hold the same letter in both words. A //pure function may not count with +=, so it compares the first letters and adds the //count for the rest of both words. lettersInPlace() as pure -> secret as String guess as String <- rtn as Integer: 0 if not secret.empty() firstMatches <- #< secret == #< guess <- 1 : 0 rtn: firstMatches + lettersInPlace(secret.subString(1), guess.subString(1)) defines function //THE FLOW of the tries, as the book's for loop takes them; the program calls it. //One round per try: the right guess ends the game, a wrong one gives a clue and plays the //next round with one try fewer, so the game ends without a flag to say it was won. The //end of input ends it too, with no guess. playRound() -> triesLeft as Integer secret as String candidates as List of String console as Console if triesLeft == 0 console.showOutOfTries(secret) else if guess <- console.nextGuess(triesLeft, candidates) if guess == secret console.showGranted() else console.showDenied(lettersInPlace(secret, guess)) playRound(triesLeft: triesLeft - 1, secret: secret, candidates: candidates, console: console) defines function spaced() as pure -> word as String <- rtn as String: `${word} ` //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 passwords are not here: they are the game's data. defines text for "en" Say introduction() `Hacking Minigame, by Al Sweigart al@inventwithpython.com Find the password. Each guess tells you how many letters are in the right place.` candidates() "Candidate passwords:" askPassword() -> triesLeft as Integer `Enter password (${triesLeft} tries left):` notACandidate() "Please enter one of the candidate passwords." granted() "A C C E S S G R A N T E D" denied() -> inPlace as Integer `Access Denied (${inPlace}/${WordLength} correct)` outOfTries() -> secret as String `Out of tries. Secret password was ${secret}.` defines program //Hacking Minigame (Big Book project 33). The Fallout password game: find the secret word among //the candidates; each wrong guess says how many letters are in the right place. The game //decides; the Console asks and tells, in the player's words. The golden replays a seeded game //(seed.txt). Hacking() //A language with no words of its own is unset: ?: falls back to English. console <- Console(Say(language()) ?: Say("en")) rng <- randomSource() candidates <- candidatesFrom(passwords(), rng) secret <- candidates.getOrDefault(rng.next(0, length candidates - 1), String()) console.showIntroduction() console.showCandidates(candidates) playRound(triesLeft: Tries, secret: secret, candidates: candidates, console: console)
About this port
- Behaviour: the Fallout password game: the secret is one of twelve 7-letter candidates; each wrong guess reports how many letters are in the right place (x/7); four tries, and the secret is revealed if they run out.
- Idioms:
- The candidates are a stream over the shuffled word list,
| head CandidateCount, and a guess is checked withcandidates contains guessrather than a counted loop. - The secret is drawn at random from the candidates, as the book's
random.choicedoes. nextGuessreturns a valid candidate, or an unsetStringwhen the input ends, andplayRoundguards on it (if guess <- console.nextGuess(...)).playRoundplays one try: the right guess ends the game, a wrong one gives the clue and plays the next round with one try fewer, so there is no "won" flag and no loop counter.lettersInPlaceisas pure: a pure function may not count with+=(E08120), so it compares the first letters (#<) and adds the count for the rest of both words.
- The candidates are a stream over the shuffled word list,
- Divergences: the word list is this port's own; the book's "computer memory" garbage display is a plain candidate line; the book's selection of words with a spread of shared letters is simplified to a shuffle.