Pixel paint
A 12 x 24 canvas of characters: paint single cells, Bresenham lines and boxes with the pen's color, fill an area with the paint bucket, switch pens (one of them erases), and undo as far back as you like. The canvas is a dictionary keyed by (row, column) tuples.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A canvas of 12 rows and 24 columns, drawn in characters. Paint single cells, lines and boxes with the pen's color, fill an area with the paint bucket, change the pen - one of the pens paints the background back, like an eraser - and undo as far back as you like. The canvas is shown after every change.
main.eml- the menu, the questions and their checks, the pen, and the undo historycanvas.eml- the canvas, painting cells with a record of what changed, undoing such a record, and the canvas on screenshapes.eml- which cells a line, a box or a fill covers
How each part works:
- The canvas is a dictionary keyed by (row, column) tuples, as in the corpus case
grid-coordinate-map: a cell never painted is not in it and shows the background. Painting returns the changes - each cell whose color really changed, with its old and new color - and undo puts the old colors back, last change first. A step that changes nothing is not recorded. - A line is Bresenham's: whole numbers only, an error term that tracks how far the drawn cells are from the true line, one step at a time along the longer direction and, when the error says so, along the shorter one too. Every cell it draws lies within half a cell of the true line, and both ends are drawn.
- A fill spreads from the cell picked over every cell of the same color that can be reached by moving up, down, left and right - so a diagonal line is a wall it does not cross. The corpus case
flood-fillrecurses into the four neighbours; here the cells still to visit wait on a list, and a cell is marked when it joins the list, so none is taken twice. - A box is the outline of the rectangle with the two corners given; with both corners in one row or column it is a line, with one corner twice a single cell.
What is checked: a cell as a row from 1 to 12 and a column from 1 to 24, typed as 3 5 or 3,5; a pen of exactly one of # o + * ~ .. An empty answer cancels.
Sessions: sessions/basic.in draws a box and a diagonal line across it, fills the part above the line with o and the part below with + (33 cells each - the line keeps them apart), draws a long line and a short diagonal that crosses it with *, undoes both, erases a corner of the box with the . pen and fills the whole background with ~ (163 cells). sessions/bad-input.in gives menu choices 0 and x, an undo with nothing to undo, cells 0 5, 13 1, 1 25, a b and 3, empty answers at each question, pens x and ##, a cell painted twice, a box with one corner twice and a box in one row, a fill on cells already that color, an erasing fill, and undoes everything, one step past the start.
Built on the verified corpus cases flood-fill (the paint bucket: spread to the four neighbours while they hold the color being replaced) and grid-coordinate-map (a sparse grid in a dictionary keyed by row and column tuples).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Pixel paint ==
A 12 x 24 canvas. Rows and columns count from 1; type a cell as 3 5.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 0
Pick a number from 1 to 8.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> x
Pick a number from 1 to 8.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Nothing to undo.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 1
cell> 0 5
Type a row from 1 to 12 and a column from 1 to 24, like 3 5.
cell> 13 1
Type a row from 1 to 12 and a column from 1 to 24, like 3 5.
cell> 1 25
Type a row from 1 to 12 and a column from 1 to 24, like 3 5.
cell> a b
Type a row from 1 to 12 and a column from 1 to 24, like 3 5.
cell> 3
Type a row from 1 to 12 and a column from 1 to 24, like 3 5.
cell>
Cancelled.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 2
from> 1 1
to>
Cancelled.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 3
from>
Cancelled.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 4
start>
Cancelled.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> x
Type one character: # o + * ~, or . to erase.
pen (# o + * ~, or . to erase)> ##
Type one character: # o + * ~, or . to erase.
pen (# o + * ~, or . to erase)>
Cancelled.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 1
cell> 1 1
dot: 1 cell, 1 changed.
Pen #, 1 cell painted
123456789012345678901234
1 #.......................
2 ........................
3 ........................
4 ........................
5 ........................
6 ........................
7 ........................
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 1
cell> 1,1
Nothing changed: 1 cell, all already #.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 3
from> 5 5
to> 5 5
box: 1 cell, 1 changed.
Pen #, 2 cells painted
123456789012345678901234
1 #.......................
2 ........................
3 ........................
4 ........................
5 ....#...................
6 ........................
7 ........................
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 3
from> 7 9
to> 7 3
box: 7 cells, 7 changed.
Pen #, 9 cells painted
123456789012345678901234
1 #.......................
2 ........................
3 ........................
4 ........................
5 ....#...................
6 ........................
7 ..#######...............
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 4
start> 7 5
Nothing changed: 7 cells, all already #.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> .
The pen is now . - it paints the background back, like an eraser.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 4
start> 1 1
fill: 1 cell, 1 changed.
Pen ., 8 cells painted
123456789012345678901234
1 ........................
2 ........................
3 ........................
4 ........................
5 ....#...................
6 ........................
7 ..#######...............
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Undid the fill: 1 cell put back.
Pen ., 9 cells painted
123456789012345678901234
1 #.......................
2 ........................
3 ........................
4 ........................
5 ....#...................
6 ........................
7 ..#######...............
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Undid the box: 7 cells put back.
Pen ., 2 cells painted
123456789012345678901234
1 #.......................
2 ........................
3 ........................
4 ........................
5 ....#...................
6 ........................
7 ........................
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Undid the box: 1 cell put back.
Pen ., 1 cell painted
123456789012345678901234
1 #.......................
2 ........................
3 ........................
4 ........................
5 ........................
6 ........................
7 ........................
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Undid the dot: 1 cell put back.
Pen ., 0 cells painted
123456789012345678901234
1 ........................
2 ........................
3 ........................
4 ........................
5 ........................
6 ........................
7 ........................
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Nothing to undo.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 7
Pen ., 0 cells painted
123456789012345678901234
1 ........................
2 ........................
3 ........................
4 ........................
5 ........................
6 ........................
7 ........................
8 ........................
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 8
Bye.
What was typed (44 lines)
0
x
6
1
0 5
13 1
1 25
a b
3
2
1 1
3
4
5
x
##
1
1 1
1
1,1
3
5 5
5 5
3
7 9
7 3
4
7 5
5
.
4
1 1
6
6
6
6
6
7
8
basic
interpreter: byte-equal== Pixel paint ==
A 12 x 24 canvas. Rows and columns count from 1; type a cell as 3 5.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 3
from> 2 3
to> 8 20
box: 46 cells, 46 changed.
Pen #, 46 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#................#....
4 ..#................#....
5 ..#................#....
6 ..#................#....
7 ..#................#....
8 ..##################....
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 2
from> 3 5
to> 7 18
line: 14 cells, 14 changed.
Pen #, 60 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#.##.............#....
4 ..#...###..........#....
5 ..#......####......#....
6 ..#..........###...#....
7 ..#.............##.#....
8 ..##################....
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> o
The pen is now o.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 4
start> 4 10
fill: 33 cells, 33 changed.
Pen o, 93 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#.##ooooooooooooo#....
4 ..#...###oooooooooo#....
5 ..#......####oooooo#....
6 ..#..........###ooo#....
7 ..#.............##o#....
8 ..##################....
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> +
The pen is now +.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 4
start> 3 4
fill: 33 cells, 33 changed.
Pen +, 126 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#+##ooooooooooooo#....
4 ..#+++###oooooooooo#....
5 ..#++++++####oooooo#....
6 ..#++++++++++###ooo#....
7 ..#+++++++++++++##o#....
8 ..##################....
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> *
The pen is now *.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 2
from> 10 2
to> 10 23
line: 22 cells, 22 changed.
Pen *, 148 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#+##ooooooooooooo#....
4 ..#+++###oooooooooo#....
5 ..#++++++####oooooo#....
6 ..#++++++++++###ooo#....
7 ..#+++++++++++++##o#....
8 ..##################....
9 ........................
10 .**********************.
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 2
from> 9 24
to> 12 21
line: 4 cells, 3 changed.
Pen *, 151 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#+##ooooooooooooo#....
4 ..#+++###oooooooooo#....
5 ..#++++++####oooooo#....
6 ..#++++++++++###ooo#....
7 ..#+++++++++++++##o#....
8 ..##################....
9 .......................*
10 .**********************.
11 .....................*..
12 ....................*...
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Undid the line: 3 cells put back.
Pen *, 148 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#+##ooooooooooooo#....
4 ..#+++###oooooooooo#....
5 ..#++++++####oooooo#....
6 ..#++++++++++###ooo#....
7 ..#+++++++++++++##o#....
8 ..##################....
9 ........................
10 .**********************.
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 6
Undid the line: 22 cells put back.
Pen *, 126 cells painted
123456789012345678901234
1 ........................
2 ..##################....
3 ..#+##ooooooooooooo#....
4 ..#+++###oooooooooo#....
5 ..#++++++####oooooo#....
6 ..#++++++++++###ooo#....
7 ..#+++++++++++++##o#....
8 ..##################....
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> .
The pen is now . - it paints the background back, like an eraser.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 1
cell> 2 3
dot: 1 cell, 1 changed.
Pen ., 125 cells painted
123456789012345678901234
1 ........................
2 ...#################....
3 ..#+##ooooooooooooo#....
4 ..#+++###oooooooooo#....
5 ..#++++++####oooooo#....
6 ..#++++++++++###ooo#....
7 ..#+++++++++++++##o#....
8 ..##################....
9 ........................
10 ........................
11 ........................
12 ........................
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 5
pen (# o + * ~, or . to erase)> ~
The pen is now ~.
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 4
start> 1 1
fill: 163 cells, 163 changed.
Pen ~, 288 cells painted
123456789012345678901234
1 ~~~~~~~~~~~~~~~~~~~~~~~~
2 ~~~#################~~~~
3 ~~#+##ooooooooooooo#~~~~
4 ~~#+++###oooooooooo#~~~~
5 ~~#++++++####oooooo#~~~~
6 ~~#++++++++++###ooo#~~~~
7 ~~#+++++++++++++##o#~~~~
8 ~~##################~~~~
9 ~~~~~~~~~~~~~~~~~~~~~~~~
10 ~~~~~~~~~~~~~~~~~~~~~~~~
11 ~~~~~~~~~~~~~~~~~~~~~~~~
12 ~~~~~~~~~~~~~~~~~~~~~~~~
1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit
choice> 8
Bye.
What was typed (33 lines)
3
2 3
8 20
2
3 5
7 18
5
o
4
4 10
5
+
4
3 4
5
*
2
10 2
10 23
2
9 24
12 21
6
6
5
.
1
2 3
5
~
4
1 1
8
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P037 pixel paint: a 12 x 24 canvas of characters. Paint single cells,
# lines and boxes with the pen's color, fill an area with the paint bucket,
# change the pen, and undo as far back as you like.
import canvas
import shapes
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def whole_number(s):
# The value of 1 to 3 digits, otherwise -1.
if s == "" or len(s) > 3:
return -1
0 => n
for c in s:
if not (c in "0123456789"):
return -1
n * 10 + int(c) => n
return n
def ask_cell(prompt):
# A cell typed as "row column" or "row,column", counting from 1, as a
# (row, column) tuple counting from 0; [] when the answer is empty.
while True:
trim(input(prompt)) => answer
if answer == "":
return []
[] => parts
"" => word
for c in answer + " ":
if c == " " or c == ",":
if word != "":
parts + [word] => parts
"" => word
else:
word + c => word
if len(parts) == 2:
whole_number(parts[0]) => r
whole_number(parts[1]) => c
if r >= 1 and r <= canvas.rows and c >= 1 and c <= canvas.cols:
return (r - 1, c - 1)
("Type a row from 1 to " + str(canvas.rows) + " and a column from 1 to " + str(canvas.cols) + ", like 3 5.") ^0
def cells_text(n):
if n == 1:
return "1 cell"
return str(n) + " cells"
def done(cv, pen, history, name, cells, changes):
# Records a drawing step for undo and shows the result. Returns history.
if len(changes) == 0:
("Nothing changed: " + cells_text(len(cells)) + ", all already " + pen + ".") ^0
return history
(name + ": " + cells_text(len(cells)) + ", " + str(len(changes)) + " changed.") ^0
canvas.show(cv, pen)
return history + [[name, changes]]
def draw(cv, pen, history, kind):
if kind == "dot":
ask_cell("cell> ") => a
if len(a) == 0:
"Cancelled." ^0
return history
[a] => cells
elif kind == "fill":
ask_cell("start> ") => a
if len(a) == 0:
"Cancelled." ^0
return history
shapes.area(cv, a) => cells
else:
ask_cell("from> ") => a
if len(a) == 0:
"Cancelled." ^0
return history
ask_cell("to> ") => b
if len(b) == 0:
"Cancelled." ^0
return history
if kind == "line":
shapes.line(a[0], a[1], b[0], b[1]) => cells
else:
shapes.box(a[0], a[1], b[0], b[1]) => cells
canvas.paint(cv, cells, pen) => changes
return done(cv, pen, history, kind, cells, changes)
def choose_pen(pen):
while True:
trim(input("pen (# o + * ~, or . to erase)> ")) => answer
if answer == "":
"Cancelled." ^0
return pen
if len(answer) == 1 and answer in canvas.colors:
if answer == canvas.background:
"The pen is now . - it paints the background back, like an eraser." ^0
else:
("The pen is now " + answer + ".") ^0
return answer
"Type one character: # o + * ~, or . to erase." ^0
"== Pixel paint ==" ^0
"A 12 x 24 canvas. Rows and columns count from 1; type a cell as 3 5." ^0
{} => cv
"#" => pen
[] => history
True => running
while running:
"" ^0
"1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
draw(cv, pen, history, "dot") => history
elif choice == "2":
draw(cv, pen, history, "line") => history
elif choice == "3":
draw(cv, pen, history, "box") => history
elif choice == "4":
draw(cv, pen, history, "fill") => history
elif choice == "5":
choose_pen(pen) => pen
elif choice == "6":
if len(history) == 0:
"Nothing to undo." ^0
else:
history[len(history) - 1] => last
canvas.undo(cv, last[1])
history[0:len(history) - 1] => history
("Undid the " + last[0] + ": " + cells_text(len(last[1])) + " put back.") ^0
canvas.show(cv, pen)
elif choice == "7":
canvas.show(cv, pen)
elif choice == "8":
False => running
else:
"Pick a number from 1 to 8." ^0
"Bye." ^0
Python projection (main.py)
import canvas
import shapes
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def whole_number(s):
if s == "" or len(s) > 3:
return -1
n = 0
for c in s:
if not c in "0123456789":
return -1
n = n * 10 + int(c)
return n
def ask_cell(prompt):
while True:
answer = trim(input(prompt))
if answer == "":
return []
parts = []
word = ""
for c in answer + " ":
if c == " " or c == ",":
if word != "":
parts = parts + [word]
word = ""
else:
word = word + c
if len(parts) == 2:
r = whole_number(parts[0])
c = whole_number(parts[1])
if r >= 1 and r <= canvas.rows and c >= 1 and c <= canvas.cols:
return (r - 1, c - 1)
print("Type a row from 1 to " + str(canvas.rows) + " and a column from 1 to " + str(canvas.cols) + ", like 3 5.")
def cells_text(n):
if n == 1:
return "1 cell"
return str(n) + " cells"
def done(cv, pen, history, name, cells, changes):
if len(changes) == 0:
print("Nothing changed: " + cells_text(len(cells)) + ", all already " + pen + ".")
return history
print(name + ": " + cells_text(len(cells)) + ", " + str(len(changes)) + " changed.")
canvas.show(cv, pen)
return history + [[name, changes]]
def draw(cv, pen, history, kind):
if kind == "dot":
a = ask_cell("cell> ")
if len(a) == 0:
print("Cancelled.")
return history
cells = [a]
elif kind == "fill":
a = ask_cell("start> ")
if len(a) == 0:
print("Cancelled.")
return history
cells = shapes.area(cv, a)
else:
a = ask_cell("from> ")
if len(a) == 0:
print("Cancelled.")
return history
b = ask_cell("to> ")
if len(b) == 0:
print("Cancelled.")
return history
if kind == "line":
cells = shapes.line(a[0], a[1], b[0], b[1])
else:
cells = shapes.box(a[0], a[1], b[0], b[1])
changes = canvas.paint(cv, cells, pen)
return done(cv, pen, history, kind, cells, changes)
def choose_pen(pen):
while True:
answer = trim(input("pen (# o + * ~, or . to erase)> "))
if answer == "":
print("Cancelled.")
return pen
if len(answer) == 1 and answer in canvas.colors:
if answer == canvas.background:
print("The pen is now . - it paints the background back, like an eraser.")
else:
print("The pen is now " + answer + ".")
return answer
print("Type one character: # o + * ~, or . to erase.")
print("== Pixel paint ==")
print("A 12 x 24 canvas. Rows and columns count from 1; type a cell as 3 5.")
cv = {}
pen = "#"
history = []
running = True
while running:
print("")
print("1) dot 2) line 3) box 4) fill 5) pen 6) undo 7) show 8) quit")
choice = trim(input("choice> "))
if choice == "1":
history = draw(cv, pen, history, "dot")
elif choice == "2":
history = draw(cv, pen, history, "line")
elif choice == "3":
history = draw(cv, pen, history, "box")
elif choice == "4":
history = draw(cv, pen, history, "fill")
elif choice == "5":
pen = choose_pen(pen)
elif choice == "6":
if len(history) == 0:
print("Nothing to undo.")
else:
last = history[len(history) - 1]
canvas.undo(cv, last[1])
history = history[0:len(history) - 1]
print("Undid the " + last[0] + ": " + cells_text(len(last[1])) + " put back.")
canvas.show(cv, pen)
elif choice == "7":
canvas.show(cv, pen)
elif choice == "8":
running = False
else:
print("Pick a number from 1 to 8.")
print("Bye.")
canvas.eml
eml# P037 pixel paint - the canvas. 12 rows and 24 columns, counted from 0
# inside the program. The canvas is a dictionary keyed by (row, column)
# tuples, as in the corpus case grid-coordinate-map: a cell that was never
# painted is simply not in it and shows the background ".".
12 => rows
24 => cols
"." => background
"#o+*~." => colors
def color_at(canvas, cell):
if cell in canvas:
return canvas[cell]
return background
def inside(r, c):
return r >= 0 and r < rows and c >= 0 and c < cols
def paint(canvas, cells, color):
# Paints the cells and returns the changes [cell, old, new] - only the
# cells whose color really changed - so that they can be undone.
[] => changes
for cell in cells:
color_at(canvas, cell) => old
if old != color:
changes + [[cell, old, color]] => changes
color => canvas[cell]
return changes
def undo(canvas, changes):
# Puts back the old colors, last change first.
len(changes) - 1 => i
while i >= 0:
changes[i][1] => canvas[changes[i][0]]
i - 1 => i
def painted(canvas):
# How many cells hold something other than the background.
0 => n
for cell in canvas:
if canvas[cell] != background:
n + 1 => n
return n
def show(canvas, pen):
painted(canvas) => n
"cells" => word
if n == 1:
"cell" => word
("Pen " + pen + ", " + str(n) + " " + word + " painted") ^0
" " => header
for c in [1:cols]:
header + str(c % 10) => header
header ^0
for r in [0:rows - 1]:
str(r + 1) => label
while len(label) < 3:
" " + label => label
label + " " => line
for c in [0:cols - 1]:
line + color_at(canvas, (r, c)) => line
line ^0
Python projection (canvas.py)
rows = 12
cols = 24
background = "."
colors = "#o+*~."
def color_at(canvas, cell):
if cell in canvas:
return canvas[cell]
return background
def inside(r, c):
return r >= 0 and r < rows and c >= 0 and c < cols
def paint(canvas, cells, color):
changes = []
for cell in cells:
old = color_at(canvas, cell)
if old != color:
changes = changes + [[cell, old, color]]
canvas[cell] = color
return changes
def undo(canvas, changes):
i = len(changes) - 1
while i >= 0:
canvas[changes[i][0]] = changes[i][1]
i = i - 1
def painted(canvas):
n = 0
for cell in canvas:
if canvas[cell] != background:
n = n + 1
return n
def show(canvas, pen):
n = painted(canvas)
word = "cells"
if n == 1:
word = "cell"
print("Pen " + pen + ", " + str(n) + " " + word + " painted")
header = " "
for c in range(1, cols+1):
header = header + str(c % 10)
print(header)
for r in range(0, rows):
label = str(r + 1)
while len(label) < 3:
label = " " + label
line = label + " "
for c in range(0, cols):
line = line + color_at(canvas, (r, c))
print(line)
shapes.eml
eml# P037 pixel paint - which cells a shape covers: a line, a box, and the area
# a fill spreads over. Cells are (row, column) tuples.
import canvas
def step_toward(a, b):
if a < b:
return 1
if a > b:
return -1
return 0
def line(r0, c0, r1, c1):
# Bresenham's line from (r0, c0) to (r1, c1), with whole numbers only:
# err tracks how far the drawn cells are from the true line, and each
# step moves along the longer direction and, when err says so, along the
# shorter one too. Both ends are included.
abs(c1 - c0) => dc
abs(r1 - r0) => dr
step_toward(c0, c1) => sc
step_toward(r0, r1) => sr
dc - dr => err
[] => cells
True => going
while going:
cells + [(r0, c0)] => cells
if r0 == r1 and c0 == c1:
False => going
else:
2 * err => e2
if e2 > 0 - dr:
err - dr => err
c0 + sc => c0
if e2 < dc:
err + dc => err
r0 + sr => r0
return cells
def box(r0, c0, r1, c1):
# The outline of the rectangle with these two corners.
min(r0, r1) => top
max(r0, r1) => bottom
min(c0, c1) => left
max(c0, c1) => right
[] => cells
for c in [left:right]:
cells + [(top, c)] => cells
if bottom != top:
cells + [(bottom, c)] => cells
for r in [top + 1:bottom - 1]:
cells + [(r, left)] => cells
if right != left:
cells + [(r, right)] => cells
return cells
def area(cv, start):
# The cells a fill from start reaches: every cell of start's color that
# can be reached through up, down, left and right moves over that color.
# The corpus case flood-fill recurses into the four neighbours; this
# keeps the cells still to visit on a list instead, and marks a cell when
# it is put on the list, so none is taken twice.
canvas.color_at(cv, start) => target
{} => seen
True => seen[start]
[start] => todo
[] => cells
while len(todo) > 0:
todo[len(todo) - 1] => cell
todo[0:len(todo) - 1] => todo
cells + [cell] => cells
cell[0] => r
cell[1] => c
for d in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
if canvas.inside(d[0], d[1]) and not (d in seen) and canvas.color_at(cv, d) == target:
True => seen[d]
todo + [d] => todo
return cells
Python projection (shapes.py)
import canvas
def step_toward(a, b):
if a < b:
return 1
if a > b:
return -1
return 0
def line(r0, c0, r1, c1):
dc = abs(c1 - c0)
dr = abs(r1 - r0)
sc = step_toward(c0, c1)
sr = step_toward(r0, r1)
err = dc - dr
cells = []
going = True
while going:
cells = cells + [(r0, c0)]
if r0 == r1 and c0 == c1:
going = False
else:
e2 = 2 * err
if e2 > 0 - dr:
err = err - dr
c0 = c0 + sc
if e2 < dc:
err = err + dc
r0 = r0 + sr
return cells
def box(r0, c0, r1, c1):
top = min(r0, r1)
bottom = max(r0, r1)
left = min(c0, c1)
right = max(c0, c1)
cells = []
for c in range(left, right+1):
cells = cells + [(top, c)]
if bottom != top:
cells = cells + [(bottom, c)]
for r in range(top + 1, bottom):
cells = cells + [(r, left)]
if right != left:
cells = cells + [(r, right)]
return cells
def area(cv, start):
target = canvas.color_at(cv, start)
seen = {}
seen[start] = True
todo = [start]
cells = []
while len(todo) > 0:
cell = todo[len(todo) - 1]
todo = todo[0:len(todo) - 1]
cells = cells + [cell]
r = cell[0]
c = cell[1]
for d in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
if canvas.inside(d[0], d[1]) and not d in seen and canvas.color_at(cv, d) == target:
seen[d] = True
todo = todo + [d]
return cells