Help with Conway's Game of Life

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
permadeth
Prole
Posts: 9
Joined: Thu Apr 17, 2014 10:17 pm

Help with Conway's Game of Life

Post by permadeth »

Hey LOVErs!

I've been trying to write Conway's Game of Life https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life in lua/love for the last couple days, and I've felt like I was making progress...
...today I was working on the logic of the game, and I do not understand why it's not working as intended! Instead of the cells evolving like they should, they are always moving to the left (while evolving, somehow)! I've been trying to decide if I should scrap what I've done so far, and try again with a clear mind and a blank canvas, but I would really appreciate it if someone would help me out, and tell me what is causing this strange occurrence...

Here is the code I have so far:

Code: Select all

math.randomseed (math.sqrt (os.time () * os.time ()))

cells = {}

cells.map = {}

cells.x = 80
cells.y = 60

cells.w = love.graphics.getWidth () / cells.x
cells.h = love.graphics.getHeight () / cells.y


for i = 1, cells.x do
  cells.map[i] = {}

  for j = 1, cells.y do
    if math.random (2) == 1 then
      cells.map[i][j] = 0
    else
      cells.map[i][j] = 1
    end
  end
end

cells.newMap = {}

for i = 1, cells.x do
  cells.newMap[i] = {}
  for j = 1, cells.y do
    cells.newMap[i][j] = {}
  end
end


function cells.getNeighbors (_cells_map, _i, _j)
  cells_map = _cells_map

  x = _i
  y = _j

  if x == 1 or y == 1 then
    ul = 0
  else
    ul = cells_map[x-1][y-1]
  end
  if y == 1 then
    uu = 0
  else
    uu = cells_map[x][y-1]
  end
  if x == cells.x or y == 1 then
    ur = 0
  else
    ur = cells_map[x+1][y-1]
  end
  if x == 1 then
    ll = 0
  else
    ll = cells_map[x-1][y]
  end
  if x == cells.x then
    rr = 0
  else
    rr = cells_map[x+1][y]
  end
  if x == 1 or y == cells.y then
    dl = 0
  else
    dl = cells_map[x-1][y+1]
  end
  if y == cells.y then
    dd = 0
  else
    dd = cells_map[x][y+1]
  end
  if x == cells.x or y == cells.y then
    dr = 0
  else
    dr = cells_map[x+1][y+1]
  end

  living = (
    ul + uu + ur
  + ll + 0  + rr
  + dr + dd + dr
  )
  return living
end

function cells.update ()
  for i = 1, cells.x do
    for j = 1, cells.y do
      cells.getNeighbors (cells.map, i, j)
      if cells.map[i][j] == 1 then
        if living == 0 or living == 1 then
          cells.newMap[i][j] = 0
        elseif living == 2 or living == 3 then
          cells.newMap[i][j] = 1
        elseif living > 3 and living < 9 then
          cells.newMap[i][j] = 0
        else
          --cells.newMap[i][j] = 1
        end
      end
      if cells.map[i][j] == 0 then
        if living == 3 then
          cells.newMap[i][j] = 1
        else
          cells.newMap[i][j] = 0
        end
      end
      if i == cells.x and j == cells.y then
        cells.map = cells.newMap
      end
    end
  end
end

function cells.draw ()
  for i = 1, cells.x do
    for j = 1, cells.y do
      if cells.map[i][j] == 0 then
        love.graphics.rectangle ('line', ((cells.w * i) - cells.w), ((cells.h * j) - cells.h), cells.w, cells.h)
      else
        love.graphics.rectangle ('fill', ((cells.w * i) - cells.w), ((cells.h * j) - cells.h), cells.w, cells.h)
      end
    end
  end
end


return cells
All main.lua does in this case is call the cells.update () and cells.draw () functions from cells.lua... Here is main.lua:

Code: Select all

local cells = require "cells"

function love.load ()
end

function love.update (dt)
  cells.update ()
end

function love.draw ()
  cells.draw ()
  --for i = 1, cells.x do
  --  for j = 1, cells.y do
  --    love.graphics.print (tostring (cells.map[i][j]), 0 + (10 * i) - 10, 0 + (10 * j) - 10)
  --  end
  --end
end
Anything obvious that I'm neglecting? Should I start over?

Thanks for taking the time to look at my work :awesome:
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Help with Conway's Game of Life

Post by ivan »

permadeth wrote:Anything obvious that I'm neglecting? Should I start over?
Hi and welcome to the forums.
One thing I would say is that, Conway's Game of Life is fairly abstract
so if you want to make a module for it, I would remove any Love2D functionality
from that module. This way, your 'cells' module would be pure Lua (with no dependencies).
Things like "cells.draw" and "love.graphics.getWidth" don't really belong in your abstract 'cells' module.
Separating the logic from the rendering will allow you to swap the front-end easily.
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Help with Conway's Game of Life

Post by zorg »

Add local here

Code: Select all

local living = (
    ul + uu + ur
  + ll + 0  + rr
  + dr + dd + dr
  )
and local living = here

Code: Select all

for i = 1, cells.x do
    for j = 1, cells.y do
      local living = cells.getNeighbors (cells.map, i, j)
Try that, see if it works or not
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Help with Conway's Game of Life

Post by ivan »

Code: Select all

function cells.getNeighbors (_cells_map, _i, _j)
  cells_map = _cells_map

  x = _i
  y = _j

  if x == 1 or y == 1 then
    ul = 0
  else
    ul = cells_map[x-1][y-1]
  end
  if y == 1 then
    uu = 0
  else
    uu = cells_map[x][y-1]
  end
  if x == cells.x or y == 1 then
    ur = 0
  else
    ur = cells_map[x+1][y-1]
  end
  if x == 1 then
    ll = 0
  else
    ll = cells_map[x-1][y]
  end
  if x == cells.x then
    rr = 0
  else
    rr = cells_map[x+1][y]
  end
  if x == 1 or y == cells.y then
    dl = 0
  else
    dl = cells_map[x-1][y+1]
  end
  if y == cells.y then
    dd = 0
  else
    dd = cells_map[x][y+1]
  end
  if x == cells.x or y == cells.y then
    dr = 0
  else
    dr = cells_map[x+1][y+1]
  end

  living = (
    ul + uu + ur
  + ll + 0  + rr
  + dr + dd + dr
  )
  return living
end
This is a fun function to solve using iteration:

Code: Select all

-- 8 adjacent cells
local dirs = { {-1,-1}, {0,-1}, {1,-1}, {-1,0}, {1,0}, {-1,1}, {0,1}, {1,1} }

function cells.getNeighbors(map, i, j)
  local v = 0
  -- iterate adjacent cells
  for _, dir in ipairs(dirs) do
    local i2, j2 = i + dir[1], j + dir[2]
    -- check if the neighbor is off the map
    if i2 >= 1 and i2 <= cells.x then
      if j2 >= 1 and j2 <= cells.y then
        v = v + map[i2][j2]
      end
    end
  end
  return v
end
User avatar
TheMeq
Citizen
Posts: 56
Joined: Fri Sep 02, 2011 9:56 pm
Location: Nottingham, UK

Re: Help with Conway's Game of Life

Post by TheMeq »

You need to create a copy of the board and compare your next generation against that instead of against the current generation as your calculating it. That's why it's going over to the left
permadeth
Prole
Posts: 9
Joined: Thu Apr 17, 2014 10:17 pm

Re: Help with Conway's Game of Life

Post by permadeth »

Thank you for all the helpful code and ideas :awesome: ! I implemented the iterative getNeighbors function, and made the living variable local, as well as the advice of copying the board, before comparing. Right now it does some pretty insane (but kinda neat) things visually.

If anyone's curious, here's the current code:

Code: Select all

math.randomseed (os.time ())

cells = {}

cells.map = {}

cells.x = 80
cells.y = 60

cells.w = 800 / cells.x
cells.h = 600 / cells.y

function cells.load ()
  for i = 1, cells.x do
    cells.map[i] = {}

    for j = 1, cells.y do
      if math.random (10) == 1 then
        cells.map[i][j] = 1
      else
        cells.map[i][j] = 0
      end
    end
  end
end


local dirs = { {-1,-1}, {0,-1}, {1,-1}, {-1,0}, {1,0}, {-1,1}, {0,1}, {1,1} }

function cells.getNeighbors(map, i, j)
  local v = 0
  -- iterate adjacent cells
  for _, dir in ipairs(dirs) do
    local i2, j2 = i + dir[1], j + dir[2]
    -- check if the neighbor is off the map
    if i2 >= 1 and i2 <= cells.x then
      if j2 >= 1 and j2 <= cells.y then
        v = v + map[i2][j2]
      end
    end
  end
  return v
end
--TODO
--You need to create a copy of the board and compare your next generation against that instead of against the current generation as your calculating it. That's why it's going over to the left
function cells.update ()
  cells.newMap = cells.map
  cells.map = {}
  for i = 1, cells.x do
    cells.map[i] = {}
    for j = 1, cells.y do
    --cells.map[i][j] = {}
      
      local living = cells.getNeighbors (cells.newMap, i, j)

      if cells.newMap[i][j] == 1 then
        if living <2 then
          cells.map[i][j] = 0
        elseif living == 2 or living == 3 then
          cells.map[i][j] = 1
        elseif living >3 then
          cells.map[i][j] = 0
        else
          cells.newMap[i][j] = 1
        end
      elseif cells.newMap[i][j] == 0 then
        if living == 3 then
          cells.map[i][j] = 1
        else
          cells.map[i][j] = 0
        end
      end
      
    end
  end

  --cells.map = cells.newMap
end

function cells.draw ()
  for i = 1, cells.x do
    for j = 1, cells.y do
      if cells.map[i][j] == 0 then
        love.graphics.rectangle ('line', ((cells.w * i) - cells.w), ((cells.h * j) - cells.h), cells.w, cells.h)
      else
        love.graphics.rectangle ('fill', ((cells.w * i) - cells.w), ((cells.h * j) - cells.h), cells.w, cells.h)
      end
    end
  end
end


return cells

Note that I reduced the frequency in which any cell could potentially be 'alive', from 1/2, to 1/20. This value changes the results significantly, as you might imagine!

I also added a love.timer.sleep (.1) function in the love.update () loop in main.lua. This makes it easier to see what each stage in the evolution is.

This is the last version of the game. I hadn't cleared to original board (cells.map), so it made for some weird possibilities!) Here is a weird image of 4 games of life being played at once Image Sorry about the bad quality, but you can see in the image that 3 have become still, while one is still going strong, many minutes later! I goofed in the cells.update () function... Anyway, thanks a lot! If you have anything else to point out, I would be happy to get more feedback! Cheers
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Help with Conway's Game of Life

Post by ivan »

Here a modified version of the game.
Changes:
1.the rules are implemented as a table

Code: Select all

-- rules table
local rules = {}
-- dead cells become alive if there are 3 live neighbors
rules[0] = { [0] = 0, 0, 0, 1, 0, 0, 0, 0, 0 }
-- live cells survive if there are 2 or 3 live neighbors
rules[1] = { [0] = 0, 0, 1, 1, 0, 0, 0, 0, 0 }
2.you can edit the map using the left/right mouse buttons
the simulation is paused while the mouse is down,
so you don't get a lot of control, but it's good for debugging

3.rendering is moved to main.lua and we don't use 'line' since it's slow
Attachments
cgl.love
(1.1 KiB) Downloaded 184 times
permadeth
Prole
Posts: 9
Joined: Thu Apr 17, 2014 10:17 pm

Re: Help with Conway's Game of Life

Post by permadeth »

ivan wrote:Here a modified version of the game.
Changes:
1.the rules are implemented as a table

Code: Select all

-- rules table
local rules = {}
-- dead cells become alive if there are 3 live neighbors
rules[0] = { [0] = 0, 0, 0, 1, 0, 0, 0, 0, 0 }
-- live cells survive if there are 2 or 3 live neighbors
rules[1] = { [0] = 0, 0, 1, 1, 0, 0, 0, 0, 0 }
2.you can edit the map using the left/right mouse buttons
the simulation is paused while the mouse is down,
so you don't get a lot of control, but it's good for debugging

3.rendering is moved to main.lua and we don't use 'line' since it's slow
That's brilliant! Thanks :)

I was tinkering with my own code and noticed that line was much much slower indeed.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 2 guests