Contents

Learning Python By Example #4: Message in a Bitmap

I'm just a hash away...

My Intro

I’m improving my Python skills 1981 style by coding through The Big Book of Small Python Projects by Al Sweigart.

Here’s a direct link to read All Sweigart’s “Bitmap Message” code, though I suggest you buy the book if you’re finding the code useful.

How many words is a picture worth?

Back in the day, ASCII art was a thing. For example, this bear:

1 __         __
2/  \.-"""-./  \
3\    -   -    /
4 |   o   o   |
5 \  .-'''-.  /
6  '-\__Y__/-'
7     `---`    (from asciiart.eu, unattributed)

Using a monospaced font and all the characters (letters, numbers, and symbols) available in the ASCII character set, people would make pictures out of characters.

In this project, we take an ASCII art image made up of spaces and stars, created to look like a world map (sort of), and then use substitution to replace the stars with a message input by the person running the program.

Getting set up

As per usual, I’ll copypasta Al’s intro code, plus I’ll do the same with the ASCII image. As much as it would add to the 1981 experience realism to copy it by hand, it would be tedious and not educational.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""Bitmap Message, by Al Sweigart al@inventwithpython.com
Displays a text message according to the provided bitmap image.
View this code at https://nostarch.com/big-book-small-python-projects
Tags: tiny, beginner, artistic"""

import sys

# (!) Try changing this multiline string to any image you like:

# There are 68 periods along the top and bottom of this string:
# (You can also copy and paste this string from
# https://inventwithpython.com/bitmapworld.txt)
bitmap = """
....................................................................
   **************   *  *** **  *      ******************************
  ********************* ** ** *  * ****************************** *
 **      *****************       ******************************
          *************          **  * **** ** ************** *
           *********            *******   **************** * *
            ********           ***************************  *
   *        * **** ***         *************** ******  ** *
               ****  *         ***************   *** ***  *
                 ******         *************    **   **  *
                 ********        *************    *  ** ***
                   ********         ********          * *** ****
                   *********         ******  *        **** ** * **
                   *********         ****** * *           *** *   *
                     ******          ***** **             *****   *
                     *****            **** *            ********
                    *****             ****              *********
                    ****              **                 *******   *
                    ***                                       *    *
                    **     *                    *
...................................................................."""

print('Bitmap Message, by Al Sweigart al@inventwithpython.com')
print('Enter the message to display with the bitmap.')
message = input('> ')
if message == '':
  sys.exit()

Opening thoughts

There’s not much to this in the beginning. The code imports the sys package, which hasn’t been used before in these programs. It’s used on line 40 where, instead of repeating the question until getting a valid answer, the code exits if nothing is input.

I have a feeling we’ll see sys again as I go through this collection, especially for the sys.argv list, which gives us all the arguments passed on the command line. In these scripts so far, all the input is requested with input() (line 38 in this), but I’m hoping a few will take command-line arguments.

The rest of the code

This is actually a fairly short script, because all that’s left is to replace the stars in the image with the message.

42
43
44
45
46
47
48
49
50
51
52
# Loop over each line in the bitmap:
for line in bitmap.splitlines():
  # Loop over each character in the line
  for i, bit in enumerate(line):
    if bit == ' ':
      # Print an empty space like in the original
      print(' ', end='')
    else:
      # Print a char from the message
      print(message[i % len(message)], end='')
  print() # print a newline

Snippet thoughts

It starts by using a built-in function of the string type, splitlines(), breaking the string into a list of its pieces, using the newline character (or others) as the break-point.

It then uses enumerate(line) to split the line into a tuple it can iterate through to read all the characters in it one-by-one.

And worth noting, the end='' in the first two print statements is important. The print() statement will default to ending with a newline character. By specifying that the end should be nothing, it allows the next character to be printed immediately to the right of the prior character.

This is similar to Commodore BASIC in the 80s, where putting a semicolon after the closing quote on a string would stop it from printing a newline.

/img/post-content/09-2021/C64-DEMO.jpg

Then, when the script finishes iterating through a line, it drops an empty print() to end the line with a newline.

Maintain the borders?

One thing I didn’t like was that the top and bottom borders are turned into the message too. This could be fixed with an elif at 49.

49
50
    elif bit == '.':
      print('.', end='')

And what about spaces in the message itself?

If your message is simply one or more spaces, you’ll get a blank image. But it just takes another nexted if-then-else to replace spaces in the message with another character.

Line 46 checks if the character in the ASCII image is a space, but a check for spaces in the message would go at line 53 (if we’re also preserving the borders)

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Loop over each line in the bitmap:
for line in bitmap.splitlines():
  # Loop over each character in the line
  for i, bit in enumerate(line):
    if bit == ' ':
      # Print an empty space like in the original
      print(' ', end='')
    elif bit == '.':
      print('.', end='')
    else:
      # Print a char from the message
      if message[i % len(message)] == ' ':
        print('*', end='')
      else:
        print(message[i % len(message)], end="")
  print() # print a newline

In this we expand that print character statement into a nested if-then-else that prints a * whenever there’s a space in the message.

Want to read the next one?

I’ll link it in this last section when it drops. But you can also follow me on Twitter or LinkedIn. I also livecode these on Twitch/YouTube/LinkedIn (that’s right, baby… simulcasting) now and again.

And here’s the final code with the border preservation and blank space replacement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
"""Bitmap Message, by Al Sweigart al@inventwithpython.com
Displays a text message according to the provided bitmap image.
View this code at https://nostarch.com/big-book-small-python-projects
Tags: tiny, beginner, artistic"""

import sys

# (!) Try changing this multiline string to any image you like:

# There are 68 periods along the top and bottom of this string:
# (You can also copy and paste this string from
# https://inventwithpython.com/bitmapworld.txt)
bitmap = """
....................................................................
   **************   *  *** **  *      ******************************
  ********************* ** ** *  * ****************************** *
 **      *****************       ******************************
          *************          **  * **** ** ************** *
           *********            *******   **************** * *
            ********           ***************************  *
   *        * **** ***         *************** ******  ** *
               ****  *         ***************   *** ***  *
                 ******         *************    **   **  *
                 ********        *************    *  ** ***
                   ********         ********          * *** ****
                   *********         ******  *        **** ** * **
                   *********         ****** * *           *** *   *
                     ******          ***** **             *****   *
                     *****            **** *            ********
                    *****             ****              *********
                    ****              **                 *******   *
                    ***                                       *    *
                    **     *                    *
...................................................................."""

print('Bitmap Message, by Al Sweigart al@inventwithpython.com')
print('Enter the message to display with the bitmap.')
message = input('> ')
if message == '':
  sys.exit()

# Loop over each line in the bitmap:
for line in bitmap.splitlines():
  # Loop over each character in the line
  for i, bit in enumerate(line):
    if bit == ' ':
      # Print an empty space like in the original
      print(' ', end='')
    elif bit == '.':
      print('.', end='')
    else:
      # Print a char from the message
      if message[i % len(message)] == ' ':
        print('*', end='')
      else:
        print(message[i % len(message)], end="")
  print() # print a newline

And the output with “Python3 " as our message…

 1Bitmap Message, by Al Sweigart al@inventwithpython.com
 2Enter the message to display with the bitmap.
 3> Python3 
 4
 5....................................................................
 6   hon3*Python3*P   o  *Py ho  *      3*Python3*Python3*Python3*Pyth
 7  thon3*Python3*Python3 Py ho 3  y hon3*Python3*Python3*Python3*P t
 8 yt      ython3*Python3*Py       ython3*Python3*Python3*Python3
 9          thon3*Python3          yt  n *Pyt on *Python3*Pytho 3
10           hon3*Pyth            Python3   thon3*Python3*Py h n
11            on3*Pyth           *Python3*Python3*Python3*Py  o
12   h        o 3*Py hon         *Python3*Python *Pytho  *P t
13               *Pyt  n         *Python3*Python   yth n3*  t
14                 ython3         Python3*Pytho    yt   3*  t
15                 ython3*P        ython3*Python    t  n3 Pyt
16                   hon3*Pyt         on3*Pyth          3 Pyt on3*
17                   hon3*Pyth         n3*Pyt  n        3*Py ho 3 Py
18                   hon3*Pyth         n3*Pyt o 3           tho 3   t
19                     n3*Pyt          n3*Py ho             thon3   t
20                     n3*Py            3*Py h            Python3*
21                    on3*P             3*Py              Python3*P
22                    on3*              3*                 ython3*   h
23                    on3                                       3    h
24                    on     h                    P
25....................................................................