18
\$\begingroup\$

Life-like cellular automaton are cellular automaton that are similar to Conway's Game of Life, in that they operate on a (theoretically) infinitely large square grid, where each cell has exactly 8 neighbours, and is one of 2 states, namely alive and dead.

However, these Like-like versions are different in a crucial way: the rules for a given cell to come alive and the rules for a given cell to survive to the next generation.

For example, classic Game of Life uses the rule B3/S23, meaning that it takes 3 alive cells to birth a new one, and either 2 or 3 living neighbours to survive. For this challenge, we will assume that neighbours do not include itself, so each cell has exactly 8 neighbours.

Your task is, given a starting configuration, a birth rule, a survival rule and a positive integer (the number of generations to be run), simulate the Life-like automaton using those rules for the number of generations given in the shortest code possible. The starting configuration will be a square matrix/2-dimensional array or a multiline string, you may choose. The others may be given in any reasonable format and method.

For example, if the birth rule was 12345678 (any living neighbours), the survival rule was 2357 and the starting configuration was

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

the next two generations would be

Generation 1:           Generation 2:

0 0 0 0 0               1 1 1 1 1
0 1 1 1 0               1 1 0 1 1
0 1 0 1 0               1 0 1 0 1
0 1 1 1 0               1 1 0 1 1
0 0 0 0 0               1 1 1 1 1

If the number of generations given was 10, the output would be something along the lines of

0 1 1 1 0
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
0 1 1 1 0

You do not have to handle changes that happen outside of the bounds given by the input matrix, however, all cells outside the matrix begin dead. Therefore, the input matrix may be any size, up to the maximum value your language can support. You do not have to output the board between generations.

This is a so the shortest code wins.

Test cases

These use the B/S notation to indicate the rules used

B2/S2, generations = 100, configuration:

1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0

Output:

0 0 0 0 0 0 0 0
0 1 0 0 0 0 1 0
1 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

B1357/S2468, generations = 12, configuration:

1 0 1 0 1 0
0 1 1 0 1 0
1 0 0 0 0 0
0 0 0 0 0 1
1 1 1 1 1 0
0 1 1 0 0 1

Output:

0 1 0 0 0 0
0 1 1 1 1 0
0 1 0 1 1 0
1 1 1 0 0 0
0 0 1 1 1 0
0 1 1 0 0 0

If you need to generate more test cases, you can use this wonderful simulator. Please make sure to limit the board size

\$\endgroup\$
7
  • \$\begingroup\$ Is the simulation toroidal? \$\endgroup\$ Oct 20, 2017 at 15:35
  • 1
    \$\begingroup\$ @EriktheOutgolfer no, as the matrix is (theoretically) infinite in size \$\endgroup\$ Oct 20, 2017 at 15:36
  • \$\begingroup\$ Also, can we assume the given matrix is square? \$\endgroup\$ Oct 20, 2017 at 15:46
  • 3
    \$\begingroup\$ @EriktheOutgolfer "infinitely large square grid" \$\endgroup\$ Oct 20, 2017 at 15:47
  • \$\begingroup\$ But it doesn't say you can assume that...will edit in. \$\endgroup\$ Oct 20, 2017 at 15:47

5 Answers 5

10
\$\begingroup\$

MATL, 24 23 bytes

xx:"tt3Y6Z+1Gm<8M2Gmb*+

Inputs are:

  • Array with birth rule
  • Array with survival rule
  • Number of generations
  • Matrix with initial cell configuration, using ; as row separator.

Try it online! Or see test cases: 1, 2.

For a few bytes more you can see the evolution in ASCII art.

Explanation

xx      % Take two inputs implicitly: birth and survival rules. Delete them
        % (but they get copied into clipboard G)
:"      % Take third input implicitly: number of generations. Loop that many times
  tt    %   Duplicate twice. This implicitly takes the initial cell configuration
        %   as input the first time. In subsequent iterations it uses the cell 
        %   configuration from the previous iteration
  3Y6   %   Push Moore neighbourhood: [1 1 1; 1 0 1; 1 1 1]
  Z+    %   2D convolution, maintaining size
  1G    %   Push first input from clipboard G: birth rule
  m     %   Ismember: gives true for cells that fulfill the birth rule
  <     %   Less than (element-wise): a cell is born if it fulfills the birth rule
        %   *and* was dead
  8M    %   Push result of convolution again, from clipboard M
  2G    %   Push second input from clipboard G: survival rule
  m     %   Ismember: gives true for cells that fulfill the survival rule
  b     %   Bubble up the starting cell configuration
  *     %   Multiply (element-wise): a cell survives if it fulfills the survival
        %   rule *and* was alive
  +     %   Add: a cell is alive if it has been born or has survived, and those
        %   are exclusive cases. This produces the new cell configuration
        % Implicit end loop. Implicit display
\$\endgroup\$
4
  • \$\begingroup\$ Can you save bytes by changing the order of the inputs? The xx at the start seems a bit wasteful to me... \$\endgroup\$ Oct 20, 2017 at 16:56
  • \$\begingroup\$ @EriktheOutgolfer I don't see how. I need to delete the first two to later reuse them several times (one per iteration), and the other inputs are already implicit now \$\endgroup\$
    – Luis Mendo
    Oct 20, 2017 at 16:57
  • \$\begingroup\$ Oh so "deleting" the inputs adds them to some sort of input list? \$\endgroup\$ Oct 20, 2017 at 16:58
  • \$\begingroup\$ @EriktheOutgolfer Yes. MATL input is interactive, meaning the program doesn't know in advance how many input there are. Here, deleting from an empty stack causes an input to be implicitly taken. Once taken, each input gets copied into clipboard G, and they can be retrieved later. \$\endgroup\$
    – Luis Mendo
    Oct 20, 2017 at 16:59
3
\$\begingroup\$

Wolfram Language (Mathematica), 144 122 bytes

CellularAutomaton[{Tr[2^#&/@Flatten@MapIndexed[2#+2-#2[[1]]&,{#2,#3},{2}]],{2,{{2,2,2},{2,1,2},{2,2,2}}},{1,1}},#,{{#4}}]&

Try it online!

Example usage:

%[RandomInteger[1, {10, 10}], {2, 3}, {3}, 5]

uses a 10x10 random grid as a start, survives with either 2 or 3 neighbors, births with 3 neighbors, plot result at 5 iterations.

\$\endgroup\$
2
  • \$\begingroup\$ Too bad the builtin is only one dimensional (correct me if I'm wrong) \$\endgroup\$
    – Adalynn
    Oct 20, 2017 at 22:32
  • \$\begingroup\$ I’m using the built-in “CellularAutomaton” with a 9-neighbor totalistic rule. Much of the code converts the survival/birth inputs into a rule number. \$\endgroup\$ Oct 21, 2017 at 23:35
2
\$\begingroup\$

R, 256 bytes

function(x,B,S,r){y=cbind(0,rbind(0,x,0),0)
n=dim(y)[1]
z=c(1,n)
f=function(h){w=-1:1
b=h%%n+1
a=(h-b+1)/n+1
'if'(a%in%z|b%in%z,0,sum(x[w+b,w+a])-x[b,a])}
while(r){x=y
for(i in 1:n^2){u=f(i-1)
y[i]=u%in%B
y[i]=(y[i]&!x[i])|(x[i]&(u%in%S))}
r=r-1}
y[-z,-z]}

Try it online!

Sadly, this does not look as golfed as I'd hoped.

Input: an R matrix, and the challenge parameters. Output: the matrix after R generations.

The algorithm pads the matrix with zeros to handle the boundaries. Then, iteratively: 1st) it applies the Birth rule and 2nd) it kills the pre-existing cells that did not pass the Survival rule. Padding is removed when returning.

\$\endgroup\$
2
  • \$\begingroup\$ nice byte count! \$\endgroup\$
    – Giuseppe
    Apr 12, 2018 at 18:47
  • \$\begingroup\$ I managed to get it to 217 bytes but if we can find exactly one more golf, we can get it to 216 which is at least a cube... \$\endgroup\$
    – Giuseppe
    Apr 12, 2018 at 19:00
2
\$\begingroup\$

Python 2, 156 149 146 bytes

lambda R,g,c:g and f(R,g-1,[[`sum(sum(l[y+y/~y:y+2])for l in c[x+x/~x:x+2])-c[x][y]`in R[c[x][y]]for y,_ in e(c)]for x,_ in e(c)])or c
e=enumerate

Try it online!

Takes input:

  • Rules: [birth,survial] rules as list of string. eg.(['135','246'])
  • generations: int
  • configuration: Square 2D array of 1/0 or True/False

Returns 2d array of True/False

\$\endgroup\$
1
\$\begingroup\$

APL (Dyalog Unicode), 38 36 34 bytes (SBCS)

  • Saved 2 4 bytes thanks to @ovs!
{(((1⊥,)∊⍺(⍵+1)⊃⍨4⊃,)⍤⊢⌺3 3⍣⍵⍵)⍺⍺}

Try it online!

Can be invoked with birth_rule (start_grid f epochs) survival_rule, where f is the operator. Requires 0-indexing.

For some reason, using the trains (+/,) and (4⊃,) to make it 36 bytes doesn't work. As ovs explained, it's because calls functions dyadically. Anyway, their new solution is 34 bytes.


can repeatedly apply a function some number of times, given a starting value. In this case, the starting value is the left operand, and the right operand tells it how many times to repeat.

((1⊥,)∊⍺(⍵+1)⊃⍨4⊃,)⍤⊢⌺3 3 computes the next generation. The stencil operator () creates windows of a given shape (a 3x3 square here), applies a function on each of them, and puts them back together into a matrix. For cells on the edge of the grid, it will use zeroes, which works well with this challenge.

The train ((1⊥,)∊⍺(⍵+1)⊃⍨4⊃,)⍤⊢ is applied to each of those windows. ⍤⊢ applies the stuff before it to the second argument. 1⊥, calculates the number of neighbors (including the cell) by turning the 2D window into a vector (,) and then summing it (1⊥). checks if that the number of neighbors is in either the birth rule or survival rule vector. Whether the birth rule or survival rule is chosen depends on the train ⍺(⍵+1)⊃⍨4⊃,.

4⊃, turns the window into a vector and chooses the 5th cell, which is the cell we are calculating the next generation of. ⍺(⍵+1) is an array where the first element is the birth rule and the second element is the survival rule, but with its elements increased by one (because we're including the current cell). Then ⊃⍨ uses the current cell/5th cell/middle cell to index into this array, so if the cell's dead (0) the birth rule will be used, and if the cell is alive (1) the survival rule will be used.

\$\endgroup\$
5
  • \$\begingroup\$ The trains don't work because gives a left argument to the function, which means all function in there are called dyadically. (+/∘,⊢) and (4⊃∘,⊢) would work, but are too long. You can save two bytes by removing the brackets around ⍺(⍵+1)⊃⍨{4⊃,⍵}. \$\endgroup\$
    – ovs
    Nov 30, 2020 at 15:00
  • \$\begingroup\$ @ovs Ah, thanks, that makes sense now. Also thanks for the brackets, I should have realized they were unnecessary. \$\endgroup\$
    – user
    Nov 30, 2020 at 15:01
  • \$\begingroup\$ f←{(((+/,)∊⍺(⍵+1)⊃⍨(4⊃,))⍤⊢⌺3 3⍣⍵⍵)⍺⍺} works in version 18, but is the same length as your current function. \$\endgroup\$
    – ovs
    Nov 30, 2020 at 15:04
  • 1
    \$\begingroup\$ My suggestion had redundant brackets again, 34 bytes ;) \$\endgroup\$
    – ovs
    Nov 30, 2020 at 15:18
  • \$\begingroup\$ @ovs Thanks! That's 4 bytes saved now! \$\endgroup\$
    – user
    Nov 30, 2020 at 15:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.