Problem with movement of snake in planned snake game

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.
mynameisnotpaul
Prole
Posts: 10
Joined: Sun Jan 01, 2017 4:25 am

Problem with movement of snake in planned snake game

Post by mynameisnotpaul »

I'm trying to make a snake game, but I have a problem with the movement part. In classic snake, each tile moved is equal to the width of the snake as to stop it from constantly killing itself on impact. I'm trying to do this, but I have no clue how to without tying it to the game's frame rate. I've tried doing it with code that only let's it go in a direction if its X is a multiple of 20 as seen here:

Code: Select all

function love.update (dt)
  if love.keyboard.isDown("up") then
    while mo20(snakeHead.x) ~= true and mo20(snakeHead.y) ~= true  do end
    direction = 1
  end
  
  function love.draw()
  love.graphics.setColor(255, 255, 255)
  love.graphics.rectangle('fill', snakeHead.x, snakeHead.y, snakeHead.width, snakeHead.height)
  if direction == 1 then
    snakeHead.y = snakeHead.y - 2
  
  function mo20(numero)
  local boolean = false
  local checker = math.abs(numero)/20
  if numero ~= 0 then
    if checker == math.floor(checker) then
      boolean = true
      return boolean
    else
      return boolean
    end
  end
end
(I have code for other arrow keys too, although it feels redundant putting them here as they are the same thing just with their respective x/y changes.)

But this just ends up making the game freeze on start up. Does anyone know how else I could make the snake turn when its x/y is a multiple of 20 or change the x/y by 20 at a speed slower than the framerate without directly changing how often love.update() is called? Thanks!
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Problem with movement of snake in planned snake game

Post by zorg »

Hi and welcome to the forums.

First, you mean that each time it moves, the movement should be equal to the size of a body segment of a snake, one block.

The way you'd accomplish it has to do with that dt parameter you see as a parameter to love.update; if you multiply the distance it should move by dt, it will no longer be framerate dependent, but time dependent.

Note that this will result in the snake's position having non-integer coordinates most of the time, but that can be solved with drawing it out floored in love.draw. (You can also floor it to a "bigger" grid, like, every 20 pixels or so: math.floor(n/20)*20 for example)

Now the issues;
- you define love.draw and your mo20 function inside love.update, which shouldn't be done, since it'll recreate those functions each frame.
- that while loop is either doing nothing, or making you experience that infinite loop you mentioned. again, it shouldn't be there.
- A minor thing, but you don't need to define a local called boolean in your function, you can just as easily return true or return false... nor do you really need a function for just checking whether a number is a multiple of 20, see above for my example one-liner solution.
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.
Sawamara
Prole
Posts: 2
Joined: Mon Nov 21, 2016 6:41 pm

Re: Problem with movement of snake in planned snake game

Post by Sawamara »

Hi! Since you are jumping from block to block, the actual movement should only occur whenever the game "ticks", so to speak.
Which means, that originally, you can make the Snake move each second. How? Well, add that DT to an accumulator variable, and whenever its contents reach its "limit" (lets give it a value of 1000, as in 1second), the movement is made, the accumulator value is set to 0. (Or, to be more precise: 0+ the amount of value it passed over 1000, so if it was 1010ms, then the accumulator will start at 10ms.)

This way, speeding up the game just means setting the limit below 1000ms, gradually. First, 900ms, then 810, then 700, etc. Sky's the limit.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Problem with movement of snake in planned snake game

Post by s-ol »

Sawamara wrote:...(lets give it a value of 1000, as in 1second), the movement is made, the accumulator value is set to 0. (Or, to be more precise: 0+ the amount of value it passed over 1000, so if it was 1010ms, then the I will start at 10ms.)
This is also what I would recommend, except you describe it in a bit complicated of a way - you are just talking about substraction. I usually do it like this:

Code: Select all

local time = 0

function love.update (dt)
  time = time + dt
  if time > time_to_do_stuff then
    -- do stuff
    time = time - time_to_do_stuff
  end
end
Also there is no reason to give it a value of 1000, dt in love is in seconds and float accuracy is enough. I'd just stick with that.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
Sawamara
Prole
Posts: 2
Joined: Mon Nov 21, 2016 6:41 pm

Re: Problem with movement of snake in planned snake game

Post by Sawamara »

Yeah, you are right, I was contemplating editing the whole bit out, because you get a value in seconds anyway.

Funny thing is, I am actually trying to learn using lua+love ("migrated" from PhaserJS/PIXI + years of Javascript), so today I decided to make a snake game, inspired by this topic. Scorecounting, proper backgrounds are tbd, also the pickup of new pieces is a bit buggy, i am going to rewrite that as well. But, at the end of the day, I am pleasantly surprised by how fast one can work with this framework.

Code: Select all

 
function love.load()
  
  platform = {}
snake = {}
stage = {};
stage.enemy = {};
stage.bound_y = 24;
stage.bound_x = 30;
stage._map = {}
collided = false; 

  for i=0, stage.bound_x do
      stage._map[i] = {}
      for j=0, stage.bound_y do
        stage._map[i][j] = 0
      end 
  end
  
snake.move ={
      ---We describe the movement depending on where it moves.--
      left = function(item)
          item.x = (item.x-1) % stage.bound_x; 
          item.prev_direction = "left";
        end; 
        
      right = function(item) 
            item.x = (item.x+1) % stage.bound_x;
            item.prev_direction = "right";
        end; 
      
      up = function(item)
            item.y = (item.y-1) % stage.bound_y;
            item.prev_direction = "up";
          end;
      
      down = function(item)
            item.y = (item.y+1) % stage.bound_y;
            item.prev_direction = "down";
          end;
    }

message = {}
accumulator = { current = 0; limit= 0.08; }
  
  
  
	font = love.graphics.newFont(14)
  snake.data = {
      { x = 3; y = 3; _prev= nil; _next= nil; direction = "right"; prev_direction = "right"; },
      { x = 2; y = 3; _prev= 1; _next= nil; direction = "right"; prev_direction = "right"; },
      { x = 1; y = 3; _prev= 2; _next= nil; direction = "right"; prev_direction = "right"; }
  }
  snake.first = snake.data[1];
  snake.first._next = snake.data[2];
  snake.data[2].prev = snake.first;
  snake.data[3].prev = snake.data[2];
  snake.data[2]._next = snake.data[3];
  snake.moved_yet = false;
  
  spawn_enemy(snake.data,24,30);
  
end


function drawy(item)
      love.graphics.rectangle("fill", 1*item.x+30*item.x, 1*item.y+30*item.y, 30, 30, 8);
    
end

function reset_table()
    for i=0, stage.bound_x do
      for j=0, stage.bound_y do
        stage._map[i][j] = 0
      end 
    end
end

function calculate_map(stage)
  collided = false;
  for i = 1, table.getn(snake.data) do
      stage._map[snake.data[i].x][snake.data[i].y] = stage._map[snake.data[i].x][snake.data[i].y] +1;
      if stage._map[snake.data[i].x][snake.data[i].y] > 1 then
        collided = true;
      end 
  end
  return collided;
end 

function love.keypressed(key,scancode,isrepeat)
      if snake.moved_yet == false then 
      
      if scancode == "w" and snake.first.direction ~= "down" then 
        snake.first.direction = "up"; 
        snake.moved_yet = true;
      end 
      if scancode == "a" and snake.first.direction ~= "right" then
        snake.first.direction = "left";
        snake.moved_yet = true;
      end 
        if scancode == "s"  and snake.first.direction ~= "up" then
        snake.first.direction = "down";
        snake.moved_yet = true;
      end 
        if scancode == "d" and snake.first.direction ~= "left" then
        snake.first.direction = "right";
        snake.moved_yet = true;
      end
        
      end
      
      end

function love.update(dt)
    reset_table();
    --INITIATE MOVE
    accumulator.current = accumulator.current +dt;
    if accumulator.current >= accumulator.limit then
      accumulator.current = accumulator.current-accumulator.limit;
      for i=1,table.getn(snake.data) do
          snake.move[snake.data[i].direction](snake.data[i]);
      end
        update_directions(snake.first);
        snake.moved_yet = false;
    end
    
   
    if (snake.first.y == stage.enemy.y and snake.first.x+1 == stage.enemy.x and snake.first.direction == "right") or 
    (snake.first.y == stage.enemy.y and snake.first.x-1 == stage.enemy.x and snake.first.direction == "left") or 
    (snake.first.x == stage.enemy.x and snake.first.y+1 == stage.enemy.y and snake.first.direction == "down") or 
    (snake.first.x == stage.enemy.x and snake.first.y-1 == stage.enemy.y and snake.first.direction == "up") then
      stage.enemy.prev = nil; stage.enemy._next = snake.first;
      stage.enemy.direction = snake.first.direction;
      stage.enemy.prev_direction = snake.first.prev_direction;
      snake.first.prev = stage.enemy; snake.first =  stage.enemy;
      snake.data[table.getn(snake.data)+1] = snake.first;
      accumulator.limit = accumulator.limit * 0.91;
      spawn_enemy(snake.data,stage.bound_x,stage.bound_y)
   
    end
     value = calculate_map(stage)
     if value == true then
        love.load();
       end 
     
end
 
function update_directions(node)
    if node.prev ~= nil then
    node.direction = node.prev.prev_direction; 
    --node.direction = "up"
    end 
    
    if node._next ~= nil then
      update_directions(node._next)
    end 
end
 
function love.draw()
	love.graphics.setColor(30, 255, 255)
	
    for i = 1,table.getn(snake.data) do 
        drawy(snake.data[i]);
    end   
    --end
    

  
   love.graphics.setColor(90,10,250)
    drawy(stage.enemy);

	love.graphics.setColor(1,1,1)
	
end

function check_position_validity(x,y,items) 
      for i = 1, table.getn(items) do
          if items[i].x == x and items[i].y == y then
            return true;
            end 
        end
        return false;
    end 
    


function spawn_enemy(items,x,y)
    local lower_limit_x = 0;
    local lower_limit_y = 0;
    local upper_limit_x = x;
    local upper_limit_y = y;
      
      _x = math.random(lower_limit_x,upper_limit_x)
      _y = math.random(lower_limit_y,upper_limit_y)
      while check_position_validity(_x,_y,snake.data) do
         _x = math.random(lower_limit_x,upper_limit_x)
      end 
      
      stage.enemy = {
          x = _x; y = _y; _next = ""; prev = ""; direction = ""; prev_direction = "";
        }
    

  end 
  
  
mynameisnotpaul
Prole
Posts: 10
Joined: Sun Jan 01, 2017 4:25 am

Re: Problem with movement of snake in planned snake game

Post by mynameisnotpaul »

zorg wrote: Note that this will result in the snake's position having non-integer coordinates most of the time, but that can be solved with drawing it out floored in love.draw. (You can also floor it to a "bigger" grid, like, every 20 pixels or so: math.floor(n/20)*20 for example)
Hi, thanks for helping I fixed everything else you said but could you explain what you mean by the flooring part with examples of my code? It would probably make me understand much better.
s-ol wrote:
Sawamara wrote:...(lets give it a value of 1000, as in 1second), the movement is made, the accumulator value is set to 0. (Or, to be more precise: 0+ the amount of value it passed over 1000, so if it was 1010ms, then the I will start at 10ms.)
This is also what I would recommend, except you describe it in a bit complicated of a way - you are just talking about substraction. I usually do it like this:

Code: Select all

local time = 0

function love.update (dt)
  time = time + dt
  if time > time_to_do_stuff then
    -- do stuff
    time = time - time_to_do_stuff
  end
end
Also there is no reason to give it a value of 1000, dt in love is in seconds and float accuracy is enough. I'd just stick with that.
I tried implementing this code but it just results in my snake thing not moving at all. I changed the code a bit from the OP but I still don't know why it doesn't work. This is the code for reference:

Code: Select all

function love.load()
    direction = 0
    time = 0
    snakeHead = {
    x = 500,
    y = 200,
    grid_x = 520,
    grid_y = 220,
    speed = 0.1
    }

end


function love.update (dt)
  time = time + dt
  if time > snakeHead.speed then
    if direction == 1 then
      snakeHead.grid_y = snakeHead.grid_y - 20
    elseif direction == 2 then
      snakeHead.grid_x = snakeHead.grid_x + 20
    elseif direction == 3 then
      snakeHead.grid_y = snakeHead.grid_y + 20
    elseif direction == 4 then
      snakeHead.grid_x = snakeHead.grid_x - 20
    end
    time = 0
  end
end


function love.draw()
  love.graphics.setColor(255, 255, 255)
  love.graphics.rectangle('fill', snakeHead.x, snakeHead.y, 20, 20)
end


function love.keypressed(key)
  if key == ("up") then
    direction = 1
  elseif key == ("right") then
    direction = 2
  elseif key == ("down") then
    direction = 3
  elseif key == ("left") then
    direction = 4
  end
end
Thanks for the help, though!
User avatar
Ulydev
Party member
Posts: 445
Joined: Mon Nov 10, 2014 10:46 pm
Location: Paris
Contact:

Re: Problem with movement of snake in planned snake game

Post by Ulydev »

mynameisnotpaul wrote:Thanks for the help, though!
The code you provided doesn't update the snake's x and y coordinates, only grid_x and grid_y.
Try either updating x and y along with grid_x and grid_y or drawing the square at grid_x and grid_y instead:

Code: Select all

love.graphics.rectangle('fill', snakeHead.grid_x, snakeHead.grid_y, 20, 20)
also, is your name really paul?
mynameisnotpaul
Prole
Posts: 10
Joined: Sun Jan 01, 2017 4:25 am

Re: Problem with movement of snake in planned snake game

Post by mynameisnotpaul »

Ulydev wrote:
mynameisnotpaul wrote:Thanks for the help, though!
The code you provided doesn't update the snake's x and y coordinates, only grid_x and grid_y.
Try either updating x and y along with grid_x and grid_y or drawing the square at grid_x and grid_y instead:

Code: Select all

love.graphics.rectangle('fill', snakeHead.grid_x, snakeHead.grid_y, 20, 20)
also, is your name really paul?
Thanks that worked perfectly. And nah, my name is not, and has never been Paul.
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Problem with movement of snake in planned snake game

Post by zorg »

mynameisnotpaul wrote:
zorg wrote:Note that this will result in the snake's position having non-integer coordinates most of the time, but that can be solved with drawing it out floored in love.draw. (You can also floor it to a "bigger" grid, like, every 20 pixels or so: math.floor(n/20)*20 for example)
Hi, thanks for helping I fixed everything else you said but could you explain what you mean by the flooring part with examples of my code? It would probably make me understand much better.
A very brief example:

Code: Select all

local square = {} -- Our object
local grid = 10 -- pixels
local speed = 10 -- pixels/second

function love.load()
  -- Let's define it to start in the window's top-left corner with a width and height of 10 pixels.
  square.x = 0
  square.y = 0
  square.w = 10
  square.h = 10
end

function love.update(dt)
  -- Let's just have the square move downwards constantly
  -- This will make the y coordinate part have a fractional part, and is basically the "real" position of the object.
  square.y = square.y + speed * dt -- dt makes it framerate independent, so it will always go with a speed of 10 px/s
  
  -- Also let's wrap it around, if we want to treat the game area as a torus...
  if square.y >= love.graphics.getHeight() then
    square.y = square.y - love.graphics.getHeight() -- or just make it = 0, but this is more "correct".
  end
end

function love.draw()
  -- Here comes the trickery, we only want to draw the square on the grid in a way, that it snaps to the grid's size that we defined.
  local x,y = math.floor(square.x/grid)*grid, math.floor(square.y/grid)*grid -- We only modify y above, but this is useable for both axes.
  love.graphics.rectangle('fill',x,y,square.w,square.h)
end
Edit: See s-ol's post below for what i mean the last meaningful line in love.update being "more correct".
Last edited by zorg on Mon Jan 02, 2017 1:02 pm, edited 2 times in total.
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
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Problem with movement of snake in planned snake game

Post by s-ol »

mynameisnotpaul wrote:
s-ol wrote:
Sawamara wrote:...(lets give it a value of 1000, as in 1second), the movement is made, the accumulator value is set to 0. (Or, to be more precise: 0 + the amount of value it passed over 1000, so if it was 1010ms, then the I will start at 10ms.)
This is also what I would recommend, except you describe it in a bit complicated of a way - you are just talking about substraction. I usually do it like this:

Code: Select all

local time = 0

function love.update (dt)
  time = time + dt
  if time >= time_to_do_stuff then
    -- do stuff
    time = time - time_to_do_stuff
  end
end
I tried implementing this code but it just results in my snake thing not moving at all. I changed the code a bit from the OP but I still don't know why it doesn't work. This is the code for reference:

Code: Select all

...
function love.update (dt)
  time = time + dt
  ...
  if time > snakeHead.speed then
    time = 0
  end
end

You didn't implement what I mentioned in my comment by the way. It's not a big deal for a snake game, but it can already be a noticeable effect and make the game easier to play for some players than for others, depending on hardware.

For example, imagine someone is playing at a constant 60 fps. That means dt is 1/60, and so after 6 frames "time" is at 0.1, but nothing happens yet, and then at 7 frames you reset the timer to 0 and do something.
So for one, your time is actually inaccurate, with a 'speed' of 0.1 and 60fps it takes not 6/60 = 0.10 but 7/60 = 0.1166s per loop.

That's already a lot of drift, but it's not that bad since if you designed the game and tested it, it doesn't really matter if when you mean 0.1 you actuall get 0.116 seconds, as long as it's consistent. But it's not:

Let's do the same example for someone who plays at 50fps:
1/50 * 5 = 0.1
but because you used a > instead of a >= it will loop only at 6/50 again, which is 1.2s, again different.

If you change it and add the >= these two examples will behave the same, but for framerates that don't evenly multiply up to your time of 0.1, the "time = 0" approach is going to be inaccurate anyway, and if dt is changing all the time aswell. For example with 45fps:

4/45 = 0.088 (< 0.1)
5/45 = 0.111 (< 0.1)

now if you subtract 0.1 from the "time" variable, the next iteration will have exactly 0.1 seconds to run, but with the time = 0, you are forgetting the fact that 0.011 seconds have passed already.

All these rather small numbers don't look like much of a difference, but your game is already harder at some speeds than on others, and if you write code like this it probably will be extremely hard to build something multiplayer.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 8 guests