Ninety Nniine Boottels
In this version of the song “Ninety-Nine Bottles,” the program introduces small imperfections in each stanza by either removing a letter, swapping the casing of a letter, transposing two letters, or doubling a letter.
As the song continues to play, these mutations add up, resulting in a very silly song. It’s a good idea to try Project 50, “Ninety-Nine Bottles,” before attempting this one.
String values in Python are immutable, meaning they cannot be changed. If the string ‘Hello’ is stored in a variable called greeting, the code greeting = greeting + ‘ world!’ doesn’t actually change the ‘Hello’ string. Rather, it creates a new string, ‘Hello world!’, to replace the ‘Hello’ string in greeting. The technical reasons for this are beyond the scope of this book, but it’s important to understand the distinction, because it means code like greeting[0] = ‘h’ isn’t allowed, since strings are immutable. However, since lists are mutable, we can create a list of single-character strings (as line 62 does), change the characters in the list, and then create a string from the list (line 85). This is how our program seemingly changes, or mutates, the strings containing the song lyrics.
ninety_nniine_boottels.py
1"""niNety-nniinE BoOttels of Mlik On teh waLl
2By Al Sweigart al@inventwithpython.com
3Print the full lyrics to one of the longest songs ever! The song
4gets sillier and sillier with each verse. Press Ctrl-C to stop.
5This code is available at https://nostarch.com/big-book-small-python-programming
6Tags: short, scrolling, word"""
7
8import random, sys, time
9
10# Set up the constants:
11# (!) Try changing both of these to 0 to print all the lyrics at once.
12SPEED = 0.01 # The pause in between printing letters.
13LINE_PAUSE = 1.5 # The pause at the end of each line.
14
15
16def slowPrint(text, pauseAmount=0.1):
17 """Slowly print out the characters in text one at a time."""
18 for character in text:
19 # Set flush=True here so the text is immediately printed:
20 print(character, flush=True, end='') # end='' means no newline.
21 time.sleep(pauseAmount) # Pause in between each character.
22 print() # Print a newline.
23
24
25print('niNety-nniinE BoOttels, by Al Sweigart al@inventwithpython.com')
26print()
27print('(Press Ctrl-C to quit.)')
28
29time.sleep(2)
30
31bottles = 99 # This is the starting number of bottles.
32
33# This list holds the string used for the lyrics:
34lines = [' bottles of milk on the wall,',
35 ' bottles of milk,',
36 'Take one down, pass it around,',
37 ' bottles of milk on the wall!']
38
39try:
40 while bottles > 0: # Keep looping and display the lyrics.
41 slowPrint(str(bottles) + lines[0], SPEED)
42 time.sleep(LINE_PAUSE)
43 slowPrint(str(bottles) + lines[1], SPEED)
44 time.sleep(LINE_PAUSE)
45 slowPrint(lines[2], SPEED)
46 time.sleep(LINE_PAUSE)
47 bottles = bottles - 1 # Decrease the number of bottles by one.
48
49 if bottles > 0: # Print the last line of the current stanza.
50 slowPrint(str(bottles) + lines[3], SPEED)
51 else: # Print the last line of the entire song.
52 slowPrint('No more bottles of milk on the wall!', SPEED)
53
54 time.sleep(LINE_PAUSE)
55 print() # Print a newline.
56
57 # Choose a random line to make "sillier":
58 lineNum = random.randint(0, 3)
59
60 # Make a list from the line string so we can edit it. (Strings
61 # in Python are immutable.)
62 line = list(lines[lineNum])
63
64 effect = random.randint(0, 3)
65 if effect == 0: # Replace a character with a space.
66 charIndex = random.randint(0, len(line) - 1)
67 line[charIndex] = ' '
68 elif effect == 1: # Change the casing of a character.
69 charIndex = random.randint(0, len(line) - 1)
70 if line[charIndex].isupper():
71 line[charIndex] = line[charIndex].lower()
72 elif line[charIndex].islower():
73 line[charIndex] = line[charIndex].upper()
74 elif effect == 2: # Transpose two characters.
75 charIndex = random.randint(0, len(line) - 2)
76 firstChar = line[charIndex]
77 secondChar = line[charIndex + 1]
78 line[charIndex] = secondChar
79 line[charIndex + 1] = firstChar
80 elif effect == 3: # Double a character.
81 charIndex = random.randint(0, len(line) - 2)
82 line.insert(charIndex, line[charIndex])
83
84 # Convert the line list back to a string and put it in lines:
85 lines[lineNum] = ''.join(line)
86except KeyboardInterrupt:
87 sys.exit() # When Ctrl-C is pressed, end the program.