Day and Night (cellular automaton)

Show off your games, demos and other (playable) creations.
Post Reply
User avatar
darkfrei
Party member
Posts: 1168
Joined: Sat Feb 08, 2020 11:09 pm

Day and Night (cellular automaton)

Post by darkfrei »

Hi all!

Here is a Day and Night (cellular automaton)

Very short code, no GUI or shortcuts.

Nice algorithm in pure Lua to create procedural generated maps (by stop after 200 steps).

Code: Select all

function love.load()
  w = 1920/4
  h = 1080/4
    love.window.setMode(w, h)
    
    field = {}
    for y = 1, h do
      field[y] = {}
      for x = 1, w do
        field[y][x] = love.math.random (2)-1
      end
    end
    
    born =    {
      [3]=true,    
      [6]=true, 
      [7]=true, 
      [8]=true}
    survive = {
      [3]=true, 
      [4]=true, 
      [6]=true, 
      [7]=true, 
      [8]=true}
    
    canvas = love.graphics.newCanvas(w, h)
end

-- moore-neighborhood
mns = {
{-1,-1},  
{ 0,-1},  
{ 1,-1},  
{ 1, 0},  
{ 1, 1},  
{ 0, 1},  
{-1, 1},  
{-1, 0},  
}

function getValue (x, y)
  return field[(y-1)%h+1][(x-1)%w+1]
end

function getAmount (x, y)
  local amount = 0
  for i, mn in ipairs (mns) do
    amount = amount + getValue (x+mn[1], y+mn[2])
  end
  return amount
end

function love.update(dt)
    
    local temp = {}
    local points = {}
    for y, xs in ipairs (field) do
      temp[y] = {}
      for x, value in ipairs (xs) do
        if value == 0 then
          if born[getAmount(x, y)] then
            temp[y][x] = 1
            table.insert (points, {x,y,1,1,1})
          else
            temp[y][x] = 0
            table.insert (points, {x,y,0,0,0})
          end
        else -- 1
          if survive[getAmount(x, y)] then
            temp[y][x] = 1
            table.insert (points, {x,y,1,1,1})
          else
            temp[y][x] = 0
            table.insert (points, {x,y,0,0,0})
          end
        end
      end
    end
    field = temp
    love.graphics.setCanvas(canvas)
      love.graphics.translate(-0.5, -0.5)
      love.graphics.points(points)
    love.graphics.setCanvas()
end

function love.draw()
  
  love.graphics.draw(canvas)
end
2022-11-29 (1).png
2022-11-29 (1).png (66.63 KiB) Viewed 2091 times
2022-11-29.png
2022-11-29.png (35.12 KiB) Viewed 2091 times
Attachments
day-night-01.love
License CC0 (no license)
(790 Bytes) Downloaded 77 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3541
Joined: Sun Oct 18, 2015 2:58 pm

Re: Day and Night (cellular automaton)

Post by pgimeno »

Isn't that suitable for a shader?
User avatar
darkfrei
Party member
Posts: 1168
Joined: Sat Feb 08, 2020 11:09 pm

Re: Day and Night (cellular automaton)

Post by darkfrei »

pgimeno wrote: Wed Nov 30, 2022 9:26 pm Isn't that suitable for a shader?
I don't know how to do it. :x
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3541
Joined: Sun Oct 18, 2015 2:58 pm

Re: Day and Night (cellular automaton)

Post by pgimeno »

This runs at over 450 FPS on my desktop, and my graphics card is not particularly fast.

Code: Select all

love.window.setVSync(false)
local daynight = love.graphics.newShader[[

  // Pixel width/height in normalized units (i.e. inverse of screen w/h)
  extern float pw, ph;

  vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos)
  {
    float state = Texel(tex, texpos).r;
    float
     sum = Texel(tex, texpos + vec2(-pw, -ph)).r;
    sum += Texel(tex, texpos + vec2(0.0, -ph)).r;
    sum += Texel(tex, texpos + vec2( pw, -ph)).r;
    sum += Texel(tex, texpos + vec2(-pw, 0.0)).r;
    sum += Texel(tex, texpos + vec2( pw, 0.0)).r;
    sum += Texel(tex, texpos + vec2(-pw,  ph)).r;
    sum += Texel(tex, texpos + vec2(0.0,  ph)).r;
    sum += Texel(tex, texpos + vec2( pw,  ph)).r;
    if (sum >= 2.5 && sum < 3.5 || sum >= 5.5) // 3, 6, 7, 8 becomes live
      state = 1.0;
    else if (sum < 3.5 || sum >= 4.5) // 4 survives, rest dies
      state = 0.0;
    return vec4(state, state, state, 1.0);
  }

]]

do
  local gettime = require('socket').gettime
  math.randomseed(gettime())
end

local current = love.graphics.newCanvas()
local new = love.graphics.newCanvas()
current:setWrap("repeat")
new:setWrap("repeat")

local rndimdata = love.image.newImageData(current:getDimensions())
rndimdata:mapPixel(function (x, y)
  local r = math.random(0, 1)
  return r, r, r, 1.0
end)
local rndimg = love.graphics.newImage(rndimdata)
rndimdata:release()

love.graphics.setCanvas(current)
love.graphics.draw(rndimg)
love.graphics.setCanvas()
rndimg:release()
daynight:send('pw', 1/love.graphics.getWidth())
daynight:send('ph', 1/love.graphics.getHeight())


function love.resize(w, h)
  new:release()
  new = love.graphics.newCanvas()
  love.graphics.setCanvas(new)
  love.graphics.draw(current)
  love.graphics.setCanvas()
  current:release()
  current = new
  new = love.graphics.newCanvas()
  current:setWrap("repeat")
  new:setWrap("repeat")
  daynight:send('pw', 1/w)
  daynight:send('ph', 1/h)
end

function love.keypressed(k) return k == "escape" and love.event.quit() end

local title = nil
function love.update(dt)
  love.graphics.setCanvas(new)
--  love.graphics.clear()
  love.graphics.setShader(daynight)
  love.graphics.draw(current)
  love.graphics.setShader()
  love.graphics.setCanvas()
  current, new = new, current
  local oldtitle = title
  title = love.timer.getFPS()
  if title ~= oldtitle then
    love.window.setTitle(title)
  end
end

function love.draw()
  love.graphics.draw(current)
end
User avatar
darkfrei
Party member
Posts: 1168
Joined: Sat Feb 08, 2020 11:09 pm

Re: Day and Night (cellular automaton)

Post by darkfrei »

Nice! But why that doesn't check the state of current pixel and values have 0.5 extra?
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3541
Joined: Sun Oct 18, 2015 2:58 pm

Re: Day and Night (cellular automaton)

Post by pgimeno »

The 0.5 extra is to prevent rounding errors, as I've had bad experiences with floats in GLSL. Not sure what you mean about checking the current pixel; it returns the current pixel when the number of neighbours is 4, like the original formulation does.

Edit: These rules:
When the current cell is 0 and the number of neighbours is 3, 6, 7 or 8, set the current cell to 1.
When the current cell is 1 and the number of neighbours is 3, 4, 6, 7 or 8, set the current cell to 1.
Otherwise set the current cell to 0.
are equivalent to these ones:
When the number of neighbours is 3, 6, 7 or 8, set the current cell to 1.
When the number of neighbours is 4, leave the current cell as is.
Otherwise set the current cell to 0.
But the latter is easier to code. Maybe that's what confused you?

Edit2: 470 FPS with this shader, which only reads the current pixel if sum = 4:

Code: Select all

  extern float pw, ph;

  vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos)
  {
    float state=0.0;
    float
     sum = Texel(tex, texpos + vec2(-pw, -ph)).r;
    sum += Texel(tex, texpos + vec2(0.0, -ph)).r;
    sum += Texel(tex, texpos + vec2( pw, -ph)).r;
    sum += Texel(tex, texpos + vec2(-pw, 0.0)).r;
    sum += Texel(tex, texpos + vec2( pw, 0.0)).r;
    sum += Texel(tex, texpos + vec2(-pw,  ph)).r;
    sum += Texel(tex, texpos + vec2(0.0,  ph)).r;
    sum += Texel(tex, texpos + vec2( pw,  ph)).r;
    if (sum >= 2.5 && sum < 3.5 || sum >= 5.5) // 3, 6, 7, 8 becomes live
      state = 1.0;
    else if (sum >= 3.5 && sum < 4.5) // 4 unchanged, rest dies
      state = Texel(tex, texpos).r;

    return vec4(state, state, state, 1.0);
  }
Post Reply

Who is online

Users browsing this forum: No registered users and 10 guests