#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# Patterns - Wolfram's elementary cellular automaton explorer

Posts: 12

In the New Year 2016 I feel I could make a tiny contribution to the community. This is on a topic of cellular automatons although this particular flavor hasn't been discussed in these forums so far.

I'm presenting a pretty small Wolfram's elementary cellular automaton explorer. Additional information:
http://mathworld.wolfram.com/ElementaryCellularAutomaton.html

You can "program" it by setting if the particular pattern of the three cells above should make the current cell "on" or "off". There are 256 possible patterns - or rules as they call them - and I made the numbering compatible with Wolfram. Double tap changes the starting conditions - single "on" cell / single "off" cell / random cells, there is an independent setting if the sides wrap. Dragging the image works as expected.

This project is not very advanced so I hope someone learning to program might find something valuable for them. And anyway the patterns are quite interesting in their own right.

I constrained myself to using just one tab and no classes, getting under 200 lines. There are four global variables which in any serious application should be upgraded to classes. The cell values are stored in the memory and later drawn to screen each frame, which is why the performance is not great. More effective, but also a bit more complicated, is generating the cells row by row and drawing them to a buffer using setContext() - which I implemented as another project. Even better would be to generate the cells using GPU which I saved for the future.

Enjoy!

``````-- Patterns

-- Use this function to perform your initial setup
function setup()
-- contains variables concerning drawing the cells
graph = {
scalef = 0.125,
offx = 0,
offy = 0
}
-- 2 dimensional table holding the actual cell contents
local N = 120
arr = {}
for y = 1,N do
arr[y]={}
for x = 1,N do
arr[y][x]=0
end
end
-- parameters
params={wrap=true, def=0,
pat={0,1,1,1,1,1,1,0},
rule=-1, -- will be recomputed
reinitmode=0
}
reinit(arr, params)
-- UI stuff
ui={
uiscale = 0.4,
uipitch = 1.1,
uipos = {},
drag = false,
dt = 1/60
}
end

-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)

for y = 1,#arr do
for x = 1,#arr[y] do
if arr[y][x]>0 then
tint(31, 97, 41, 255)
else
tint(159, 215, 12, 255)
end

sprite(graph.block,
graph.block.width * graph.scalef * x + graph.offx,
HEIGHT - graph.block.height * graph.scalef*y + graph.offy,
graph.block.width * graph.scalef,
graph.block.height * graph.scalef)
end
end
-- UI
local sc=ui.uiscale/3
local tints = {[0]=color(255, 255, 255, 214),   [1]=color(255, 0, 0, 204)}
for i=1,8 do
local k = i - 1 -- 0 based
local b = { ((k & 4) > 0) and 1 or 0,
((k & 2) > 0) and 1 or 0,
((k & 1) > 0) and 1 or 0 }
local iw = 9 - i -- wolfram compatible
tint(tints[params.pat[i]])
local ux, uy = ui.block.width*ui.uiscale*ui.uipitch*iw,
HEIGHT-ui.block.width*ui.uiscale*2
local uw, uh = ui.block.width*ui.uiscale, ui.block.height*ui.uiscale
sprite(ui.block, ux, uy, uw, uh)

ui.uipos[i] = {x=ux-uw/2,y=uy-uh/2,w=uw,h=uh}
for j=1,3 do
tint(tints[b[j]])
sprite(ui.block,
ux+(ui.block.width*sc*(j-2)),
HEIGHT-ui.block.width*ui.uiscale*1.5,
ui.block.width*sc,ui.block.height*sc)
end
end

tint(255, 255, 255, 255)
fill(255, 255, 255, 255)
local t={"101011", "0001000", "1110111"}
ui.dt = 0.9 * ui.dt + 0.1 * DeltaTime
text("#" .. params.rule .. "  " ..
t[params.reinitmode%3+1] ..
(params.reinitmode<=3 and "<->" or "") ..
string.format(" %4.1f FPS", 1/ui.dt),
100, HEIGHT-10)
end

function getthree(row, x, data)
local v = { data.def, data.def, data.def }
for i=-1,1 do
if x+i < 1 or x+i > #row then
if data.wrap then
v[i+2]=row[(x+i-1+#row)%#row+1]
end
else
v[i+2]=row[x+i]
end
end
return 1+4*v[1]+2*v[2]+1*v[3]
end

function ruleno(data)
local ptwo,sum=1,0
for i=1,8 do
sum = sum + data.pat[i]*ptwo
ptwo = ptwo * 2
end
return sum
end

function wolfram(a, data)

data.rule=ruleno(data)

for y = 2,#a do
for x = 1,#a[y] do
local idx=getthree(a[y-1],x,data)
a[y][x]=data.pat[idx]
end
end
return a
end

function touched(touch)
local x,y=touch.x, touch.y
if touch.state == BEGAN then
for i = 1, #ui.uipos do
local r = ui.uipos[i]
if x >= r.x and y >= r.y and
x-r.x <r.w and y-r.y < r.h
then
params.pat[i]=1-params.pat[i]
wolfram(arr, params)
return
end
end

if touch.tapCount > 1 then
reinit(arr, params)
end

ui.drag = true
elseif touch.state == MOVING then
if ui.drag then
graph.offx = graph.offx + touch.deltaX
graph.offy = graph.offy + touch.deltaY
end
elseif touch.state == ENDED then
if ui.drag then
graph.offx = graph.offx + touch.deltaX
graph.offy = graph.offy + touch.deltaY
end
ui.drag = false
end
end

function reinit(arr, data)
data.reinitmode = data.reinitmode == 6 and 1 or data.reinitmode + 1
data.wrap = data.reinitmode <= 3

if data.reinitmode % 3 == 0 then
for i=1,#arr[1] do
arr[1][i]=math.random(0,1)
end
else
local v = (data.reinitmode % 3) == 1 and 0 or 1
for i=1,#arr[1] do
arr[1][i]=v
end

arr[1][#arr[1]//2]=1-arr[1][#arr[1]//2]
end

wolfram(arr, data)
end

``````
Tagged:

• Mod
Posts: 10,053

@guiath Very interesting. I'm not sure how it works, but I'll read up on the info you provided. I find this type of code more interesting than games.

• Mod
edited January 2016 Posts: 10,053

@quiath I looked at the link you provided and it's kind of interesting. I have a question because I may not be understanding what exactly is supposed to happen. Looking at the link you provided where they show the 8 rules at the top of the page, rule 1 (right most) shows that if 3 squares in a row are off, then the center square of those 3 in the next generation will be off. If that's the case, then no squares for rule 1 should be turned on, yet you're program and the link example shows squares on. Apparently I'm missing something.

• Posts: 12

@dave1707 In my program the first row is not computed, but it's seeded in the following way:
1. a single "on" cell, double tap for:
2. a single "off" cell in a row of "on" cells, double tap for:
3. a randomly generated first row, double tap for:
4-6. repeat of the above with left-right wrapping turned on.

Simply speaking, you need some seed, even very simple, for the patterns to be interesting.

• Mod
Posts: 10,053

@quiath I understand the rules for your program and the need for the starting single seed. But for rule 1 (right most) in the link, it shows that if 3 squares in a row are off, then the center square of those 3 squares in the next generation will be off. If rule 1 doesn't turn any squares on, then nothing should change from generation to generation. But in the link example and your program, there are squares turned on. Shouldn't the seed be the only square turned on when rule 1 is run.

• Posts: 12

@dave1707 I think it's best if I show it on an example. Let's use the rule shown at the top of the page I linked earlier:
http://mathworld.wolfram.com/images/eps-gif/ElementaryCA30Rules_750.gif

(rule 30)
Let's start with an initial row containing 7 columns marked by letters. There are infinitely many columns to the left and right, we assume all of them contain 0:

``````| a | b | c | d | e | f | g |
-----------------------------
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1st
``````

In the second row we will assign the values in the following way, looking at the existing values in the row immediately above, i.e. the first row:
For column a: look up 000 -> result 0
For column b: look up 000 -> result 0
For column c: look up 001 -> result 1
For column d: look up 010 -> result 1
For column e: look up 100 -> result 1
For column f: look up 000 -> result 0
For column g: look up 000 -> result 0

The result is this second row:

``````| a | b | c | d | e | f | g |
-----------------------------
| 0 | 0 | 1 | 1 | 1 | 0 | 0 | 2nd
``````

From here we iterate the rows up to infinity ;-)

For the third row:

For column a: look up 000 -> result 0
For column b: look up 001 -> result 1
For column c: look up 011 -> result 1
For column d: look up 111 -> result 0
For column e: look up 110 -> result 0
For column f: look up 100 -> result 1
For column g: look up 000 -> result 0

In the result we receive a growing triangle pattern:

``````| a | b | c | d | e | f | g |
-----------------------------
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1st
| 0 | 0 | 1 | 1 | 1 | 0 | 0 | 2nd
| 0 | 1 | 1 | 0 | 0 | 1 | 0 | 3rd
...
``````

And so on.

Hope that helps!

• Mod
Posts: 10,053

@quiath I understand it now. I was totally misinterpreting the 8 rules. Sorry for the trouble.

• Mod
Posts: 894

@quiath, some are doubled some not existing

• Posts: 12

@TokOut What is doubled/not existing, would you care to elaborate?

• Mod
Posts: 10,053

I think he means that some values show the same pattern and some other values don't draw a pattern at all. He thinks that every value should show a different pattern.

• Posts: 12

@dave1707 Thank you, that makes sense. ;-)

• Posts: 509

@quiath Here's a shader version. I like your UI, but for simplicity I went with Codea's parameters in this (so rules are specified by their number rather than by choosing the individual actions). I count 193 lines, so still under your self-imposed limit of 200.

``````-- CellularShader

function setup()
cellular = mesh()
width = 10
rule = 18
backingMode(RETAINED)
parameter.integer("rule",0,255,18,setRule)
parameter.integer("width",10,2*WIDTH,10,setWidth)
parameter.boolean("continuous", function(b)
if b then
step = true
end
end)
parameter.action("step",function() step = true end)
parameter.action("restart",clearImgs)
parameter.watch("math.floor(1/DeltaTime)")
noSmooth()
step = true
end

function draw()
if step then
doRule()
step = continuous
end
end

function doRule()
pushMatrix()
translate(WIDTH/2,y)
scale(WIDTH/width)
sprite(imga)
popMatrix()
y=y-sf
if y< 0 then
y = HEIGHT-sf/2
background(0, 0, 0, 255)
pushMatrix()
translate(WIDTH/2,y)
scale(WIDTH/width)
sprite(imga)
popMatrix()
end
setContext(imgb)
background(0, 0, 0, 255)
cellular:draw()
setContext()
imga,imgb = imgb,imga
cellular.texture = imga
end

local patterns = {
"OOO",
"OOX",
"OXO",
"OXX",
"XOO",
"XOX",
"XXO",
"XXX"
}

local result = {"O", "X"}

function setRule(r)
local rules = {}
output.clear()
local disp = {"Rule:"}
for k=1,8 do
table.insert(rules,r%2)
table.insert(disp,patterns[k] .. " -> " .. result[r%2 + 1])
r = r//2
end
print(table.concat(disp,"\n"))
clearImgs()
end

setWidth(width)
setRule(rule)
end

function setWidth(w)
width = w
sf = WIDTH/w
y=HEIGHT-sf/2
cellular:setRect(1,width/2,.5,width,1)
imga = image(width,1)
imgb = image(width,1)
cellular.texture = imga
clearImgs()
end

function clearImgs()
setContext(imga)
background(0, 0, 0, 255)
setContext()
imga:set(math.floor(width/2),1,color(255,255,255,255))
background(0, 0, 0, 255)
y = HEIGHT-sf/2
step = true
end

function automata(b)
local wrap
if b then
wrap = function(s)
return "fract(" .. s .. ")"
end
else
wrap = function(s)
return s
end
end
[[
//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}

]],[[
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

uniform int rules[8];
uniform float width;
float rw = 1./width;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

int parents(float x)
{
int v = 0;
v += int( texture2D(texture,vec2(]] .. wrap("x-rw") .. [[,.5)).r);
v += 2*int( texture2D(texture,vec2(x,.5)).r);
v += 4*int( texture2D(texture,vec2(]] .. wrap("x+rw") .. [[,.5)).r);
return v;
}

void main()
{
//Sample the texture at the interpolated coordinate
int i = parents(vTexCoord.x);
vec4 col = vec4(0.);
col.r = float( rules[i]);
//Set the output color to the texture color
gl_FragColor = col;
}

]]
)
end
``````
• Posts: 12

@LoopSpace Very interesting since the continuous button lets you stare at the patterns until infinity ;-)
BTW does it compute one row per frame?

• Posts: 509

@quiath Yes, in continuous mode then it is one row per frame. One could do more per frame without affecting the framerate, I guess, as it's quite cheap to do a row on the gpu.