Etching Drawer

Tags: large, artistic

When you move a pen point around the screen with the WASD keys, the etching drawer forms a picture by tracing a continuous line, like the Etch A Sketch toy. Let your artistic side break out and see what images you can create! This program also lets you save your drawings to a text file so you can print them out later. Plus, you can copy and paste the WASD movements of other drawings into this program, like the WASD commands for the Hilbert Curve fractal presented on lines 6 to 14 of the source code.

etching_drawer.py
  1"""Etching Drawer, by Al Sweigart al@inventwithpython.com
  2An art program that draws a continuous line around the screen using the
  3WASD keys. Inspired by Etch A Sketch toys.
  4
  5For example, you can draw Hilbert Curve fractal with:
  6SDWDDSASDSAAWASSDSASSDWDSDWWAWDDDSASSDWDSDWWAWDWWASAAWDWAWDDSDW
  7
  8Or an even larger Hilbert Curve fractal with:
  9DDSAASSDDWDDSDDWWAAWDDDDSDDWDDDDSAASDDSAAAAWAASSSDDWDDDDSAASDDSAAAAWA
 10ASAAAAWDDWWAASAAWAASSDDSAASSDDWDDDDSAASDDSAAAAWAASSDDSAASSDDWDDSDDWWA
 11AWDDDDDDSAASSDDWDDSDDWWAAWDDWWAASAAAAWDDWAAWDDDDSDDWDDSDDWDDDDSAASDDS
 12AAAAWAASSDDSAASSDDWDDSDDWWAAWDDDDDDSAASSDDWDDSDDWWAAWDDWWAASAAAAWDDWA
 13AWDDDDSDDWWAAWDDWWAASAAWAASSDDSAAAAWAASAAAAWDDWAAWDDDDSDDWWWAASAAAAWD
 14DWAAWDDDDSDDWDDDDSAASSDDWDDSDDWWAAWDD
 15
 16This code is available at https://nostarch.com/big-book-small-python-programming
 17Tags: large, artistic"""
 18
 19import shutil, sys
 20
 21# Set up the constants for line characters:
 22UP_DOWN_CHAR         = chr(9474)  # Character 9474 is '│'
 23LEFT_RIGHT_CHAR      = chr(9472)  # Character 9472 is '─'
 24DOWN_RIGHT_CHAR      = chr(9484)  # Character 9484 is '┌'
 25DOWN_LEFT_CHAR       = chr(9488)  # Character 9488 is '┐'
 26UP_RIGHT_CHAR        = chr(9492)  # Character 9492 is '└'
 27UP_LEFT_CHAR         = chr(9496)  # Character 9496 is '┘'
 28UP_DOWN_RIGHT_CHAR   = chr(9500)  # Character 9500 is '├'
 29UP_DOWN_LEFT_CHAR    = chr(9508)  # Character 9508 is '┤'
 30DOWN_LEFT_RIGHT_CHAR = chr(9516)  # Character 9516 is '┬'
 31UP_LEFT_RIGHT_CHAR   = chr(9524)  # Character 9524 is '┴'
 32CROSS_CHAR           = chr(9532)  # Character 9532 is '┼'
 33# A list of chr() codes is at https://inventwithpython.com/chr
 34
 35# Get the size of the terminal window:
 36CANVAS_WIDTH, CANVAS_HEIGHT = shutil.get_terminal_size()
 37# We can't print to the last column on Windows without it adding a
 38# newline automatically, so reduce the width by one:
 39CANVAS_WIDTH -= 1
 40# Leave room at the bottom few rows for the command info lines.
 41CANVAS_HEIGHT -= 5
 42
 43"""The keys for canvas will be (x, y) integer tuples for the coordinate,
 44and the value is a set of letters W, A, S, D that tell what kind of line
 45should be drawn."""
 46canvas = {}
 47cursorX = 0
 48cursorY = 0
 49
 50
 51def getCanvasString(canvasData, cx, cy):
 52    """Returns a multiline string of the line drawn in canvasData."""
 53    canvasStr = ''
 54
 55    """canvasData is a dictionary with (x, y) tuple keys and values that
 56    are sets of 'W', 'A', 'S', and/or 'D' strings to show which
 57    directions the lines are drawn at each xy point."""
 58    for rowNum in range(CANVAS_HEIGHT):
 59        for columnNum in range(CANVAS_WIDTH):
 60            if columnNum == cx and rowNum == cy:
 61                canvasStr += '#'
 62                continue
 63
 64            # Add the line character for this point to canvasStr.
 65            cell = canvasData.get((columnNum, rowNum))
 66            if cell in (set(['W', 'S']), set(['W']), set(['S'])):
 67                canvasStr += UP_DOWN_CHAR
 68            elif cell in (set(['A', 'D']), set(['A']), set(['D'])):
 69                canvasStr += LEFT_RIGHT_CHAR
 70            elif cell == set(['S', 'D']):
 71                canvasStr += DOWN_RIGHT_CHAR
 72            elif cell == set(['A', 'S']):
 73                canvasStr += DOWN_LEFT_CHAR
 74            elif cell == set(['W', 'D']):
 75                canvasStr += UP_RIGHT_CHAR
 76            elif cell == set(['W', 'A']):
 77                canvasStr += UP_LEFT_CHAR
 78            elif cell == set(['W', 'S', 'D']):
 79                canvasStr += UP_DOWN_RIGHT_CHAR
 80            elif cell == set(['W', 'S', 'A']):
 81                canvasStr += UP_DOWN_LEFT_CHAR
 82            elif cell == set(['A', 'S', 'D']):
 83                canvasStr += DOWN_LEFT_RIGHT_CHAR
 84            elif cell == set(['W', 'A', 'D']):
 85                canvasStr += UP_LEFT_RIGHT_CHAR
 86            elif cell == set(['W', 'A', 'S', 'D']):
 87                canvasStr += CROSS_CHAR
 88            elif cell == None:
 89                canvasStr += ' '
 90        canvasStr += '\n'  # Add a newline at the end of each row.
 91    return canvasStr
 92
 93
 94moves = []
 95while True:  # Main program loop.
 96    # Draw the lines based on the data in canvas:
 97    print(getCanvasString(canvas, cursorX, cursorY))
 98
 99    print('WASD keys to move, H for help, C to clear, '
100        + 'F to save, or QUIT.')
101    response = input('> ').upper()
102
103    if response == 'QUIT':
104        print('Thanks for playing!')
105        sys.exit()  # Quit the program.
106    elif response == 'H':
107        print('Enter W, A, S, and D characters to move the cursor and')
108        print('draw a line behind it as it moves. For example, ddd')
109        print('draws a line going right and sssdddwwwaaa draws a box.')
110        print()
111        print('You can save your drawing to a text file by entering F.')
112        input('Press Enter to return to the program...')
113        continue
114    elif response == 'C':
115        canvas = {}  # Erase the canvas data.
116        moves.append('C')  # Record this move.
117    elif response == 'F':
118        # Save the canvas string to a text file:
119        try:
120            print('Enter filename to save to:')
121            filename = input('> ')
122
123            # Make sure the filename ends with .txt:
124            if not filename.endswith('.txt'):
125                filename += '.txt'
126            with open(filename, 'w', encoding='utf-8') as file:
127                file.write(''.join(moves) + '\n')
128                file.write(getCanvasString(canvas, None, None))
129        except:
130            print('ERROR: Could not save file.')
131
132    for command in response:
133        if command not in ('W', 'A', 'S', 'D'):
134            continue  # Ignore this letter and continue to the next one.
135        moves.append(command)  # Record this move.
136
137        # The first line we add needs to form a full line:
138        if canvas == {}:
139            if command in ('W', 'S'):
140                # Make the first line a horizontal one:
141                canvas[(cursorX, cursorY)] = set(['W', 'S'])
142            elif command in ('A', 'D'):
143                # Make the first line a vertical one:
144                canvas[(cursorX, cursorY)] = set(['A', 'D'])
145
146        # Update x and y:
147        if command == 'W' and cursorY > 0:
148            canvas[(cursorX, cursorY)].add(command)
149            cursorY = cursorY - 1
150        elif command == 'S' and cursorY < CANVAS_HEIGHT - 1:
151            canvas[(cursorX, cursorY)].add(command)
152            cursorY = cursorY + 1
153        elif command == 'A' and cursorX > 0:
154            canvas[(cursorX, cursorY)].add(command)
155            cursorX = cursorX - 1
156        elif command == 'D' and cursorX < CANVAS_WIDTH - 1:
157            canvas[(cursorX, cursorY)].add(command)
158            cursorX = cursorX + 1
159        else:
160            # If the cursor doesn't move because it would have moved off
161            # the edge of the canvas, then don't change the set at
162            # canvas[(cursorX, cursorY)].
163            continue
164
165        # If there's no set for (cursorX, cursorY), add an empty set:
166        if (cursorX, cursorY) not in canvas:
167            canvas[(cursorX, cursorY)] = set()
168
169        # Add the direction string to this xy point's set:
170        if command == 'W':
171            canvas[(cursorX, cursorY)].add('S')
172        elif command == 'S':
173            canvas[(cursorX, cursorY)].add('W')
174        elif command == 'A':
175            canvas[(cursorX, cursorY)].add('D')
176        elif command == 'D':
177            canvas[(cursorX, cursorY)].add('A')

https://inventwithpython.com/bigbookpython/project23.html