Page 1 of 2
Problem with movement of snake in planned snake game
Posted: Sun Jan 01, 2017 4:39 am
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!
Re: Problem with movement of snake in planned snake game
Posted: Sun Jan 01, 2017 10:59 am
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.
Re: Problem with movement of snake in planned snake game
Posted: Sun Jan 01, 2017 2:47 pm
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.
Re: Problem with movement of snake in planned snake game
Posted: Sun Jan 01, 2017 7:45 pm
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.
Re: Problem with movement of snake in planned snake game
Posted: Sun Jan 01, 2017 10:53 pm
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
Re: Problem with movement of snake in planned snake game
Posted: Mon Jan 02, 2017 9:20 am
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!
Re: Problem with movement of snake in planned snake game
Posted: Mon Jan 02, 2017 10:24 am
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?
Re: Problem with movement of snake in planned snake game
Posted: Mon Jan 02, 2017 10:47 am
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.
Re: Problem with movement of snake in planned snake game
Posted: Mon Jan 02, 2017 11:53 am
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".
Re: Problem with movement of snake in planned snake game
Posted: Mon Jan 02, 2017 12:39 pm
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.