Cellular automata

I’m trying to write a cellular automaton shader, and I started off by making one to run the basic Conway rules. Here’s my code:


--# Main
-- LifeShader

-- Use this function to perform your initial setup
function setup()
    
    displayMode(FULLSCREEN)
    noSmooth()
    life = {}

    life.s = vec2(WIDTH, HEIGHT)
    
    life.m = mesh()
    life.m.vertices = triangulate{vec2(0,0),vec2(0,HEIGHT),vec2(WIDTH,HEIGHT),vec2(WIDTH,0)}
    life.m.texCoords = triangulate{vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)}
    life.m.shader = shader(vertex, fragment)
    life.m.shader.size = life.s
    life.m.shader.scale = ContentScaleFactor
    life.source = image(life.s.x, life.s.y)
    life.target = image(life.s.x, life.s.y)
    
    spriteMode(CORNER)
    setContext(life.target)
    background(0)
    setContext()
    
    --setContext(life.source)
    --noStroke()
    --noSmooth()
    --[[
    local nf = 50 + 5*math.random()
    local xd = math.random()
    local yd = math.random()
    for x = 1,WIDTH do
        for y = 1,HEIGHT do
            if noise(nf*x/WIDTH + xd,nf*y/HEIGHT + yd) > .2 then
                life.source:set(x, y, color(255))
            end
            --rect(x, y, 1, 1)
        end
    end
    --]]
    --setContext()
    
    --[[
    local pattern = readImage("Project:glidergun")
    local p2 = image(math.ceil(pattern.width / 2) * 2, math.ceil(pattern.height / 2) * 2)
    setContext(p2)
    background(0)
    for x = 1, pattern.width do
        for y = 1, pattern.height do
            p2:set(x, y, color(255 - pattern:get(x, y)))
        end
    end
    saveImage("Project:ggun", p2)
    setContext()
    --[=[
    --]]
    local pattern = readImage("Project:ggun")
    --]=]
    setContext(life.source)
    sprite(pattern, math.floor(WIDTH / 2), math.floor(HEIGHT / 2), pattern.width/2, pattern.height/2)
    setContext()
    
    life.m:setColors(color(255))
    life.m.texture = life.source
    
    --local i = life.m:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    --life.m:setRectTex(i, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    
end

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

    -- This sets the line thickness
    strokeWidth(0)
    noSmooth()
    --fill(255)

    -- Do your drawing here
    spriteMode(CORNER)
    life.m.texture = life.source
    setContext(life.target)
    --background(255)
    life.m:draw()
    setContext()
    spriteMode(CORNER)
    sprite(life.target, 0,0)
    life.source = life.target
end

function touched(t)
    setContext(life.source)
    stroke(255)
    strokeWidth(1)
    line(t.prevX, t.prevY, t.x, t.y)
    setContext()
end

--# Shader
vertex = [[
//
// A basic vertex shader
//

//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 highp 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;
}
]]
fragment = [[
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

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

uniform highp vec2 size;

uniform highp float scale;

//The interpolated vertex color for this fragment
varying highp vec4 vColor;

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

void main()
{
    //Sample the texture at the interpolated coordinate
    highp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    
    //*
    int neighbors = 0;
    
    highp float w = 1. / size.x / scale;
    highp float h = 1. / size.y / scale;
    
    neighbors += int(texture2D(texture, vTexCoord + vec2(-w, -h)).r+.5);
    neighbors += int(texture2D(texture, vTexCoord + vec2(0., -h)).r+.5);
    neighbors += int(texture2D(texture, vTexCoord + vec2(w, -h)).r+.5);
    
    neighbors += int(texture2D(texture, vTexCoord + vec2(-w, 0.)).r+.5);
    neighbors += int(texture2D(texture, vTexCoord + vec2(w, 0.)).r+.5);
    
    neighbors += int(texture2D(texture, vTexCoord + vec2(-w, h)).r+.5);
    neighbors += int(texture2D(texture, vTexCoord + vec2(0., h)).r+.5);
    neighbors += int(texture2D(texture, vTexCoord + vec2(w, h)).r+.5);
    
    if (neighbors < 2) col = vec4(0,0,0,1);
    else if (neighbors > 3) col = vec4(0,0,0,1);
    else if (neighbors == 3) col = vec4(1,1,1,1);
    else col = vec4(int(col.r+.5),int(col.r+.5),int(col.r+.5),1);
    //*/
    
    /*
    col = vec4(1, 0, 0, 1);
    //*/

    //Set the output color to the texture color
    gl_FragColor = col;
}
]]

For some reason, it doesn’t work, and what’s really weird is that the results are different every time I run it, even though the pattern is always the same. It runs perfectly for a little while, and then somewhere some cell is processed wrong, and the errors don’t seem to follow any particular pattern. Any idea how I could fix this?

Are you aware that @Andrew_Stacey did this very thing? See the post here: http://codea.io/talk/discussion/2252/game-of-life-shader

If you’re trying to make the game of life as a stepping stone to a more complex project, I would recommend taking the time to understand Stacey’s code, since it will leapfrog you past some of the basic challenges at the start.

Yeah I checked out his code and I’ve managed to fix it now. I still have absolutely no idea why it didn’t work before, but I’m ready to build on it now

@FLCode what are you planning to do? I’m curious because I tried to build on it too, but my thing didn’t work out great.

I’m making a Langton’s Ant shader.

I don’t think that Langton’s Ant is a good fit for a shader because only one cell changes per cycle, and this kind of thing on a shader works best when every cell has the potential to change each cycle so that it can take advantage of the parallel processing afforded by the GPU.

Incidentally, a search of the forum for “Langton” threw up this post.

Fascinating! Are you intending to go all the way through to turmites, etc?

@FLCode Here’s a version of Langton’s Ant I have. The default is 20 iterations per draw cycle, but you can slide that to 500. You can go higher if you change the slider max value. It’s interesting to watch it.

EDIT: It took over 35,000,000 iterations to fill the screen in Portrait mode.
EDIT: It took over 40,000,000 iterations to fill the screen in Landscape mode. I also increased the iterations to 5,000 per draw cycle for that one.

-- Langton's Ant

displayMode(OVERLAY)

function setup()
    parameter.watch("iter")
    parameter.integer("iterations_per_draw",1,500,20)
    img=image(WIDTH,HEIGHT)
    x,y=600,400
    ant=2
    fill(255)
    iter=0
end

function draw()
    background(0)
    sprite(img,WIDTH/2,HEIGHT/2)
    for z=1,iterations_per_draw do
        r,g,b=img:get(x,y)
        if r>0 then
            img:set(x,y,0,0,0,1)
            rotLeft()
        else
            img:set(x,y,255,255,255,255)
            rotRight()
        end
        iter=iter+1
    end
end

function rotRight()
    ant=ant+1
    if ant>4 then
        ant=1
    end
    move()
end

function rotLeft()
    ant=ant-1
    if ant<1 then
        ant=4
    end
    move()
end

function move()
    if ant==3 then
        y=y+1
        if y>HEIGHT then
            y=1
        end
    elseif ant==4 then
        x=x+1
        if x>WIDTH then
            x=1
        end
    elseif ant==1 then
        y=y-1
        if y<1 then
            y=HEIGHT
        end
    else
        x=x-1
        if x<1 then
            x=WIDTH
        end
    end
end

Here’s another version of the Langton’s Ant. This one has 20 random ants instead of just one. Also, this one is interesting to watch running at a slow speed because you can watch as the ants interact with each other. You can watch as 2 ants will devour each other, and exchange their color.


-- Langton's Ant

displayMode(OVERLAY)

function setup()
    antTab={}
    parameter.watch("iter")
    parameter.integer("iterations_per_draw",1,5000,20)
    img=image(WIDTH,HEIGHT)
    for z=1,20 do
        x=math.random(WIDTH)
        y=math.random(HEIGHT)
        d=math.random(4)
        c=color(math.random(255),math.random(255),math.random(255))
        table.insert(antTab,ant(x,y,d,c))
    end
    iter=0
end

function draw()
    background(0)
    sprite(img,WIDTH/2,HEIGHT/2)
    for z=1,iterations_per_draw do
        for a,b in pairs(antTab) do
            b:move()
        end
        iter=iter+1
    end
end

ant=class()

function ant:init(x,y,d,c)
    self.x=x
    self.y=y
    self.dir=d
    self.col=c
end

function ant:move()
    local r,g,b=img:get(self.x,self.y)
    if r+g+b>0 then
        img:set(self.x,self.y,0,0,0)
        self.dir=self.dir-1
        if self.dir<1 then
            self.dir=4
        end
    else
        img:set(self.x,self.y,self.col)
        self.dir=self.dir+1
        if self.dir>4 then
            self.dir=1
        end
    end
    if self.dir==3 then
        self.y=self.y+1
        if self.y>HEIGHT then
            self.y=1
        end
    elseif self.dir==4 then
        self.x=self.x+1
        if self.x>WIDTH then
            self.x=1
        end
    elseif self.dir==1 then
        self.y=self.y-1
        if self.y<1 then
            self.y=HEIGHT
        end
    else
        self.x=self.x-1
        if self.x<1 then
            self.x=WIDTH
        end
    end
end

@FLCode, would you mind renaming this thread? “Code not working” is so vague that I never remember what this discussion is about, and IMO it’s a very interesting one.