Implementing turn-based time system

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
User avatar
norubal
Party member
Posts: 137
Joined: Tue Jan 15, 2013 5:55 am

Implementing turn-based time system

Post by norubal »

First, I apologize for another "I can't make this" question.
Currently I'm trying to implementing turn-based time system.
.love file is what I made - you can move player and enemies will move towards player using Jumper library.
turnsystem.png
turnsystem.png (11.11 KiB) Viewed 4217 times
turnsystem.love
(32.08 KiB) Downloaded 132 times
But this is my problem: I want to apply different "speed" for each entities.
Example: movedelay for "player" entity is 100 units, and "snake" entity is 80 units.
Let's assume one "turn" is 100 units - so in 4 turns, player entity can move 4 times and snake entity can move 5 times.

http://www.gamesetwatch.com/2008/08/col ... ion_on.php
https://journal.stuffwithstuff.com/2014 ... game-loop/
I checked links above, and they are using one big main while() loop - but I'm not sure where should I place it.
If anyone can give me any advice, it would be really appreciated.
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Implementing turn-based time system

Post by pgimeno »

norubal wrote: Tue Nov 17, 2020 9:09 amLet's assume one "turn" is 100 units - so in 4 turns, player entity can move 4 times and snake entity can move 5 times.
That means that in some turns, the snake will move once, and in some other turns, the snake will move twice. Is that what you mean?

If so, there are two ways of implementing it. One is using decimals, the other is using integers. The decimals way is simpler but it is prone to rounding errors.

With decimals, the snake moves 5/4 or 1.25 times the speed of the player. So, you have a variable, steps, and you add 1.25 to it at every step. You then compare the difference in the integral part before and after incrementing, and that's the number of movements that the snake can take:

Code: Select all

local snakeSteps = 0
local snakeRatio = 5/4

function love.keypressed(k)
  local moveX, moveY = 0, 0
  if k == "left" then moveX = -1 end
  if k == "right" then moveX = 1 end
  if k == "up" then moveY = -1 end
  if k == "down" then moveY = 1 end
  if moveX ~= 0 or moveY ~= 0 then
    -- Player takes turn and moves once
    movePlayer(moveX, moveY)

    -- Snake's turn
    local oldSnakeSteps = math.floor(snakeSteps)
    snakeSteps = snakeSteps + snakeRatio
    -- Move the snake as many times as the difference indicates
    for i = 1, snakeSteps - oldSnakeSteps do
      moveSnake()
    end
  end
end
This will work well in this case, because the divisor of the fraction (4) is a power of 2, and the floating point numbers use base 2. But it can result in rounding errors otherwise (but very rarely because the precision is high). However, it can be done with integers too and avoid rounding errors:

Code: Select all

local snakeSteps = 0
local snakeNum = 5  -- numerator of fraction indicating ratio
local snakeDen = 4  -- denominator of fraction indicating ratio

function love.keypressed(k)
  local moveX, moveY = 0, 0
  if k == "left" then moveX = -1 end
  if k == "right" then moveX = 1 end
  if k == "up" then moveY = -1 end
  if k == "down" then moveY = 1 end
  if moveX ~= 0 or moveY ~= 0 then
    -- Player takes turn and moves once
    movePlayer(moveX, moveY)

    -- Snake's turn
    snakeSteps = snakeSteps + snakeNum
    while snakeSteps >= snakeDen do
      moveSnake()
      snakeSteps = snakeSteps - snakeDen
    end
  end
end
I haven't checked your code, so you will have to adapt the above idea to your actual code.

norubal wrote: Tue Nov 17, 2020 9:09 amI checked links above, and they are using one big main while() loop - but I'm not sure where should I place it.
No, forget about that. The main loop is already created in Löve and I don't recommend fiddling with it.
User avatar
norubal
Party member
Posts: 137
Joined: Tue Jan 15, 2013 5:55 am

Re: Implementing turn-based time system

Post by norubal »

Thanks pgimeno , I checked https://ironypolicy.wordpress.com/2014/ ... oguelikes/ and solved it:

First, I added "energy" and "energygen" field for each entities, and changed "movedelay" to "movecost".
"energy" represents how much energy entity has, "energygen" represents how much energy will generated by each turn, and "movecost" is how much energy needed to move.
"energygen" is not shared among entities because if entity move faster or slower somehow(like "haste" or "slow" effect), modifying energygen value will be easier than modifying every movecost/attackcost/castcost...(if I add those later.)

Before:

Code: Select all

entities = {
	{type = "player", x = 2, y = 2, movedelay = 100}, -- player is always entity #1
	{type = "goblin", x = 14, y = 9, movedelay = 100},
	{type = "snake", x = 2, y = 9, movedelay = 80},
	{type = "troll", x = 14, y = 2, movedelay = 120}
	}
After:

Code: Select all

entities = {
	{type = "player", x = 2, y = 2, energy = 0, energygen = 100, movecost = 100}, -- player is always entity #1
	{type = "goblin", x = 14, y = 9, energy = 0, energygen = 100, movecost = 100},
	{type = "snake", x = 2, y = 9, energy = 0, energygen = 100, movecost = 80},
	{type = "troll", x = 14, y = 2, energy = 0, energygen = 100, movecost = 120}
	}
Then, I fixed love.keypressed callback function - if player has less than 0 energy, turn(s) will be processed until player regenerate enough amount of energy to act.
Before:

Code: Select all

function love.keypressed(key)
	local playertookaction = false
	-- h(←) j(↓) k(↑) l(→) y(↖) u(↗) b(↙) n(↘) p(perform pathfinding to mouse cursor)
	if key == "h" or key == "left" then -- move left
		playertookaction = move(player(), -1, 0)
	elseif key == "j" or key == "down" then -- move down
		playertookaction = move(player(), 0, 1)
	elseif key == "k" or key == "up" then -- move up
		playertookaction = move(player(), 0, -1)
	elseif key == "l" or key == "right" then -- move right
		playertookaction = move(player(), 1, 0)
	elseif key == "y" then -- move left-up
		playertookaction = move(player(), -1, -1)
	elseif key == "u" then -- move right-up
		playertookaction = move(player(), 1, -1)
	elseif key == "b" then -- move left-down
		playertookaction = move(player(), -1, 1)
	elseif key == "n" then -- move right-down
		playertookaction = move(player(), 1, 1)
	elseif key == "." then
		playertookaction = true
	elseif key == "r" then
		reset()
	elseif key == "p" then -- perform pathfinding to mouse pointer
		
	end
	
	if playertookaction == true then
		tick()
	end
end
After:

Code: Select all

function love.keypressed(key)
	local playertookaction = false
	-- h(←) j(↓) k(↑) l(→) y(↖) u(↗) b(↙) n(↘) p(perform pathfinding to mouse cursor)
	if key == "h" or key == "left" then -- move left
		playertookaction = move(player(), -1, 0)
	elseif key == "j" or key == "down" then -- move down
		playertookaction = move(player(), 0, 1)
	elseif key == "k" or key == "up" then -- move up
		playertookaction = move(player(), 0, -1)
	elseif key == "l" or key == "right" then -- move right
		playertookaction = move(player(), 1, 0)
	elseif key == "y" then -- move left-up
		playertookaction = move(player(), -1, -1)
	elseif key == "u" then -- move right-up
		playertookaction = move(player(), 1, -1)
	elseif key == "b" then -- move left-down
		playertookaction = move(player(), -1, 1)
	elseif key == "n" then -- move right-down
		playertookaction = move(player(), 1, 1)
	elseif key == "." then
		playertookaction = true
	elseif key == "r" then
		reset()
	elseif key == "p" then -- perform pathfinding to mouse pointer
		
	end
	
	if playertookaction == true then
		player().energy = player().energy - player().movecost
		while player().energy < 0 do
			player().energy = player().energy + player().energygen
			tick()
		end
	end
end
Finally, I added while loop in tick() function - if entity has enough energy to move, move entity while it has enough energy to move.

Before:

Code: Select all

function tick()
	-- process turn and move another monsters
	turn = turn + 1
	
	-- make non-player creatures move
	local k, v, path
	for k, v in pairs(entities) do
		if v.type ~= "player" then
			-- perform pathfinding and track player
			path = pathfind(v, player())
			if path[1] ~= nil then
				move(v, path[1].x, path[1].y)
			end
		end
	end	
end
After:

Code: Select all

function tick()
	-- process turn and move another monsters
	turn = turn + 1
	
	-- make non-player creatures move
	local k, v, path
	local creature_moved
	for k, v in pairs(entities) do
		if v.type ~= "player" then
			-- perform pathfinding and track player
			v.energy = v.energy + v.energygen
			while(v.energy >= v.movecost) do
				path = pathfind(v, player())
				if path[1] ~= nil then
					move(v, path[1].x, path[1].y)
				end
				v.energy = v.energy - v.movecost
			end
		end
	end	
end
Final product is working well(at least I think). I attach modified .love file here.
turnsystem_solved.love
(32.21 KiB) Downloaded 124 times
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Implementing turn-based time system

Post by pgimeno »

Great that you solved it. That actually follows the exact same scheme as the integer solution I posted, where snakeSteps is v.energy, snakeNum is v.energygen and snakeDen is v.movecost.
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 3 guests