<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P078-matrix-patterns/ | updated: 2026-10-14 -->

# P078 Matrix patterns

A grid filled in a clockwise spiral and read back along it, an odd magic square by the Siamese method with every row, column and diagonal sum checked, and the last grid turned and flipped - a magic square stays magic all eight ways.

EML-P project `projects/matrix-patterns` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/matrix-patterns` runs it and `eml project verify projects/matrix-patterns` 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: spiral-matrix-traversal (https://efficientnewlanguage.org/cases/104-spiral-matrix-traversal/).

## Sessions

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

Input:

```text
0
x
1
0
13
abc
3

2
4
17
1

3
rotate

1
3
3
3
turn
5
```

Screen:

```text
== Matrix patterns ==
Spirals, magic squares, and grids turned and flipped.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 0
Pick a number from 1 to 5.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> x
Pick a number from 1 to 5.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 1
rows (1 to 12)> 0
Type a whole number from 1 to 12.
rows (1 to 12)> 13
Type a whole number from 1 to 12.
rows (1 to 12)> abc
Type a whole number from 1 to 12.
rows (1 to 12)> 3
columns (1 to 12)> 
Cancelled.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 2
odd size (3 to 15)> 4
Type an odd whole number from 3 to 15.
odd size (3 to 15)> 17
Type an odd whole number from 3 to 15.
odd size (3 to 15)> 1
Type an odd whole number from 3 to 15.
odd size (3 to 15)> 
Cancelled.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 3
turn, mirror, upside down or transpose> rotate
Type turn, mirror, upside down or transpose.
turn, mirror, upside down or transpose> 
Cancelled.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 1
rows (1 to 12)> 3
columns (1 to 12)> 3
 1 2 3
 8 9 4
 7 6 5
Read back along the same spiral: 1 to 9 in order, each once.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 3
turn, mirror, upside down or transpose> turn
 7 8 1
 6 9 2
 5 4 3
Not a magic square - and turning or flipping cannot make one.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 5
Bye.
```

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

Input:

```text
4
1
4
5
1
1
7
1
5
1
2
5
3
turn
3
mirror
3
transpose
3
upside down
2
7
5
```

Screen:

```text
== Matrix patterns ==
Spirals, magic squares, and grids turned and flipped.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 4
 8 1 6
 3 5 7
 4 9 2

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 1
rows (1 to 12)> 4
columns (1 to 12)> 5
  1  2  3  4  5
 14 15 16 17  6
 13 20 19 18  7
 12 11 10  9  8
Read back along the same spiral: 1 to 20 in order, each once.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 1
rows (1 to 12)> 1
columns (1 to 12)> 7
 1 2 3 4 5 6 7
Read back along the same spiral: 1 to 7 in order, each once.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 1
rows (1 to 12)> 5
columns (1 to 12)> 1
 1
 2
 3
 4
 5
Read back along the same spiral: 1 to 5 in order, each once.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 2
odd size (3 to 15)> 5
 17 24  1  8 15
 23  5  7 14 16
  4  6 13 20 22
 10 12 19 21  3
 11 18 25  2  9
rows    65 65 65 65 65
columns 65 65 65 65 65
diagonals 65 and 65
Every row, column and both diagonals add up to 65 = n(n^2 + 1)/2, with 1 to 25 each once: magic.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 3
turn, mirror, upside down or transpose> turn
 11 10  4 23 17
 18 12  6  5 24
 25 19 13  7  1
  2 21 20 14  8
  9  3 22 16 15
Still magic: turning and flipping move whole rows, columns and diagonals.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 3
turn, mirror, upside down or transpose> mirror
 17 23  4 10 11
 24  5  6 12 18
  1  7 13 19 25
  8 14 20 21  2
 15 16 22  3  9
Still magic: turning and flipping move whole rows, columns and diagonals.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 3
turn, mirror, upside down or transpose> transpose
 17 24  1  8 15
 23  5  7 14 16
  4  6 13 20 22
 10 12 19 21  3
 11 18 25  2  9
Still magic: turning and flipping move whole rows, columns and diagonals.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 3
turn, mirror, upside down or transpose> upside down
 11 18 25  2  9
 10 12 19 21  3
  4  6 13 20 22
 23  5  7 14 16
 17 24  1  8 15
Still magic: turning and flipping move whole rows, columns and diagonals.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 2
odd size (3 to 15)> 7
 30 39 48  1 10 19 28
 38 47  7  9 18 27 29
 46  6  8 17 26 35 37
  5 14 16 25 34 36 45
 13 15 24 33 42 44  4
 21 23 32 41 43  3 12
 22 31 40 49  2 11 20
rows    175 175 175 175 175 175 175
columns 175 175 175 175 175 175 175
diagonals 175 and 175
Every row, column and both diagonals add up to 175 = n(n^2 + 1)/2, with 1 to 49 each once: magic.

1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit
choice> 5
Bye.
```

## Modules

### main.eml

```eml
# P078 matrix patterns: a grid filled in a spiral and read back, an odd
# magic square by the Siamese method with all its sums, and the last grid
# turned and flipped - a magic square stays magic all eight ways.
import grids

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 number(s):
    if len(s) == 0 or len(s) > 2:
        return -1
    0 => n
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n * 10 + int(c) => n
    return n

def lpad(s, n):
    while len(s) < n:
        " " + s => s
    return s

def ask(prompt, low, high):
    while True:
        trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> ")) => a
        if a == "":
            return -1
        number(a) => n
        if n >= low and n <= high:
            return n
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0

def show(g):
    # Every column as wide as the widest number in the grid, plus a space.
    1 => w
    for row in g:
        for x in row:
            if len(str(x)) > w:
                len(str(x)) => w
    for row in g:
        "" => line
        for x in row:
            line + lpad(str(x), w + 1) => line
        line ^0

def joined(xs):
    "" => s
    for x in xs:
        if s != "":
            s + " " => s
        s + str(x) => s
    return s

def spiral(g):
    ask("rows", 1, 12) => r
    if r == -1:
        "Cancelled." ^0
        return g
    ask("columns", 1, 12) => c
    if c == -1:
        "Cancelled." ^0
        return g
    grids.spiral_fill(r, c) => g
    show(g)
    grids.spiral_order(g) => path
    True => inorder
    for k in [0:len(path) - 1]:
        if path[k] != k + 1:
            False => inorder
    if inorder:
        ("Read back along the same spiral: 1 to " + str(r * c) + " in order, each once.") ^0
    else:
        ("Read back along the same spiral: " + joined(path)) ^0
    return g

def square(g):
    while True:
        trim(input("odd size (3 to 15)> ")) => a
        if a == "":
            "Cancelled." ^0
            return g
        number(a) => n
        if n >= 3 and n <= 15 and n % 2 == 1:
            grids.magic(n) => g
            show(g)
            report(g)
            return g
        "Type an odd whole number from 3 to 15." ^0

def report(g):
    len(g) => n
    grids.sums(g) => s
    ("rows    " + joined(s[0])) ^0
    ("columns " + joined(s[1])) ^0
    ("diagonals " + str(s[2]) + " and " + str(s[3])) ^0
    int(n * (n * n + 1) / 2) => target
    if grids.is_magic(g):
        ("Every row, column and both diagonals add up to " + str(target) + " = n(n^2 + 1)/2, with 1 to " + str(n * n) + " each once: magic.") ^0
    else:
        ("Not magic: n(n^2 + 1)/2 = " + str(target) + " is not every sum, or a number is missing.") ^0

def move(g):
    while True:
        trim(input("turn, mirror, upside down or transpose> ")) => how
        if how == "":
            "Cancelled." ^0
            return g
        if how == "turn" or how == "mirror" or how == "upside down" or how == "transpose":
            if how == "turn":
                grids.turned(g) => g
            elif how == "mirror":
                grids.mirrored(g) => g
            elif how == "upside down":
                grids.upside_down(g) => g
            else:
                grids.transposed(g) => g
            show(g)
            if len(g) == len(g[0]) and len(g) >= 3 and len(g) % 2 == 1:
                if grids.is_magic(g):
                    "Still magic: turning and flipping move whole rows, columns and diagonals." ^0
                else:
                    "Not a magic square - and turning or flipping cannot make one." ^0
            return g
        "Type turn, mirror, upside down or transpose." ^0

grids.magic(3) => grid

"== Matrix patterns ==" ^0
"Spirals, magic squares, and grids turned and flipped." ^0
True => running
while running:
    "" ^0
    "1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        spiral(grid) => grid
    elif choice == "2":
        square(grid) => grid
    elif choice == "3":
        move(grid) => grid
    elif choice == "4":
        show(grid)
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import grids

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 number(s):
    if len(s) == 0 or len(s) > 2:
        return -1
    n = 0
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n = n * 10 + int(c)
    return n

def lpad(s, n):
    while len(s) < n:
        s = " " + s
    return s

def ask(prompt, low, high):
    while True:
        a = trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> "))
        if a == "":
            return -1
        n = number(a)
        if n >= low and n <= high:
            return n
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")

def show(g):
    w = 1
    for row in g:
        for x in row:
            if len(str(x)) > w:
                w = len(str(x))
    for row in g:
        line = ""
        for x in row:
            line = line + lpad(str(x), w + 1)
        print(line)

def joined(xs):
    s = ""
    for x in xs:
        if s != "":
            s = s + " "
        s = s + str(x)
    return s

def spiral(g):
    r = ask("rows", 1, 12)
    if r == -1:
        print("Cancelled.")
        return g
    c = ask("columns", 1, 12)
    if c == -1:
        print("Cancelled.")
        return g
    g = grids.spiral_fill(r, c)
    show(g)
    path = grids.spiral_order(g)
    inorder = True
    for k in range(0, len(path)):
        if path[k] != k + 1:
            inorder = False
    if inorder:
        print("Read back along the same spiral: 1 to " + str(r * c) + " in order, each once.")
    else:
        print("Read back along the same spiral: " + joined(path))
    return g

def square(g):
    while True:
        a = trim(input("odd size (3 to 15)> "))
        if a == "":
            print("Cancelled.")
            return g
        n = number(a)
        if n >= 3 and n <= 15 and n % 2 == 1:
            g = grids.magic(n)
            show(g)
            report(g)
            return g
        print("Type an odd whole number from 3 to 15.")

def report(g):
    n = len(g)
    s = grids.sums(g)
    print("rows    " + joined(s[0]))
    print("columns " + joined(s[1]))
    print("diagonals " + str(s[2]) + " and " + str(s[3]))
    target = int(n * (n * n + 1) / 2)
    if grids.is_magic(g):
        print("Every row, column and both diagonals add up to " + str(target) + " = n(n^2 + 1)/2, with 1 to " + str(n * n) + " each once: magic.")
    else:
        print("Not magic: n(n^2 + 1)/2 = " + str(target) + " is not every sum, or a number is missing.")

def move(g):
    while True:
        how = trim(input("turn, mirror, upside down or transpose> "))
        if how == "":
            print("Cancelled.")
            return g
        if how == "turn" or how == "mirror" or how == "upside down" or how == "transpose":
            if how == "turn":
                g = grids.turned(g)
            elif how == "mirror":
                g = grids.mirrored(g)
            elif how == "upside down":
                g = grids.upside_down(g)
            else:
                g = grids.transposed(g)
            show(g)
            if len(g) == len(g[0]) and len(g) >= 3 and len(g) % 2 == 1:
                if grids.is_magic(g):
                    print("Still magic: turning and flipping move whole rows, columns and diagonals.")
                else:
                    print("Not a magic square - and turning or flipping cannot make one.")
            return g
        print("Type turn, mirror, upside down or transpose.")

grid = grids.magic(3)
print("== Matrix patterns ==")
print("Spirals, magic squares, and grids turned and flipped.")
running = True
while running:
    print("")
    print("1) spiral  2) magic square  3) turn or flip the last grid  4) show it  5) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        grid = spiral(grid)
    elif choice == "2":
        grid = square(grid)
    elif choice == "3":
        grid = move(grid)
    elif choice == "4":
        show(grid)
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")
```

### grids.eml

```eml
# P078 matrix patterns - a grid filled in a spiral and read back the same
# way, an odd magic square by the Siamese method with every sum checked,
# and the grid turned and flipped. A grid is a list of rows.

def blank(rows, cols):
    # A rows x cols grid of zeros, each row its own list ([[0] * cols] *
    # rows would make every row the same list).
    [0] * rows => g
    for r in [0:rows - 1]:
        [0] * cols => g[r]
    return g

def spiral_fill(rows, cols):
    # 1, 2, 3, ... clockwise from the top left, turning inwards: along the
    # top edge, down the right, back along the bottom, up the left, and the
    # four edges move in by one.
    blank(rows, cols) => g
    1 => n
    0 => top
    rows - 1 => bottom
    0 => left
    cols - 1 => right
    while top <= bottom and left <= right:
        for c in [left:right]:
            n => g[top][c]
            n + 1 => n
        top + 1 => top
        for r in [top:bottom]:
            n => g[r][right]
            n + 1 => n
        right - 1 => right
        if top <= bottom:
            for k in [0:right - left]:
                n => g[bottom][right - k]
                n + 1 => n
            bottom - 1 => bottom
        if left <= right:
            for k in [0:bottom - top]:
                n => g[bottom - k][left]
                n + 1 => n
            left + 1 => left
    return g

def spiral_order(g):
    # The corpus case spiral-matrix-traversal: read the same path. The two
    # guards stop a last single row or column being read twice.
    len(g) => rows
    len(g[0]) => cols
    [] => out
    0 => top
    rows - 1 => bottom
    0 => left
    cols - 1 => right
    while top <= bottom and left <= right:
        for c in [left:right]:
            out + [g[top][c]] => out
        top + 1 => top
        for r in [top:bottom]:
            out + [g[r][right]] => out
        right - 1 => right
        if top <= bottom:
            for k in [0:right - left]:
                out + [g[bottom][right - k]] => out
            bottom - 1 => bottom
        if left <= right:
            for k in [0:bottom - top]:
                out + [g[bottom - k][left]] => out
            left + 1 => left
    return out

def magic(n):
    # The Siamese method for an odd n: 1 in the middle of the top row, then
    # each next number one up and one right, wrapping round the edges; if
    # that square is taken, one down instead.
    blank(n, n) => g
    0 => r
    int((n - 1) / 2) => c
    for k in [1:n * n]:
        k => g[r][c]
        (r - 1 + n) % n => up
        (c + 1) % n => over
        if g[up][over] != 0:
            (r + 1) % n => r
        else:
            up => r
            over => c
    return g

def sums(g):
    # [row sums, column sums, main diagonal, other diagonal].
    len(g) => n
    [0] * n => rs
    [0] * n => cs
    0 => d1
    0 => d2
    for r in [0:n - 1]:
        for c in [0:n - 1]:
            rs[r] + g[r][c] => rs[r]
            cs[c] + g[r][c] => cs[c]
        d1 + g[r][r] => d1
        d2 + g[r][n - 1 - r] => d2
    return [rs, cs, d1, d2]

def is_magic(g):
    # Every row, column and both diagonals add up to n(n^2 + 1)/2, and the
    # numbers are 1 to n^2 once each.
    len(g) => n
    int(n * (n * n + 1) / 2) => target
    sums(g) => s
    for x in s[0] + s[1] + [s[2], s[3]]:
        if x != target:
            return False
    [0] * (n * n + 1) => seen
    for row in g:
        for x in row:
            if x < 1 or x > n * n or seen[x] == 1:
                return False
            1 => seen[x]
    return True

def turned(g):
    # A quarter turn clockwise: row r becomes column (rows - 1 - r).
    len(g) => rows
    len(g[0]) => cols
    blank(cols, rows) => out
    for r in [0:rows - 1]:
        for c in [0:cols - 1]:
            g[r][c] => out[c][rows - 1 - r]
    return out

def mirrored(g):
    # Flipped left to right.
    len(g) => rows
    len(g[0]) => cols
    blank(rows, cols) => out
    for r in [0:rows - 1]:
        for c in [0:cols - 1]:
            g[r][c] => out[r][cols - 1 - c]
    return out

def upside_down(g):
    # Flipped top to bottom.
    len(g) => rows
    blank(rows, len(g[0])) => out
    for r in [0:rows - 1]:
        for c in [0:len(g[0]) - 1]:
            g[r][c] => out[rows - 1 - r][c]
    return out

def transposed(g):
    # Rows become columns: flipped over the main diagonal.
    blank(len(g[0]), len(g)) => out
    for r in [0:len(g) - 1]:
        for c in [0:len(g[0]) - 1]:
            g[r][c] => out[c][r]
    return out
```

Python projection of grids.eml:

```python
def blank(rows, cols):
    g = [0] * rows
    for r in range(0, rows):
        g[r] = [0] * cols
    return g

def spiral_fill(rows, cols):
    g = blank(rows, cols)
    n = 1
    top = 0
    bottom = rows - 1
    left = 0
    right = cols - 1
    while top <= bottom and left <= right:
        for c in range(left, right+1):
            g[top][c] = n
            n = n + 1
        top = top + 1
        for r in range(top, bottom+1):
            g[r][right] = n
            n = n + 1
        right = right - 1
        if top <= bottom:
            for k in range(0, right - left+1):
                g[bottom][right - k] = n
                n = n + 1
            bottom = bottom - 1
        if left <= right:
            for k in range(0, bottom - top+1):
                g[bottom - k][left] = n
                n = n + 1
            left = left + 1
    return g

def spiral_order(g):
    rows = len(g)
    cols = len(g[0])
    out = []
    top = 0
    bottom = rows - 1
    left = 0
    right = cols - 1
    while top <= bottom and left <= right:
        for c in range(left, right+1):
            out = out + [g[top][c]]
        top = top + 1
        for r in range(top, bottom+1):
            out = out + [g[r][right]]
        right = right - 1
        if top <= bottom:
            for k in range(0, right - left+1):
                out = out + [g[bottom][right - k]]
            bottom = bottom - 1
        if left <= right:
            for k in range(0, bottom - top+1):
                out = out + [g[bottom - k][left]]
            left = left + 1
    return out

def magic(n):
    g = blank(n, n)
    r = 0
    c = int((n - 1) / 2)
    for k in range(1, n * n+1):
        g[r][c] = k
        up = (r - 1 + n) % n
        over = (c + 1) % n
        if g[up][over] != 0:
            r = (r + 1) % n
        else:
            r = up
            c = over
    return g

def sums(g):
    n = len(g)
    rs = [0] * n
    cs = [0] * n
    d1 = 0
    d2 = 0
    for r in range(0, n):
        for c in range(0, n):
            rs[r] = rs[r] + g[r][c]
            cs[c] = cs[c] + g[r][c]
        d1 = d1 + g[r][r]
        d2 = d2 + g[r][n - 1 - r]
    return [rs, cs, d1, d2]

def is_magic(g):
    n = len(g)
    target = int(n * (n * n + 1) / 2)
    s = sums(g)
    for x in s[0] + s[1] + [s[2], s[3]]:
        if x != target:
            return False
    seen = [0] * (n * n + 1)
    for row in g:
        for x in row:
            if x < 1 or x > n * n or seen[x] == 1:
                return False
            seen[x] = 1
    return True

def turned(g):
    rows = len(g)
    cols = len(g[0])
    out = blank(cols, rows)
    for r in range(0, rows):
        for c in range(0, cols):
            out[c][rows - 1 - r] = g[r][c]
    return out

def mirrored(g):
    rows = len(g)
    cols = len(g[0])
    out = blank(rows, cols)
    for r in range(0, rows):
        for c in range(0, cols):
            out[r][cols - 1 - c] = g[r][c]
    return out

def upside_down(g):
    rows = len(g)
    out = blank(rows, len(g[0]))
    for r in range(0, rows):
        for c in range(0, len(g[0])):
            out[rows - 1 - r][c] = g[r][c]
    return out

def transposed(g):
    out = blank(len(g[0]), len(g))
    for r in range(0, len(g)):
        for c in range(0, len(g[0])):
            out[c][r] = g[r][c]
    return out
```

## README

# P078 - Matrix patterns

A grid filled in a clockwise spiral and read back along it. An odd magic
square made by the Siamese method, with every row, column and diagonal
added up. And the last grid turned and flipped: a magic square stays
magic all eight ways. It starts with the 3 x 3 magic square.

- `main.eml` - the menu, reading sizes, showing grids and sums
- `grids.eml` - blank grids, the spiral both ways, the magic square, the
  sums, the check, and the turns and flips

How each part works:

- A grid is a list of rows, each made as its own list: repeating one row
  list would make every row the same list, so writing one square would
  change a whole column.
- Spiral fill: 1, 2, 3, ... along the top edge, down the right, back
  along the bottom, up the left, then the four edges move in by one.
- Spiral reading, as in the corpus case `spiral-matrix-traversal`, walks
  the same path. Two guards stop a last single row or column being read
  twice, which the 1 x 7 and 5 x 1 grids test.
- Magic square, Siamese method, for an odd n: 1 goes in the middle of the
  top row. Each next number goes one up and one right, wrapping round the
  edges; if that square is taken, it goes one down instead. Every row,
  column and both diagonals then add up to n(n^2 + 1)/2: 15 for 3, 65 for
  5, 175 for 7.
- Turns and flips: a quarter turn clockwise, a mirror left to right, upside
  down, and a transpose (rows become columns). They move whole rows,
  columns and diagonals, so a magic square stays magic. Its 8 views are
  all different.

What is checked: menu choices 1 to 5; 1 to 12 rows and columns for a
spiral; an odd size from 3 to 15 for a magic square; turn, mirror, upside
down or transpose. An empty answer cancels.

Sessions: `sessions/basic.in` gives:
- the 3 x 3 magic square, then spirals of 4 x 5, 1 x 7 and 5 x 1, each
  read back in order;
- the 5 x 5 magic square with its sums, then turned, mirrored, transposed
  (back to the start) and turned upside down, magic each time;
- the 7 x 7 magic square: every sum 175.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- rows 0, 13 and abc, then a cancelled column count;
- sizes 4, 17 and 1 for a magic square, then a cancel;
- the move "rotate", then a cancel;
- a 3 x 3 spiral turned: not a magic square.

Built on the verified corpus case `spiral-matrix-traversal` (four
shrinking edges, with the guards that stop a last row or column being
read twice).
