Revisiting the tiles (e.g.

here) I noticed that there is another way to get symmetry besides octagons, which increases symmetry and I think results in more intuitive tiles as well, since the Moore neighborhood is not really an octagon.

If we continue to treat cells as squares and count neighbors by quadrant, the quadrants with one neighbor can be treated identically (unlike with octagons). Specifically, take this cell neighborhood:

Arbitrarily grouping neighboring cells (there are two choices) we count the neighbors in quadrants clockwise around the center e as b+c, f+i, h+g, d+a. By doing this, a given neighborhood is represented as a cycle of values in {0, 1, 2}. E.g. (0, 1, 2, 0) is a neighborhood in which b+c=0, f+i=1, h+g=2, and d+a=0. The total count is 3 so birth or survival occurs in cell e.

If we represent these neighbors as tiles with boundaries, allowing flips and rotations, we can reduce the number of neighborhoods by a lot. In fact, treating 0+1 and 1+0 the same in each quadrant results in more reduction than maintaining 0-1, 1-0 order around an octagon.

For still lifes, we can also rule out tiles with 7 or 8 neighbors total, since they are internally overcrowded, as well as 6 neighbors grouped as (0, 2, 2, 2) for the same reason.

Here is the complete set of 27 tiles. Black tiles are live. White tiles are dead. Light blue tiles go in-between.

- tiles.png (43.13 KiB) Viewed 1568 times

This is kind of a lot but not crazy (note that the game Blokus uses 21 distinct pieces for each player for a total of 84). No doubt we could reduce the number with some splitting rules, but I think that harms the usability of physical tiles.

(Assume for now you can't link black and white tiles directly. I can fix that by tweaking contours. It may not matter anyway. You can chain together 2-neighbor black and white tiles but you can't tile the plane with them.)

Like the previous construction, the in-between tile represent 2x2 windows. These tiles have a zig-zag where they join so they cannot be flipped relative to each other (which would reverse the count of cells around the quadrants and change the meaning). For handling, it's preferable if the tiles are about the same size, though admittedly the in-between ones are bigger than the cells (in the very first from 2016, they were smaller, and this is adjustable).

Here's the eater done with these tiles:

- eater.png (54.15 KiB) Viewed 1568 times

And here's the block on table:

- b_on_t.png (67.32 KiB) Viewed 1568 times

I'm still working on the best contours. I just used polygon points that I found by trial and error, and then rotated them around a square. If I get ambitious, I can use bezier curves for more of a jigsaw puzzle effect.

Note: this opens up the possibility of making stackable tiles for computing Life generations. These would require some additional surface features (and get a little tricky if you wanted to make them flippable). I am skeptical that this would be a fun activity for human beings, but still life construction is essentially a jigsaw puzzle, so it has more potential.

A link to 27 transparent PNGs of tiles on Google drive:

https://drive.google.com/drive/folders/ ... sp=sharing
Finally, here's a python script that creates the PNGs in imagemagick.

Code: Select all

```
import math
WIDTH = 50
HEIGHT = 200 / math.sqrt(2) - WIDTH
def rotation(theta):
cosv = math.cos(math.radians(theta))
sinv = math.sin(math.radians(theta))
def rotate(pair):
x, y = pair
return (round(cosv * x - sinv * y, 12),
round(sinv * x + cosv * y, 12))
return rotate
def scaling(r):
def scale(pair):
x, y, = pair
return x * r, y * r
return scale
W2 = WIDTH-25
H2 = HEIGHT-25
def flip(pair):
x, y = pair
return x, -2 * H2 - y
def to_svg(origpath, r = 1.0):
path = list(map(scaling(r), origpath))
return " ".join(["M %5.3f, %5.3f" % path[0]] +
["L %5.3f, %5.3f" % pair for pair in path[1:]] +
["Z"])
PATH1 = [(-W2, -H2), (-8, -H2), (-12, -H2 - 20), (12, -H2 - 20), (8, -H2), (W2, -H2)]
PATH2 = [(-W2, -H2), (-15, -H2), (-25, -H2 - 25),
(-8, -H2 - 30), (-8, -H2 - 18), (8, -H2 - 18), (8, -H2 - 30),
(25, -H2 - 25), (15, -H2), (W2, -H2)]
PATH1F = list(map(flip, PATH1))
PATH2F = list(map(flip, PATH2))
EMPTY = [(-W2, -H2), (W2, -H2)]
PATHS = [[EMPTY, PATH1, PATH2],
[EMPTY, PATH1F, PATH2F]]
COLOR = ["white", "black"]
def tile_path(quadrants):
result = []
for i in range(4):
count = quadrants[(i + 1) % 4] + quadrants[(i + 2) % 4]
tooth = ([(-WIDTH, -HEIGHT)] + PATHS[quadrants[i]][count] +
[(WIDTH, -HEIGHT), (WIDTH + 16, -HEIGHT + 9), (HEIGHT - 16, -WIDTH - 9)])
result.extend(map(rotation(90 * i - 45), tooth))
return result
def cell_path(state, quadrants):
result = []
for i in range(4):
path = [(x, -HEIGHT - WIDTH - y) for x, y in PATHS[state][quadrants[i]]]
tooth = [(-WIDTH, -WIDTH)] + path + [(WIDTH, -WIDTH)]
result.extend(map(rotation(90 * i - 45), tooth))
return result
def pattern(x, y, grid, scale, size):
lines = []
lines.append("-stroke none")
for i in range(len(grid)):
for j in range(len(grid[i])):
color = COLOR[grid[i][j]]
lines.append("-fill %s -draw 'rectangle %s %s %s %s'" % (color,
x + j * size - size, y + i * size - size,
x + j * size, y + i * size))
lines.append("-fill lightblue -stroke gray")
for i in range(len(grid) - 1):
for j in range(len(grid[i]) - 1):
quadrants = (grid[i][j], grid[i][j + 1], grid[i + 1][j + 1], grid[i + 1][j])
lines.append("-draw 'translate %d, %d path \"%s\"'" %
(x + j * size, y + i * size, to_svg(tile_path(quadrants), scale)))
return lines
def all_cells():
result = {}
for a in range(3):
for b in range(3):
for c in range(3):
for d in range(3):
forward = (a, b, c, d)
reverse = forward[::-1]
canonical = min(
min(forward[-i:] + forward[:-i] for i in range(4)),
min(reverse[-i:] + reverse[:-i] for i in range(4)))
if forward == canonical and sum(forward) <= 6:
result.setdefault(sum(forward), []).append(forward)
return result
lines = ["magick -size 700x600 canvas:none -fill lightblue -stroke gray"]
x = 70
y = 70
for a in range(2):
for b in range(2):
for c in range(2):
for d in range(2):
quadrants = (a, b, c, d)
if quadrants == min([quadrants[-i:] + quadrants[:-i] for i in range(4)]):
lines.append("-draw 'translate %d, %d path \"%s\"'" % (x, y, to_svg(tile_path(quadrants), 0.5)))
x += 110
by_count = all_cells()
x = 70
y = 200
state = 1
for count in [2, 3]:
for quadrants in by_count[count]:
lines.append("-fill %s -draw 'translate %d, %d path \"%s\"'" % (COLOR[state], x, y, to_svg(cell_path(state, quadrants), 0.5)))
x += 110
x = 70
y += 110
state = 0
for count in [0, 1, 2]:
for quadrants in by_count[count]:
lines.append("-fill %s -draw 'translate %d, %d path \"%s\"'" % (COLOR[state], x, y, to_svg(cell_path(state, quadrants), 0.5)))
x += 110
x = 70
y += 110
state = 0
for count in [4]:
for quadrants in by_count[count]:
lines.append("-fill %s -draw 'translate %d, %d path \"%s\"'" % (COLOR[state], x, y, to_svg(cell_path(state, quadrants), 0.5)))
x += 110
x = 70
y += 110
state = 0
for count in [5, 6]:
for quadrants in by_count[count]:
if quadrants != (0, 2, 2, 2):
lines.append("-fill %s -draw 'translate %d, %d path \"%s\"'" % (COLOR[state], x, y, to_svg(cell_path(state, quadrants), 0.5)))
x += 110
lines.append("tiles.png")
print(" \\\n").join(lines)
lines = ["magick -size 600x600 canvas:none -fill lightblue -stroke gray"]
eater = [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]
lines.extend(pattern(100, 100, eater, 0.5, 100))
lines.append("eater.png")
print(" \\\n").join(lines)
lines = ["magick -size 600x700 canvas:none -fill lightblue -stroke gray"]
block_on_table = [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0]]
lines.extend(pattern(100, 100, block_on_table, 0.5, 100))
lines.append("b_on_t.png")
print(" \\\n").join(lines)
```