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 78 — Trick Questions
This is project 78 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:
nine
all of them
an egg
purple
glass
a sponge
Run it with:
BIGBOOK_SEED=1 ek9 trick-questions.ek9 < input.txt
It prints:
Trick Questions, by Al Sweigart al@inventwithpython.com
Can you figure out the answers? (Enter QUIT to stop.)
Question 1 of 6 (Score: 0)
QUESTION: A farmer has 17 sheep and all but 9 run away. How many are left?
Correct! Nine.
Question 2 of 6 (Score: 1)
QUESTION: How many months have 28 days?
Correct! All of them.
Question 3 of 6 (Score: 2)
QUESTION: What has to be broken before you can use it?
Correct! An egg.
Question 4 of 6 (Score: 3)
QUESTION: What gets wetter the more it dries?
Nope. The answer is: A towel.
Question 5 of 6 (Score: 3)
QUESTION: A greenhouse is made of glass. What is it made of?
Correct! Glass.
Question 6 of 6 (Score: 4)
QUESTION: What is full of holes but still holds water?
Correct! A sponge.
Final score: 5 / 6
The program
#!ek9 defines module bigbook.trickquestions defines type List of Question shuffled of Question defines record //A question, the answer to show, and the lowercase keywords any one of which makes a reply //correct. A record keeps the three together, so nothing has to be split back out of a string. Question text as String: String() answer as String: String() accepts as List of String: List() of String Question() -> text as String answer as String accepts as List of String this.text: text this.answer: answer this.accepts: accepts default operator ? 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()) showQuestion() -> number as Integer total as Integer score as Integer question as Question stdout.println("") stdout.println(say.questionHeader(number, total, score)) stdout.println(say.askQuestion(question.text)) //The player's reply, trimmed. Unset when they QUIT or the input ends: either way there is //no reply to mark. nextReply() <- rtn as String: String() if line <- input.next() reply <- line.trim() if reply.upperCase() <> say.quit() rtn: reply showMarked() -> correct as Boolean question as Question stdout.println(correct <- say.correct(question.answer) : say.nope(question.answer)) showFinalScore() -> score as Integer total as Integer stdout.println(say.finalScore(score, total)) showGoodbye() stdout.println(say.goodbye()) ended() as pure <- rtn as Boolean: input.ended() override operator ? as pure <- rtn as Boolean: true defines function //THE GAME: its questions, their order and its rule, with no input or output. //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 game's data: the questions, the answers shown and the keywords a reply is compared with. questionBank() <- rtn as List of Question: [ Question("A farmer has 17 sheep and all but 9 run away. How many are left?", "Nine.", ["9", "nine"]), Question("What has to be broken before you can use it?", "An egg.", ["egg"]), Question("What gets wetter the more it dries?", "A towel.", ["towel"]), Question("A greenhouse is made of glass. What is it made of?", "Glass.", ["glass"]), Question("How many months have 28 days?", "All of them.", ["all", "12", "twelve"]), Question("What is full of holes but still holds water?", "A sponge.", ["sponge"]) ] //A reply is correct when it contains any accepted keyword, so "an egg" and "eggs" both count. isCorrect() as pure -> question as Question reply as String <- rtn as Boolean: false for keyword in question.accepts if reply contains keyword rtn: true 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 or types. The questions and their answers are not here: //they are the game's data, compared with what the player types. defines text for "en" Say introduction() `Trick Questions, by Al Sweigart al@inventwithpython.com Can you figure out the answers? (Enter QUIT to stop.)` questionHeader() -> number as Integer total as Integer score as Integer `Question ${number} of ${total} (Score: ${score})` askQuestion() -> questionText as String `QUESTION: ${questionText}` quit() "QUIT" goodbye() "Thanks for playing!" correct() -> answer as String `Correct! ${answer}` nope() -> answer as String `Nope. The answer is: ${answer}` finalScore() -> score as Integer total as Integer `Final score: ${score} / ${total}` defines program //Trick Questions (Big Book project 78). A quiz of trick questions, asked in a random order. The //game decides; the Console asks and tells, in the player's words. The golden replays a seeded //game (seed.txt). TrickQuestions() //A language with no words of its own is unset: ?: falls back to English. console <- Console(Say(language()) ?: Say("en")) rng <- randomSource() console.showIntroduction() shuffle <- () is shuffled of Question as function questions <- shuffle(questionBank(), rng) total <- length questions score <- 0 asked <- 0 //An iterator rather than a for loop, because QUIT or the end of the input stops the quiz early. remaining <- questions.iterator() running <- true while running and remaining.hasNext() and not console.ended() question <- remaining.next() console.showQuestion(number: asked + 1, total: total, score: score, question: question) if reply <- console.nextReply() correct <- isCorrect(question, reply.lowerCase()) console.showMarked(correct, question) if correct score += 1 asked += 1 else console.showGoodbye() running: false if asked == total console.showFinalScore(score, total)
About this port
- Idioms: the order comes from the inlined generic
shuffled of type T(seelib/Shuffle.ek9); the quiz walks anIterator, because QUIT or the end of the input ends it early and EK9 has nobreak;nextReplyreads one line, so only a reply that was actually read is marked.