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 70 — Soroban Japanese Abacus
This is project 70 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:
42
quit
Run it with:
ek9 soroban.ek9 < input.txt
It prints:
Soroban - The Japanese Abacus
+=====================+
I O O O O O O O O O O I
I | | | | | | | | | | I
I | | | | | | | | | | I
+=====================+
I | | | | | | | | | | I
I | | | | | | | | | | I
I O O O O O O O O O O I
I O O O O O O O O O O I
I O O O O O O O O O O I
I O O O O O O O O O O I
+=====================+
0 0 0 0 0 0 0 0 0 0
+q w e r t y u i o p
-a s d f g h j k l ;
(Enter a number, "quit", or a stream of up/down letters.)
+=====================+
I O O O O O O O O O O I
I | | | | | | | | | | I
I | | | | | | | | | | I
+=====================+
I | | | | | | | | O O I
I | | | | | | | | O O I
I O O O O O O O O O | I
I O O O O O O O O O | I
I O O O O O O O O | O I
I O O O O O O O O | O I
+=====================+
0 0 0 0 0 0 0 0 4 2
+q w e r t y u i o p
-a s d f g h j k l ;
(Enter a number, "quit", or a stream of up/down letters.)
The program
#!ek9 defines module bigbook.soroban defines constant Columns <- 10 Largest <- 9999999999 RaiseKeys <- "qwertyuiop" LowerKeys <- "asdfghjkl;" //The abacus's own marks, data like the beads: the frame, a bead, a bare rod, and how far the //digits under the rods are indented (this port's own layout). Corner <- "+" Rail <- '=' Frame <- "I" Bead <- " O" Rod <- " |" DigitIndent <- " " defines type List 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 abacus is drawn, not worded: computed from the number, a row of rods at a time, with //the frame sized to Columns. showAbacus() -> number as Integer shown <- $number digits <- shown.zeroPad(Columns) border <- frameEdge() stdout.println(border) for members in heavenRows() stdout.println(beadRow(digits, members)) stdout.println(border) for members in earthRows() stdout.println(beadRow(digits, members)) stdout.println(border) stdout.println(`${DigitIndent}${spacedOut(digits)}`) //The keys are the game's; the Console spaces them out under the rods they move. showControls() stdout.println(say.controls(spacedOut(RaiseKeys), spacedOut(LowerKeys))) //A number or a run of keys; unset when the player quits or the input ends. nextCommands() <- rtn as String: String() if line <- input.next() commands <- line.trim() if commands <> say.quit() rtn: commands ended() as pure <- rtn as Boolean: input.ended() override operator ? as pure <- rtn as Boolean: true defines function //THE GAME: the abacus's rules, with no input or output. //The value of one bead step in each column, left to right. Key n of RaiseKeys and of //LowerKeys works on column n, so indexOf on the key string finds the column. placeValue() as pure -> column as Integer <- rtn as Integer: 0 values <- [1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1] rtn: values.getOrDefault(column, 0) //What one control key adds to the abacus; 0 for any other character. keyAmount() as pure -> key as Character <- rtn as Integer: 0 if raised <- RaiseKeys.indexOf(key) rtn: placeValue(raised) else if lowered <- LowerKeys.indexOf(key) rtn: -placeValue(lowered) //A text of digits that does not fit in an Integer is larger than the abacus anyway. clamped() as pure -> amount as Integer <- rtn as Integer: Largest if amount? and amount < 0 rtn: 0 else if amount? and amount <= Largest rtn: amount //The number on the abacus once the player's commands are applied: a number sets it, a run //of keys moves its beads up and down. abacusAfter() -> abacus as Integer commands as String <- rtn as Integer: Integer() if commands matches /\d+/ rtn: clamped(Integer(commands)) else //A copy, so the keys do not change abacus itself before it is clamped. total <- Integer(abacus) for key in commands total += keyAmount(key) rtn: clamped(total) //The book's rules for which digits put a bead on each row: two heaven rows either side of //a bare rod row (no digit is in ""), then six earth rows. heavenRows() as pure <- rtn as List of String: ["01234", "", "56789"] earthRows() as pure <- rtn as List of String: ["12346789", "234789", "034589", "014569", "012567", "01235678"] defines function //The Console's drawing: computed output, with no words in it. //The top, middle and bottom of the frame: a rail as wide as the rods and the frame's sides. frameEdge() <- rtn as String: String() rail <- String("").rightPadded((Columns * 2) + 1, Rail) rtn: `${Corner}${rail}${Corner}` //One row of rods: a bead in each column whose digit is in members, else a bare rod. beadRow() -> digits as String members as String <- rtn as String: Frame for digit in digits rtn += members contains $digit <- Bead : Rod rtn += ` ${Frame}` //The characters of a text with a space between each: "0 0 4 2", "q w e". spacedOut() -> text as String <- rtn as String: "" first <- true for character in text rtn += first <- $character : ` ${character}` first: false //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 abacus and its marks are not here: the //Console draws them from the number. defines text for "en" Say introduction() "Soroban - The Japanese Abacus" controls() -> raiseKeys as String lowerKeys as String ` +${raiseKeys} -${lowerKeys} (Enter a number, "quit", or a stream of up/down letters.)` quit() "quit" defines program //Soroban Japanese Abacus (Big Book project 70). Shows a number as abacus beads; type a number //to set it, or a run of keys to move the beads. The game decides; the Console asks and tells, //in the player's words. Soroban() //A language with no words of its own is unset: ?: falls back to English. console <- Console(Say(language()) ?: Say("en")) console.showIntroduction() abacus <- 0 running <- true while running and not console.ended() console.showAbacus(abacus) console.showControls() if commands <- console.nextCommands() abacus: abacusAfter(abacus, commands) else running: false
About this port
- Behaviour: a 10-column abacus. Type a number to set it, or a run of control keys:
q..padd 1,000,000,000 down to 1 anda..;subtract the same amounts. The value is kept between 0 and 9,999,999,999, andquitends. Which rows show a bead for each digit follows the book's rules exactly. - Idioms:
- The book's twenty-branch
if letter == 'q' ... elif letter == ';'chain becomesRaiseKeys.indexOf(key)andLowerKeys.indexOf(key): the key's position is its column, and a table of place values gives the amount.indexOfis unset for any other character, so the guards also ignore stray keys. - "Is this a number?" is
commands matches /\d+/. A run of digits too long for anIntegerparses as unset, andclampedtreats that as the largest value, as the book's clamp does. - Each row, the bare rod row between the heaven beads included, is one
beadRowcall with the digits that put a bead on it (none,"", for the bare rods), so every row followsColumns; the six earth rows are a list walked by aforloop. placeValue,keyAmountandclampedareas pure.
- The book's twenty-branch
- Divergence: the frame is this port's own, without the book's digits in the bottom border.