<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P037-pixel-paint/ | updated: 2026-10-10 -->

# P037 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.

EML-P project `projects/pixel-paint` in the EML language repo: 3 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/pixel-paint` runs it and `eml project verify projects/pixel-paint` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: flood-fill (https://efficientnewlanguage.org/cases/099-flood-fill/), grid-coordinate-map (https://efficientnewlanguage.org/cases/172-grid-coordinate-map/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
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
```

Screen:

```text
== 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.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
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
```

Screen:

```text
== 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.
```

## Modules

### main.eml

```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 of main.eml:

```python
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 of canvas.eml:

```python
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 of shapes.eml:

```python
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
```

## README

# P037 - Pixel paint

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 history
- `canvas.eml` - the canvas, painting cells with a record of what changed,
  undoing such a record, and the canvas on screen
- `shapes.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-fill` recurses 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).
