# Game of Life, but on a 4-8-8 tiling

## Background

The 4-8-8 tiling looks like this:

For the purpose of this challenge, we take the orientation of the tiling as exactly shown above. In plain English words, we take the tiling so that it can be seen as a rectangular grid of alternating octagons and squares, the upper left corner being an octagon.

In order to define a Game of Life on this tiling, we use a simplified definition of neighborhood: cell B is a neighbor of cell A if the two cells share an edge. An octagon has at most 8 neighbors and a square has at most 4. We assume the cells outside the given grid are simply non-existent (or alternatively, hardwired as all dead).

Other than the grid differences, we use the rules of Game of Life as-is:

• An off-cell is turned on if it has exactly three on-neighbors, and is kept off otherwise.
• An on-cell is kept on if it has exactly two or three on-neighbors, and is turned off otherwise.

## Challenge

Given a rectangular grid of on- and off-cells on the 4-8-8 tiling as defined above, calculate the next generation in Game of Life.

The on- and off-cells can be represented by any two distinct, consistent values respectively. The rectangular grid may be represented in any suitable format supported by your language. The output format doesn't need to be the same as the input format. The grid can be assumed to have at least two rows and two columns.

Standard rules apply. The shortest code in bytes wins.

## Test cases

Generated using this Python program.

For simplicity, the grid is represented as a list of lists of zeros (off) and ones (on). Some test cases have multiple steps, which means if your program is given the first state, it should produce the second, and if given the second, it should produce the third, etc.

[[1, 1],
[1, 1]] -> unchanged

[[1, 1],
[0, 1]] -> unchanged

[[1, 1],
[1, 0]] ->
[[1, 0],
[0, 1]] ->
[[0, 0],
[0, 0]]

[[0, 0, 0],
[1, 1, 1],
[0, 0, 0]], ->
[[0, 0, 0],
[0, 1, 0],
[0, 0, 0]]

[[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]] ->
[[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]] -> back to first

[[1, 0, 0, 1, 1],
[1, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 1, 0, 1, 1],
[1, 1, 0, 0, 1]] ->
[[1, 0, 1, 1, 1],
[1, 1, 1, 1, 0],
[1, 1, 0, 1, 1],
[0, 1, 1, 1, 1],
[1, 1, 1, 0, 1]] ->
[[1, 1, 0, 1, 1],
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 1]] ->
[[1, 0, 1, 0, 1],
[0, 0, 0, 0, 0],
[1, 0, 0, 0, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1]] ->
[[0, 0, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 0, 0]] -> all zeros

• Related Sep 15 at 23:41

# JavaScript, 99 bytes

a=>a.map((r,y)=>r.map((c,x)=>((g=i=>i--&&((i%2|x+~y&i!=4)&a[~-(y+i/3)]?.[x+i%3-1])+g(i))(9)|c)==3))


f=

a=>a.map((r,y)=>r.map((c,x)=>((g=i=>i--&&((i%2|x+~y&i!=4)&a[~-(y+i/3)]?.[x+i%3-1])+g(i))(9)|c)==3))

print = (a, contain) => {
let t = contain.appendChild(document.createElement('div'));
t.className = 'grid';
a.map(r => {
let tr = t.appendChild(document.createElement('div'));
tr.className = 'row';
r.map(c => {
tr.appendChild(document.createElement('div')).className = c ? 'on cell' : 'off cell';
});
});
}

test = (a, n, contain) => {
contain ??= document.body.appendChild(document.createElement('div'));
contain.className = 'testcase';
print(a, contain);
if (n) test(f(a), n - 1, contain);
};

test([[true, true],
[true, true]], 1);

test([[true, true],
[false, true]], 1);

test([[true, true],
[true, false]], 2);

test([[false, false, false],
[true, true, true],
[false, false, false]], 1);

test([[false, false, false, false, false],
[false, true, true, true, false],
[false, false, false, false, false]], 2);

test([[true, false, false, true, true],
[true, true, false, true, false],
[false, false, true, false, false],
[false, true, false, true, true],
[true, true, false, false, true]], 5);
body { background: #ccc; }
.grid { display: flex; flex-direction: column; padding: 10px; }
.grid + .grid { margin-top: 10px; }
.row { display: flex; }
.cell { width: 23px; height: 23px; position: relative; padding: 0; }
.on { --cell-color: white; }
.off { --cell-color: black; }
.cell::before { content: " "; display: block; position: absolute; }
.cell::before {
width: 16px; height: 16px;
top: 4px; left: 4px;
background-image:
background-position: 1px 1px, top left;
background-repeat: no-repeat;
background-size: 14px 14px, 16px 16px;
}
.row:nth-child(2n) .cell:nth-child(2n)::before,
.row:nth-child(2n+1) .cell:nth-child(2n+1)::before {
width: 34px; height: 34px;
top: -5px; left: -5px;
background-image:
linear-gradient(to bottom, gray 0, gray 1px, transparent 1px, transparent 33px, gray 33px, gray 34px),
linear-gradient(to right, gray 0, gray 1px, transparent 1px, transparent 33px, gray 33px, gray 34px),
linear-gradient( 135deg, transparent 0, transparent 7px, gray 7px, gray 8px, var(--cell-color) 8px, var(--cell-color) 20px),
linear-gradient(-135deg, transparent 0, transparent 7px, gray 7px, gray 8px, var(--cell-color) 8px, var(--cell-color) 20px),
linear-gradient(  45deg, transparent 0, transparent 7px, gray 7px, gray 8px, var(--cell-color) 8px, var(--cell-color) 20px),
linear-gradient( -45deg, transparent 0, transparent 7px, gray 7px, gray 8px, var(--cell-color) 8px, var(--cell-color) 20px);
background-position: top center, left center, top left, top right, bottom left, bottom right;
background-repeat: no-repeat;
background-size: 16px 34px, 34px 16px, 20px 20px, 20px 20px, 20px 20px, 20px 20px;
}
.testcase + .testcase { border-top: 1px solid gray; }

# JavaScript (ES6), 109 bytes

a=>a.map((r,y)=>r.map((n,x)=>[...'0124689',10].map(i=>n+=x-~y&1|i&5&&(a[y+~-(i/4)]||0)[x+i%4-1]<<1)|n>4&n<8))


Try it online!

# JavaScript (ES2020), 139 bytes

x=>x.map((y,i)=>y.map((v,j)=>(v+=(g=(a,b)=>x[i+a]?.[j+b]*2|0)(0,1)+g(1,0)+g(n=-1,0)+g(0,n)+(i+j+1)%2*(g(1,1)+g(1,n)+g(n,1)+g(n,n)))>4&v<8))


Try it online! (Link uses a 142 byte version, as TIO doesn't support the ES2020 ?. operator)

## Explanation

x=>x.map((y,i)=>y.map((v,j)=> // Map over each element of the grid:
(v+=                          // Add to the value of the cell (0 or 1):
(g=(a,b)=>x[i+a]?.[j+b]*2|0)  // Define a function g to get neighbor
// values; 2 for alive, 0 for dead.
(0,1)+g(1,0)              // Sum the neighbor values for the cells
+g(n=-1,0)+g(0,n)           // above, below, left, and right.
+                           // Add the product of:
(i+j+1)%2                  // 1 for octagons, 0 for squares.
*(g(1,1)+g(1,n)             // The sum of the values for the
+g(n,1)+g(n,n))            // diagonal neighbors.
)>4&v<8                       // 1 if the sum is >4 and <8, 0 otherwise.
))                              // This is true for:
// - 5 (1 (alive cell) + 4 (2 alive neighbors))
// - 6 (0 (dead cell) + 6 (3 alive neighbors))
// - 7 (1 (alive cell) + 6 (3 alive neighbors))


# MATL, 353332 31 bytes

t1Y6Z+G5B&*Z+G&n:w:!+o~*+E+5:7m


### How it works

t      % Implicit input: binary matrix. Duplicate
1Y6    % Push [0 1 0; 1 0 1; 0 1 0]. This mask defines cells that are neighbours
% of the central cell, whether the central cell is a square or an octagon
Z+     % 2D convolution, maintaining size. This counts neighbours according to
G      % Push input again
5B     % Push 5, convert to binary: gives [1 0 1]
&*     % All pair-wise products: gives [1 0 1; 0 0 0; 1 0 1]. This mask defines
% cells that are neighbours of octagons squares and not of squares
Z+     % 2D convolution, maintaining size. (**)
G      % Push input again
&n     % Push number of rows R, then number of columns C
:      % Range: gives row vector [1 2 ... C]
w      % SWap: moves R to top
:!     % Range, transpose: gives column vector [1; 2; ... ; R]
o~     % Modulo 2, negate. This gives a matrix the same size as the input with a
% checkered pattern, where 1 indicates octagon and 0 indicates square
*      % Multiply, element-wise. This keeps the results (**) for cells that are
% octagons, and sets the rest to 0
+      % Add (*) and (**). This gives the number of neighbours of each cell
E      % Multiply by 2
+      % Add to copy of the input that is at the bottom the stack. (***)
5:7    % Push range [5 6 7]
m      % Ismember. This gives 1 for cells at which (***) equals 5 (meaning the
% cell was active and had 2 active neighbours), 6 (it was inactive and had
% 3 active neighbours) or 7 (it was active and had 3 active neighbours)
% Implicit display


# Python 3, 136 bytes

Returns a 2d-list of booleans.

lambda c,E=enumerate:[[sum(sum([([0]+l)[x-y:x-y+3]for l in c[y+y%~y:y+2]],[])[x%2::1+x%2])-~x*v%2|v==3for x,v in E(r,y)]for y,r in E(c)]


Try it online!

# Jelly, 37 bytes

ŒJ§ị3ṗ2$’’A§ĠịƲḊ;\¤+"Ʋf€$œị⁸§ṁ,+¥=3o/


Try it online!

A monadic link taking and returning a 2D list of 0 and 1.

# Charcoal, 65 bytes

ＷＳ⊞υιυＵＭυ⪪ι¹ＦＬυ«Ｊ⁰ιＦＬ§υι«§≔§υικ¬÷⁻⁷⁺ＩＫＫ⊗№⎇﹪⁺ικ²ＫＶＫＭ1³→»»Ｊ⁰¦⁰Ｅυ⪫ιω


Try it online! Link is to verbose version of code. Takes input as a newline-terminated list of strings of 0s and 1s. Explanation:

ＷＳ⊞υιυ


Input the strings and print them to the canvas.

ＵＭυ⪪ι¹


Split the strings into character lists so that they can be replaced.

ＦＬυ«


Loop over each of the lists.

Ｊ⁰ι


ＦＬ§υι«


Loop over the cells of the list.

§≔§υικ¬÷⁻⁷⁺ＩＫＫ⊗№⎇﹪⁺ικ²ＫＶＫＭ1³


Look at the Von Neumann or Moore neighbourhood depending on the parity of the square, count the number of 1s, double that, add on the current square, subtract from 7, integer divide by 3, and complement the result. This gives the new live state of the cell.

→


Move to the next cell.

»»Ｊ⁰¦⁰Ｅυ⪫ιω


Overwrite the canvas with the new states.