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 52 — Numeral Systems Counters

This is project 52 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:

8
10

Run it with:

ek9 numeralsystemcounters.ek9 < input.txt

It prints:

Numeral System Counters, by Al Sweigart al@inventwithpython.com

This program shows you equivalent numbers in decimal (base 10),
hexadecimal (base 16), and binary (base 2) numeral systems.

(Ctrl-C to quit.)

Enter the starting number (e.g. 0) >
Enter how many numbers to display (e.g. 1000) >
DEC: 8    HEX: 8    BIN: 1000
DEC: 9    HEX: 9    BIN: 1001
DEC: 10    HEX: A    BIN: 1010
DEC: 11    HEX: B    BIN: 1011
DEC: 12    HEX: C    BIN: 1100
DEC: 13    HEX: D    BIN: 1101
DEC: 14    HEX: E    BIN: 1110
DEC: 15    HEX: F    BIN: 1111
DEC: 16    HEX: 10    BIN: 10000
DEC: 17    HEX: 11    BIN: 10001

The program

#!ek9
defines module bigbook.numeralsystemcounters

  defines constant

    Hexadecimal <- 16
    Binary <- 2
    DefaultStart <- 0
    DefaultAmount <- 1000

  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())

      //Unset only if the input ends before a usable answer.
      nextStart()
        <- rtn as Integer: askCount(say.askStart(), say.notAStart(), DefaultStart)

      nextAmount()
        <- rtn as Integer: askCount(say.askAmount(), say.notAnAmount(), DefaultAmount)

      showNumerals()
        ->
          decimal as Integer
          hexadecimal as String
          binary as String

        stdout.println(say.numerals(decimal, hexadecimal, binary))

      //Asks until the game takes the reply as a count.
      private askCount()
        ->
          question as String
          refusal as String
          fallback as Integer
        <- rtn as Integer: Integer()

        while not rtn? and not input.ended()
          stdout.println(question)
          if entry <- input.next()
            rtn: countFrom(entry.trim(), fallback)
            if not rtn?
              stdout.println(refusal)

      override operator ? as pure
        <- rtn as Boolean: true

  defines function

    //THE GAME: the numbers and their numerals, with no input or output.

    //A count typed by the player: an empty reply takes the fallback, a whole number of zero or
    //more is taken as it is, anything else is unset so the caller asks again.
    countFrom() as pure
      ->
        reply as String
        fallback as Integer
      <- rtn as Integer: Integer()

      if reply == ""
        rtn: fallback
      else
        count <- Integer(reply)
        if count? and count >= 0
          rtn: count

    //Integer.radix(base) does the conversion; the book shows hexadecimal in capitals.
    inHexadecimal() as pure
      -> number as Integer
      <- rtn as String: number.radix(Hexadecimal).upperCase()

    inBinary() as pure
      -> number as Integer
      <- rtn as String: number.radix(Binary)

  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.
  defines text for "en"

    Say

      introduction()
        `Numeral System Counters, by Al Sweigart al@inventwithpython.com

This program shows you equivalent numbers in decimal (base 10),
hexadecimal (base 16), and binary (base 2) numeral systems.

(Ctrl-C to quit.)
`

      askStart()
        "Enter the starting number (e.g. 0) >"

      notAStart()
        "Please enter a number greater than or equal to 0."

      askAmount()
        "Enter how many numbers to display (e.g. 1000) >"

      notAnAmount()
        "Please enter a number."

      numerals()
        ->
          decimal as Integer
          hexadecimal as String
          binary as String
        `DEC: ${decimal}    HEX: ${hexadecimal}    BIN: ${binary}`

  defines program

    //Numeral System Counters (Big Book project 52). Shows a run of numbers in decimal,
    //hexadecimal and binary. The game decides; the Console asks and tells, in the player's words.
    NumeralSystemCounters()
      //A language with no words of its own is unset: ?: falls back to English.
      console <- Console(Say(language()) ?: Say("en"))

      console.showIntroduction()
      start <- console.nextStart()
      amount <- console.nextAmount()

      //A range runs in either direction, so `start ... start - 1` would count DOWN: guard the
      //empty case instead of relying on the range to be empty.
      if start? and amount? and amount > 0
        for number in start ... start + amount - 1
          console.showNumerals(number, inHexadecimal(number), inBinary(number))

About this port