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 51 — niNety-nniinE bOttels

This is project 51 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.

Run it with:

BIGBOOK_SEED=1 ek9 ninetynniineboottels.ek9

It prints:

niNety-nniinE BoOttels, by Al Sweigart al@inventwithpython.com

(Press Ctrl-C to quit.)
99 bottles of milk on the wall,
99 bottles of milk,
Take one down, pass it around,
98 bottles of milk on the wall!

98 bottles of milko n the wall,
98 bottles of milk,
Take one down, pass it around,
97 bottles of milk on the wall!

97 bottles of milko n the wall,
97 bo tles of milk,
Take one down, pass it around,
96 bottles of milk on the wall!

96 bottles of milko n the wall,
96 bo tles of milk,
Take one down, pass it around,
95 bottles of milk on the wall!

95 bottles of milko n the wall,
95 bo tles of milk,
Take one down, pass it around,
94 bottles of milk on th  wall!

94 bottles of milko n the wall,
94 bo tles of milk,
Take one down, pass it  around,
93 bottles of milk on th  wall!

93 bottles of milko n the wall,
93 bo tles of milk,
Take one down, pass it  around,
92 bottles of milk on th  wall!

92 bottles of milko n the waLl,
92 bo tles of milk,
Take one down, pass it  around,
91 bottles of milk on th  wall!

91 bottles of milko n the waLl,
91 bo tles of milk,
Take one dOwn, pass it  around,
90 bottles of milk on th  wall!

90 bottles of milko n the waLl,
90 bo tles of milk,
Take one dOwn, pass it  around,
89 bottles of milk on th  walll!

89 bottles of milko n the waLl,
89 bo tles of milk,
Take one dOwn, pass it  around,
88 bottles of miilk on th  walll!

88 bottles of milko n the waLl,
88 bo tles of milk,
Take one dOwn, pass it  around,
87 bottles of miilk on th  waalll!

87 bottles of milko n the wa l,
87 bo tles of milk,
Take one dOwn, pass it  around,
86 bottles of miilk on th  waalll!

86 bottles of milok n the wa l,
86 bo tles of milk,
Take one dOwn, pass it  around,
85 bottles of miilk on th  waalll!

85 bottles of milok n the wa l,
85 bo tles of milk,
Take one dOwn, paSs it  around,
84 bottles of miilk on th  waalll!

84 bottles of milok n the wa l,
84 bo tles of milk,
Take one dOwn, paSs it  around,
83 bottlees of miilk on th  waalll!

83 bottles of milok n the wa l,
83 bo tles of milk,
Take one dOwn, paSs it  around,
82 bottlees of miilk on th  waalll!

82 bottles of milok n the wa l,
82 bo tles of milk,
Take one dOwn, paSs it  around,
81 bottlees of miilk on th  waall !

81 bottles of milok n the wa l,
81 bo tles of milk,
Take one dOwn, paSs it  around,
80 bott ees of miilk on th  waall !

80 bottles of milok n the wa l,
80 bo tles of milk,
Take one dOwn, paSs it  around,
79 bo t ees of miilk on th  waall !

79 bottles of milok n the wa l,
79 boo tles of milk,
Take one dOwn, paSs it  around,
78 bo t ees of miilk on th  waall !

78 bottles of milok n the wa l,
78 boo tles of milk,
Take one dOwn, paSs it  around,
77 bo t ees of miilk on th  waall !

77 bottles of milok n the wa l,
77 boo tles of milk,
Take on edOwn, paSs it  around,
76 bo t ees of miilk on th  waall !

76 bottles of milok n the wa l,
76 boo tles of milk,
take on edOwn, paSs it  around,
75 bo t ees of miilk on th  waall !

75 bottles of milok n the wa l,
75 boo tles of milk,
take on edOwn, paSs it  around,
74 bo t ees of miilk on th  waall !

74 bottles of milok n the wa l,
74 boo tles oof milk,
take on edOwn, paSs it  around,
73 bo t ees of miilk on th  waall !

73 bottless of milok n the wa l,
73 boo tles oof milk,
take on edOwn, paSs it  around,
72 bo t ees of miilk on th  waall !

72  bottless of milok n the wa l,
72 boo tles oof milk,
take on edOwn, paSs it  around,
71 bo t ees of miilk on th  waall !

71  bottless of milok n the wa l,
71 boo tles oof milk,
take on edOwn, paSs it  around,
70 bo t ees of miilk on th  waall !

70  bottless of milok n the wa l,
70 boo tles oof milk,
take on edOwn, paSs it  around,
69 bo t ees of miilk on th  waall !

69  bottless of milok n the wa l,
69 boo tles oof milk,
take on edOwn, paSs it  around,
68 bo t ees of miilk on th  waall !

68  bottless of milok n the wa l,
68 boo tles oof milK,
take on edOwn, paSs it  around,
67 bo t ees of miilk on th  waall !

67  bottless of milok n the wa l,
67 boo tles oof milK,
take on edOwn, paSs it  around,
66 bo t ees of miilk on th w aall !

66  bottless of milok n the wa l,
66 boo tles oof milK,
take on edOwn, paSs it  aro nd,
65 bo t ees of miilk on th w aall !

65  bottless of milok n the wa l,
65 boo tles ooff milK,
take on edOwn, paSs it  aro nd,
64 bo t ees of miilk on th w aall !

64  bottless of milok n the wa l,
64 boo tles ooff milK,
take no edOwn, paSs it  aro nd,
63 bo t ees of miilk on th w aall !

63  bottless of milok n the wa l,
63 boo tles ooff milK,
take no edOwn, paSs it  aro dn,
62 bo t ees of miilk on th w aall !

62  bottless of milok n the wa l,
62 boo tles ooff milK,
take no edOwn, paSs it  aro dn,
61 bo t ees of miilk on th w aall !

61  bottless of milok n the wa l,
61 boo tles ooff milK,
take no edOwn, paSs it  aro dn,
60 bo t ees of miilk on th w aall !

60  bottless of milok n the wa l,
60 bboo tles ooff milK,
take no edOwn, paSs it  aro dn,
59 bo t ees of miilk on th w aall !

59  bottless of milok n the wa l,
59 bboo tles ooff milK,
take no edOwn, paSs it  aro dn,
58 bo t ees of miilk on th w aall !

58  bottless of milok n the wa l,
58 bboo tles ooff milK,
take no edOwn, paSs it  aro dn,
57 bo t ees of miil kon th w aall !

57  bottless ofm ilok n the wa l,
57 bboo tles ooff milK,
take no edOwn, paSs it  aro dn,
56 bo t ees of miil kon th w aall !

56  bottless ofm il k n the wa l,
56 bboo tles ooff milK,
take no edOwn, paSs it  aro dn,
55 bo t ees of miil kon th w aall !

55  bottless ofm il k n the wa l,
55 bboo tles ooff milK,
take no edOwn, paSs it  aro dn,
54 bo t ees of miil kon th w Aall !

54  bottless ofm il k n the wa l,
54 bboo tles ooff milK,
take no edOwn, apSs it  aro dn,
53 bo t ees of miil kon th w Aall !

53  bottless ofm il k n the wa  l,
53 bboo tles ooff milK,
take no edOwn, apSs it  aro dn,
52 bo t ees of miil kon th w Aall !

52  bottless ofm il k n the wa  l,
52 bboo tles ooff milK,
take no edOwn, apSs it  aro dn,
51 boo t ees of miil kon th w Aall !

51  bottless ofm il k n the wa  l,
51 bboo tles ooff milK,
take no edOwn, apSs it  aro dn,
50 boo t ees of miil kon thw  Aall !

50  bottless ofm il k n the wa  l,
50 bboo tles ooff milK,
take no edOwn, apSs it  aro dn,
49 boo t ees of miil kon thw  Aall !

49  bottless ofm il k n the wa  l,
49 bboo tles ooff milK,
take no edOwn, apSs it  aro dnn,
48 boo t ees of miil kon thw  Aall !

48  bottless ofm il k n the wa  l,
48 bboO tles ooff milK,
take no edOwn, apSs it  aro dnn,
47 boo t ees of miil kon thw  Aall !

47  bottless ofm il k n the wa  l,
47 bboOO tles ooff milK,
take no edOwn, apSs it  aro dnn,
46 boo t ees of miil kon thw  Aall !

46  bottless ofm il k n the wa  l,
46 bboOO tles ooff milK,
take no edOwn, apSs it  aro dnn,
45 boo t ees of miil kon thw  Aall !

45  bottless ofm il k n the wa  L,
45 bboOO tles ooff milK,
take no edOwn, apSs it  aro dnn,
44 boo t ees of miil kon thw  Aall !

44  bottless ofm il k n the wa  L,
44 bboOO tles ooff milK,
take no edOwn ,apSs it  aro dnn,
43 boo t ees of miil kon thw  Aall !

43  bottless ofm il k n the wa  L,
43 bboOO tles ooff milK,
take nno edOwn ,apSs it  aro dnn,
42 boo t ees of miil kon thw  Aall !

42  bottless ofm il k n the wa  L,
42 bboOO tles ooff milK,
take nno edOwn ,apSs it  aro dnn,
41 boo t ees of miil kon thw  Aall !

41  bottless ofm il k n the aw  L,
41 bboOO tles ooff milK,
take nno edOwn ,apSs it  aro dnn,
40 boo t ees of miil kon thw  Aall !

40  bbottless ofm il k n the aw  L,
40 bboOO tles ooff milK,
take nno edOwn ,apSs it  aro dnn,
39 boo t ees of miil kon thw  Aall !

39  bbottless ofm il k n the aw  L,
39 bboOO tles ooff milK,
take nno edOwn ,apSs it  aro dnn,
38 boo t eees of miil kon thw  Aall !

38  bbottless ofm il k n the aw  L,
38 bboOO tles ooff milK,
take nno edOwn ,apSs it  aro dnn,
37 boo t eees of  miil kon thw  Aall !

37  bbottless ofm il k n the aw  L,
37 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
36 boo t eees of  miil kon thw  Aall !

36  bbottless ofm il k n the aw  L,
36 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
35 boo t eees of  miil kon thw  A ll !

35  bbottless ofm il k n the aw  L,
35 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
34 boo t eees of  miil kon tHw  A ll !

34  bbottless ofm il k n thE aw  L,
34 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
33 boo t eees of  miil kon tHw  A ll !

33  bbottless ofm il k n thE aw  L,
33 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
32 boo t eees of  miil kon twH  A ll !

32  bbottless ofm il k n thE aw  L,
32 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
31 boo t eees of  miil kon twH  A ll !

31  bbottless ofm il k n thE aw  L,
31 bboOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
30  oo t eees of  miil kon twH  A ll !

30  bbottless ofm il k n thE aw  L,
30 b oOO tles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
29  oo t eees of  miil kon twH  A ll !

29  bbottless ofm il k n thE aw  L,
29 b oOO ttles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
28  oo t eees of  miil kon twH  A ll !

28  bbottless ofm il k n thE aw  L,
28 b oOO ttles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
27  oo   eees of  miil kon twH  A ll !

27  bbottless ofm il k n thE aw  L,
27 b oOO ttles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
26  oo   eees of  miil kon twH  A ll !

26  bbottless ofm il k n thE aw  L,
26 b oOO ttles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
25  o    eees of  miil kon twH  A ll !

25  bbottlEss ofm il k n thE aw  L,
25 b oOO ttles ooff milK,
take nno  edOwn ,apSs it  aro dnn,
24  o    eees of  miil kon twH  A ll !

24  bbottlEss ofm il k n thE aw  L,
24 b oOO ttles ooff milK,
t ke nno  edOwn ,apSs it  aro dnn,
23  o    eees of  miil kon twH  A ll !

23  bbottlEss ofm il k n thE aw  L,
23 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
22  o    eees of  miil kon twH  A ll !

22  bbottlEss ofm il k n thE aw  L,
22 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
21  o    eees of  miil kon twH  A ll !

21  bbottlEss ofm il k n thE aw  L,
21 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
20  o    eees of  miil kon twH  A ll !

20  bbottlEss ofm il k N thE aw  L,
20 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
19  o    eees of  miil kon twH  A ll !

19  bbottlEss ofm il k N thE aw  L,
19 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
18  o    eees of   miil kon twH  A ll !

18  bbottlEss ofm il k N thE aw  L,
18 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
17  o    eees of   miil kon twH  A ll !

17  bbottlEss ofm il k N thE aw  L,
17 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
16  o    eees of   miill kon twH  A ll !

16  bbottlEss ofm il k N thE aw  L,
16 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
15  o    eees of   miill kon twH  A ll !

15  bbottlEss ofm il k N thE aw  L,
15 b oOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
14  o     eees of   miill kon twH  A ll !

14  bbottlEss ofm il k N thE aw  L,
14 b OOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
13  o     eees of   miill kon twH  A ll !

13  bbottLEss ofm il k N thE aw  L,
13 b OOO ttles ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
12  o     eees of   miill kon twH  A ll !

12  bbottLEss ofm il k N thE aw  L,
12 b OOO ttLes ooff imlK,
t ke nno  edOwn ,apSs it  aro dnn,
11  o     eees of   miill kon twH  A ll !

11  bbottLEss ofm il k N thE aw  L,
11 b OOO ttLes ooff imKl,
t ke nno  edOwn ,apSs it  aro dnn,
10  o     eees of   miill kon twH  A ll !

10  bbottLEss ofm il k N thE aw  L,
10 b OOO ttLes ooff imKl,
t ke nno  edOwn ,apSs it  aro dnn,
9  o     eees of   miill kon twH  A ll !

9  bbottLEss ofm il k N thE aw  L,
9 b OOO ttLes ooff imKl,
t ke nno  edOwn ,apSs it  aro dnn,
8  o     eees of   miill kon twH  A ll !

8  bbottLEss ofm il k N thE aw  L,
8 b OOO ttLes ooff imKl,
t ke nno  edOwn ,apSs it  aro dnn,
7  o     eees of   miill kon twH  A ll !

7  bbottLEss ofm il k N thE aw  L,
7 b OOO ttLes ooff imKl,
t ke nno  edOwn ,paSs it  aro dnn,
6  o     eees of   miill kon twH  A ll !

6  bbottLEss ofm il k N thE aw  L,
6 b OOO ttLes ooff imKl,
t ke nno e dOwn ,paSs it  aro dnn,
5  o     eees of   miill kon twH  A ll !

5  bbottLEss ofm il k N thE aw  L,
5 b OOO ttLes ooff imKl,
t ke nno E dOwn ,paSs it  aro dnn,
4  o     eees of   miill kon twH  A ll !

4  bbottLEss ofm il k N thE aw  L,
4 b OOO ttLes ooff imKl,
t ke nno E dOwn ,paSs it  aro dnn,
3  o     eees of   miill kon twH  A ll !

3  bbottLEss ofm il k N  hE aw  L,
3 b OOO ttLes ooff imKl,
t ke nno E dOwn ,paSs it  aro dnn,
2  o     eees of   miill kon twH  A ll !

2  bbottLEss ofm il k N  hE aw  L,
2 b OOO ttLes ooff imKl,
t ke nno E dOwn ,paSs it  aro dnn,
1  o     eees of   miill kon twH  A ll !

1  bbottLEss ofm il k N  hE aw  L,
1 b OOO ttLes ooff imKl,
t ke nno E dOwn ,paSs it  aro dnn,
No more bottles of milk on the wall!

The program

#!ek9
defines module bigbook.ninetynniineboottels

  defines constant

    StartBottles <- 99

    //The song's last line, after the last bottle. It never gets sillier, so it is not one of
    //the lyrics the game changes.
    NoMoreBottles <- "No more bottles of milk on the wall!"

  defines type

    //The four lines of a verse, in the order they are sung.
    Lyric
      OnTheWall
      OfMilk
      TakeOneDown
      OnTheWallStill

    //The four ways a line gets sillier after each verse.
    Effect
      Blank
      FlipCase
      Transpose
      Double

    List of Lyric
    List of Effect
    Dict of (Lyric, String)

  defines class

    //THE OUTPUT: everything the listener sees goes through here, worded by Say. The song reads
    //no input, so there is no LineInput.
    Console
      stdout as Stdout: Stdout()
      say as Say: Say("en")

      Console()
        -> words as Say
        say: words

      showIntroduction()
        stdout.println(say.introduction())

      //The verse is the game's, sung as it now stands; the blank line after it is layout, so
      //the Console prints it.
      showVerse()
        -> lines as List of String
        for line in lines
          stdout.println(line)
        stdout.println("")

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

  defines function

    //THE GAME: the song and how it gets sillier, with no output or words. The lyrics are game
    //state, not text: the game changes them as the song goes on.

    //The lyrics as the song starts, before any line has been made sillier.
    startingLyrics() as pure
      <- rtn as Dict of (Lyric, String): {
        Lyric.OnTheWall: " bottles of milk on the wall,",
        Lyric.OfMilk: " bottles of milk,",
        Lyric.TakeOneDown: "Take one down, pass it around,",
        Lyric.OnTheWallStill: " bottles of milk on the wall!"
      }

    //The four lines of the verse for this many bottles, with the lyrics as they now are.
    verseOf() as pure
      ->
        bottles as Integer
        lyrics as Dict of (Lyric, String)
      <- rtn as List of String: List()

      onTheWall <- `${bottles}${lyrics.getOrDefault(Lyric.OnTheWall, "")}`
      ofMilk <- `${bottles}${lyrics.getOrDefault(Lyric.OfMilk, "")}`
      takeOneDown <- lyrics.getOrDefault(Lyric.TakeOneDown, "")
      remaining <- bottles - 1
      onTheWallStill <- remaining > 0 <- `${remaining}${lyrics.getOrDefault(Lyric.OnTheWallStill, "")}` : NoMoreBottles
      rtn: [onTheWall, ofMilk, takeOneDown, onTheWallStill]

    flippedCase() as pure
      -> text as String
      <- rtn as String: text == text.upperCase() <- text.lowerCase() : text.upperCase()

    //How many characters from the chosen position an effect works on. As in the book,
    //doubling also stops one short of the end.
    charactersUsed() as pure
      -> effect as Effect
      <- rtn as Integer: 1
      if effect == Effect.Transpose or effect == Effect.Double
        rtn: 2

    //The line with one effect applied at one position. Strings are cut and rejoined with
    //subString rather than edited in place.
    sillier() as pure
      ->
        line as String
        effect as Effect
        position as Integer
      <- rtn as String: line

      before <- line.subString(0, position)
      here <- line.subString(position, position + 1)
      after <- line.subString(position + 1)
      switch effect
        case Effect.Blank
          rtn: `${before} ${after}`
        case Effect.FlipCase
          rtn: `${before}${flippedCase(here)}${after}`
        case Effect.Transpose
          next <- line.subString(position + 1, position + 2)
          rest <- line.subString(position + 2)
          rtn: `${before}${next}${here}${rest}`
        case Effect.Double
          rtn: `${before}${here}${here}${after}`
        default
          rtn: line

    //Pick a line, an effect and a position at random, in the book's order, and make that
    //line sillier. The choices come from the enumerations, so their sizes are not repeated.
    makeSillier()
      ->
        lyrics as Dict of (Lyric, String)
        rng as Random

      allLyrics <- cat Lyric | collect as List of Lyric
      allEffects <- cat Effect | collect as List of Effect
      lyric <- allLyrics.getOrDefault(rng.next(0, length allLyrics - 1), Lyric.OnTheWall)
      effect <- allEffects.getOrDefault(rng.next(0, length allEffects - 1), Effect.Blank)
      line <- lyrics.getOrDefault(lyric, "")
      position <- rng.next(0, length line - charactersUsed(effect))
      lyrics += DictEntry(lyric, sillier(line, effect, position))

  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 listener sees that the game does not change. The song's lines are
  //not here: they are the game's lyrics.
  defines text for "en"

    Say

      introduction()
        `niNety-nniinE BoOttels, by Al Sweigart al@inventwithpython.com

(Press Ctrl-C to quit.)`

  defines program

    //niNety-nniinE BoOttels (Big Book project 51). The bottles song, where every verse leaves one
    //line a little sillier than before. The game decides; the Console shows, in the reader's
    //words. The golden replays a seeded run (seed.txt).
    NiNetyNniineBoottels()
      //A language with no words of its own is unset: ?: falls back to English.
      console <- Console(Say(language()) ?: Say("en"))
      rng <- randomSource()
      lyrics <- startingLyrics()

      console.showIntroduction()
      for bottles in StartBottles ... 1
        console.showVerse(verseOf(bottles, lyrics))
        makeSillier(lyrics, rng)

About this port