Template for using Löve as if it were not event-oriented

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
pgimeno
Party member
Posts: 1870
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Template for using Löve as if it were not event-oriented

Post by pgimeno » Sat Sep 21, 2019 10:11 am

Inspired by this question from forum user neku: https://love2d.org/forums/viewtopic.php ... 84#p220884 I've written a template that allows using Löve for, e.g., visualizing algorithms without needing to interrupt them. It is based on hump.timer's timer.script function, which internally uses a coroutine. Grab the timer module from the hump suite here: https://github.com/vrld/hump

The program will run without displaying anything until you use the sleep() global function. If you want to update the display without pausing, use sleep(0).

For best results, it's better to disable vsync in conf.lua, so you can use this as conf.lua:

Code: Select all

-- Disable vsync
function love.conf(c)
  c.window.vsync = false
end
otherwise sleep(0) will take about 0.01666 seconds (or whatever your frame rate is).

The code to run must be placed in a file called yourcode.lua. The template goes in main.lua. Here's the template code:

Code: Select all

local timer = require 'hump.timer'()
local canvas
local script
local started = false
local cont_key

function love.load()
  script = love.filesystem.load('yourcode.lua')
  canvas = love.graphics.newCanvas()
  love.graphics.setCanvas(canvas)
  love.graphics.clear(0, 0, 0, 255)
  love.graphics.setCanvas()
  love.graphics.push()
end

local huge = math.huge

function waitkey(cont_key_param)
  cont_key = cont_key_param or true
  sleep(huge)
  cont_key = false
end

local function resume()
  -- Hack - hump.timer does not expose its resume function, so we
  -- find the time limit in its internal structure and set it to 0
  -- to force the 'after' event to immediately trigger.
  local tmrfunc = next(timer.functions)
  if tmrfunc then
    tmrfunc.limit = 0
  end
end

local function timer_script_start(wait_func)
  sleep = wait_func
  script()
end

function love.update(dt)
  love.graphics.setCanvas(canvas)
  love.graphics.pop()
  timer:update(dt)
  if not started then
    started = true
    timer:script(timer_script_start)
  end
  love.graphics.push()
  love.graphics.setCanvas()
end

function love.draw()
  local r,g,b,a = love.graphics.getColor()
  local rgbmode, alphamode = love.graphics.getBlendMode()
  love.graphics.setColor(255, 255, 255, 255)
  love.graphics.setBlendMode("replace", "premultiplied")
  love.graphics.draw(canvas)
  love.graphics.setBlendMode(rgbmode, alphamode)
  love.graphics.setColor(r, g, b, a)
end

function love.quit()
  love.graphics.pop()
end

function love.keypressed(k)
  if k == "escape" then
    return love.event.quit()
  end
  if cont_key == true or k == cont_key then
    resume()
  end
end

function love.mousepressed(x, y, btn)
  if cont_key == true or btn == cont_key then
    resume()
  end
end
This makes your code work in a similar fashion to that of PyGame and similar. It uses 255 for colour values because that way, it is compatible with all versions of LÖVE starting with 0.9, without losing compatibility with 11.0+.

As an example, here's a straightforward implementation of an Eight Queens Problem solver that uses it. Save it as yourcode.lua. It shows progress as it goes, and pauses at every solution found, while printing it to standard output:

Code: Select all

-- Eight queens with progress

local size = 64
local img = love.graphics.newImage('queen-black-64x64.png')

local function clear(x, y)
  if (x + y) % 2 == 1 then
    love.graphics.setColor(0, 0, 0)
  else
    love.graphics.setColor(255, 255, 255)
  end
  love.graphics.rectangle("fill", x*size, y*size, size, size)
  love.graphics.setColor(255, 255, 255)
end

love.graphics.translate(love.graphics.getWidth()/2 - size*4,
                        love.graphics.getHeight()/2 - size*4)

love.graphics.rectangle("fill", -2, -2, size*8+4, size*8+4)
for y = 0, 7 do
  for x = 0, 7 do
    clear(x, y)
  end
end

local abs = math.abs

for q0 = 0, 7 do
  love.graphics.draw(img, q0*size, 0*size)
  -- Force display
  sleep(0)
  for q1 = 0, 7 do
    if q1 ~= q0 and abs(q1 - q0) ~= 1 then
      love.graphics.draw(img, q1*size, 1*size)
      sleep(0)
      for q2 = 0, 7 do
        if q2 ~= q0 and q2 ~= q1 and abs(q2 - q0) ~= 2 and abs(q2 - q1) ~= 1 then
          love.graphics.draw(img, q2*size, 2*size)
          sleep(0)
          for q3 = 0, 7 do
            if q3 ~= q0 and q3 ~= q1 and q3 ~= q2
               and abs(q3 - q0) ~= 3 and abs(q3 - q1) ~= 2 and abs(q3 - q2) ~= 1
            then
              love.graphics.draw(img, q3*size, 3*size)
              sleep(0)
              for q4 = 0, 7 do
                if q4 ~= q0 and q4 ~= q1 and q4 ~= q2 and q4 ~= q3
                   and abs(q4 - q0) ~= 4 and abs(q4 - q1) ~= 3
                   and abs(q4 - q2) ~= 2 and abs(q4 - q3) ~= 1
                then
                  love.graphics.draw(img, q4*size, 4*size)
                  sleep(0)
                  for q5 = 0, 7 do
                    if q5 ~= q0 and q5 ~= q1 and q5 ~= q2 and q5 ~= q3
                       and q5 ~= q4
                       and abs(q5 - q0) ~= 5 and abs(q5 - q1) ~= 4
                       and abs(q5 - q2) ~= 3 and abs(q5 - q3) ~= 2
                       and abs(q5 - q4) ~= 1
                    then
                      love.graphics.draw(img, q5*size, 5*size)
                      sleep(0)
                      for q6 = 0, 7 do
                        if q6 ~= q0 and q6 ~= q1 and q6 ~= q2 and q6 ~= q3
                           and q6 ~= q4 and q6 ~= q5
                           and abs(q6 - q0) ~= 6 and abs(q6 - q1) ~= 5
                           and abs(q6 - q2) ~= 4 and abs(q6 - q3) ~= 3
                           and abs(q6 - q4) ~= 2 and abs(q6 - q5) ~= 1
                        then
                          love.graphics.draw(img, q6*size, 6*size)
                          sleep(0)
                          for q7 = 0, 7 do
                            if q7 ~= q0 and q7 ~= q1 and q7 ~= q2 and q7 ~= q3
                               and q7 ~= q4 and q7 ~= q5 and q7 ~= q6
                               and abs(q7 - q0) ~= 7 and abs(q7 - q1) ~= 6
                               and abs(q7 - q2) ~= 5 and abs(q7 - q3) ~= 4
                               and abs(q7 - q4) ~= 3 and abs(q7 - q5) ~= 2
                               and abs(q7 - q6) ~= 1
                            then
                              love.graphics.draw(img, q7*size, 7*size)
                              print("--------")
                              print(string.rep(" ", q0) .. "Q")
                              print(string.rep(" ", q1) .. "Q")
                              print(string.rep(" ", q2) .. "Q")
                              print(string.rep(" ", q3) .. "Q")
                              print(string.rep(" ", q4) .. "Q")
                              print(string.rep(" ", q5) .. "Q")
                              print(string.rep(" ", q6) .. "Q")
                              print(string.rep(" ", q7) .. "Q")
                              sleep(0.5)
                              clear(q7, 7)
                            end
                          end
                          clear(q6, 6)
                        end
                      end
                      clear(q5, 5)
                    end
                  end
                  clear(q4, 4)
                end
              end
              clear(q3, 3)
            end
          end
          clear(q2, 2)
        end
      end
      clear(q1, 1)
    end
  end
  clear(q0, 0)
end

love.event.quit()
EDIT: I forgot to specify a license! The code and image in this post (except hump of course, which is not mine) are donated to the public domain. The image was created by me.

At the bottom of the post is the image used by the program (see next post for a full .love file).

Update 30 Oct 2019: Moved loading of script to love.load(). Added waitkey() function; usage: waitkey() to wait for any key, or waitkey(key) to wait for a specific key (see KeyConstant enum in the wiki for valid keys you can pass to it).

Update 02 Nov 2019: waitkey() can now also wait for a mouse button, therefore it should be usable on mobile too. Use numeric values (button numbers) to wait for specific buttons, or waitkey() to wait for any key or mouse button.
Attachments
queen-black-64x64.png
queen-black-64x64.png (3.46 KiB) Viewed 1671 times
Last edited by pgimeno on Fri Nov 01, 2019 11:24 pm, edited 7 times in total.

User avatar
pgimeno
Party member
Posts: 1870
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Template for using Löve as if it were not event-oriented

Post by pgimeno » Sat Sep 21, 2019 10:12 am

Here's a complete .love file with the 8 queens demonstration (sorry for the double post, the forum has problems when trying to attach more than one file):
Attachments
8queens.love
(8.32 KiB) Downloaded 54 times

User avatar
ivan
Party member
Posts: 1520
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Template for using Löve as if it were not event-oriented

Post by ivan » Sat Sep 21, 2019 11:03 am

Pretty cool. I have though about doing algorithm visualizations but it's hard to generalize that sort of thing and there's not much use for it apart from looking cool.
Nice result although the nested "for" loops are atrocious. :)

neku
Prole
Posts: 11
Joined: Thu May 17, 2018 3:07 pm

Re: Template for using Löve as if it were not event-oriented

Post by neku » Sat Sep 21, 2019 8:18 pm

That's great!
and atrocious nested "for" loops?
well - who can do it better?
;)
Thank you very much.

User avatar
Roland Chastain
Prole
Posts: 29
Joined: Sat Feb 07, 2015 2:30 pm
Location: France
Contact:

Re: Template for using Löve as if it were not event-oriented

Post by Roland Chastain » Mon Sep 23, 2019 7:04 am

Very interesting. Thank you for sharing.

neku
Prole
Posts: 11
Joined: Thu May 17, 2018 3:07 pm

Re: Template for using Löve as if it were not event-oriented

Post by neku » Thu Sep 26, 2019 1:50 pm

It's any time a go..
after I have (due to carelessness in the zerobrane) overwrited an already functioning version of the "knigt's tour" (with some test) I was so frustrated that I switched to corona sdk. well, the other (maybe the first) reason was "delay". I managed not (even with HUMP not) to slow down the jump animation.
I succeeded in corona (and knigt's tour) quickly so I stayed with corona. in my next project (maze generator & solver) I walked (at the way back from end to start) in a problem and sought help. I found that by pedro gimeno: technically competent and human a gigant. for me a not everyday experience.
Thanks a lot Pedro.

maze main-code:

Code: Select all

-------------------------------------------------------------------------
-- main.lua  	v.5f	(after: pgimeno)
-------------------------------------------------------------------------
--[[
 concept of:
 Python maze generator program (using PyGame for animation)
 Davis MT
 Python 3.4
 10.02.2018
 video with explanations of the original py program:
 https://www.youtube.com/watch?v=Xthh4SEMA2o
 ]]
-- ----------------------------------------------------------------------

this can be combined with the program "queens" mentioned above (by pgimeno).
(I have to search first how I can attach the zip ..)
-- globals:
w = 17					-- field side (17 is ok for: iPhone5 640 x 1136
						-- others are questionable in corona sdk).
x, y = w, w				-- grid startposition

-- tables:
mgrid 		= {}
--visited	= {}		-- in carve
--stack		= {}		-- in carve
solution 	= {} 		-- as dictionary

-- *****************  helper functions:  *****************

-- if 2D-table contains Value		-- important
local function contains( table, x, y )
   for i=1,#table do
      if table[i][1] == x and table[i][2] == y then
         return true
      end
   end
   return false
end
 
-- randomizer kick on for maze construction & way back
math.randomseed(os.time())

-- ****************** -- pgm functions: *********************

-- build the maze matrix
local function build_grid( x, y, w )	
	love.graphics.setLineStyle("rough")
	for i = 1, w do             -- col
		x=w                     -- set koord. to start pos.
		y=y+w         		   	-- start a new row
		for j = 1, w do         -- row 
			love.graphics.setColor(255, 255, 255)
			love.graphics.rectangle("line", x+0.5, y+0.5, w, w)			
			table.insert( mgrid, {x, y} ) 	--append cell to grid: OK
			x = x+w							-- move to new position
		end
	end
end --func

-- ********************  pgm grafics: *********************

local function newRect(x, y, w, h)
  love.graphics.rectangle("fill", x, y, w, h)
end

function push_up(x, y)		--draw a rectangle twice the width of the cell
	love.graphics.setColor( 0, 0, 255 ) 		-- blau  / blue
	newRect( x+1, y-w+1, w-1, 2*w-1 )
end
--
function push_down(x, y) 
	love.graphics.setColor( 0, 0, 255 ) 		--blau
	newRect( x+1, y+1, w-1, 2*w-1 )  
end
--
function push_left(x, y)	
	love.graphics.setColor( 0, 0, 255 ) 		--blau
	newRect( x-w+1, y+1, 2*w-1, w-1 )
end
--
function push_right(x, y)
	love.graphics.setColor( 0, 0, 255 ) 		--blau
	newRect( x+1, y+1, 2*w-1, w-1 )
end
--
function single_cell(x, y)		-- draw a single width cell  
	love.graphics.setColor( 0, 255, 0 ) 		--gruen / green
	love.graphics.rectangle("fill", x+2, y+2, w-3, w-3 )
end
--
function backtracking_cell(x, y)	--used to re-colour the path after 
	love.graphics.setColor( 0, 0, 255 ) --blau single_cell has visited cell
	newRect( x+2, y+2, w-3, w-3 )
end
--
function solution_cell(x, y)		-- used to show the solution
	love.graphics.setColor(255, 255, 0) 	-- gelb / yellow
	newRect( x+w/2-1, y+w/2-1, 3, 3 )
end
-- ---------------------------------------------------------------
-- 	      **************  main function:  **************
-- ---------------------------------------------------------------
function carve_out_maze(x, y, w)
	visited		= {}
	stack		= {}
	single_cell(x, y)				-- starting positing of maze
	-- append stack & visited (at end of table) 
	table.insert(stack, {x, y})		-- place starting cell into stack   
	table.insert(visited, {x, y})	-- add starting cell to visited list
	while #stack > 0 do				-- loop until stack is empty (done)
		cell = {} 					-- create new empty table 
		-- check availability - Right,Left,Down,Up
		-- right cell available?  (NOT in visited BUT in grid)		
		if not contains(visited, x+w, y) and contains(mgrid, x+w, y) then
			table.insert( cell, "right" )	--if yes add to cell list
		end
		if not contains(visited, x-w, y) and contains(mgrid, x-w, y) then
			table.insert( cell, "left" )
		end
		if not contains(visited, x, y+w) and contains(mgrid, x, y+w) then
			table.insert( cell, "down" )
		end
		if not contains(visited, x, y-w) and contains(mgrid, x, y-w) then
			table.insert( cell, "up" )
		end		

			if #cell >0 then					--check if cell list is empty
				math.random(); math.random()	-- really necessary?
				cell_chosen = cell[math.random(1, #cell)] -- chose random cell-item			
				-- cell_chosen:
				if cell_chosen == "right" then	-- if cell is has been chosen
					push_right(x, y)			-- cell push_right function					
					solution[x+w..","..y] = {x,y}					
					x = x+w						-- make this cell the current cell
					table.insert(visited, {x, y} )
					table.insert(stack, {x, y} )
				elseif cell_chosen == "left" then		
					push_left(x, y)	
					solution[x-w..","..y] = {x,y}
					x = x-w
					table.insert(visited, {x, y} )	-- add to visited list
					table.insert(stack, {x, y} )	-- place current on to stack
				elseif cell_chosen == "down" then
					push_down(x, y)	
					solution[x..","..y+w] = {x,y}
					y = y+w
					table.insert(visited, {x, y} )
					table.insert(stack, {x, y} )				
				elseif cell_chosen == "up" then
					push_up(x, y)
					solution[x..","..y-w] = {x,y}
					y = y-w
					table.insert(visited, {x, y} )
					table.insert(stack, {x, y} )
				end	--end-if
				sleep(0.1)						-- pause
			else		
				-- if no cells are available pop one from the stack
				x, y = stack[#stack][1], stack[#stack][2]
				table.remove(stack)				-- stack pop (the last)
				-- use single_cell func. to show backtracking image
				single_cell(x, y)	
				sleep(0.1)						-- pause
				-- change colour to identify the backtracking path
				backtracking_cell(x, y)	
			end --if #cell
	end --while
end --carve_out_maze

-- visualize the way back to the start point  
function plot_route_back(x, y)	
	-- start b. retour: x: 289  y: 289 (ziel: 17 / 17
	solution_cell( x, y )		--[[ solution list contains all the  
									 coordinates to route back to start.]]		
	while x ~= w or y ~= w do	-- loop until cell pos == start pos. better but??
		-- "keys" now becomes the new values	 
		local xy = x..","..y	-- important!
		x = solution[xy][1]		-- because otherwise error:
		y = solution[xy][2]		-- <--<<<   nil value   
		
		solution_cell(x, y)				-- animate route back (grafics)
		table.remove(solution)			-- pop the last value
		sleep(0.1)						-- pause
	end --while
end --func plot_route_back

-- 		***********************  main:  ************************

-- 1st argument = x value, 2nd arg. = y value, 3rd arg. = width of cell
build_grid(2*w, 0, w)		-- build labyrinth
carve_out_maze(x, y, w)		-- call build the maze  function
plot_route_back(w*w, w*w)  	-- call the plot solution function

-- 	  ***********************  the end   ************************


neku
Prole
Posts: 11
Joined: Thu May 17, 2018 3:07 pm

Re: Template for using Löve as if it were not event-oriented

Post by neku » Thu Sep 26, 2019 2:03 pm

Here's a complete zip-file with the maze (generator&solver) project:
Attachments
NestorMazeSolverPGim.zip
love2d project maze: generator&solver
(6.02 KiB) Downloaded 35 times
Dank an Pedro-2-18_09.png
way back to the start.
Dank an Pedro-2-18_09.png (4.1 KiB) Viewed 1326 times

User avatar
YoungNeer
Party member
Posts: 105
Joined: Wed May 15, 2019 7:49 am

Re: Template for using Löve as if it were not event-oriented

Post by YoungNeer » Fri Sep 27, 2019 4:06 pm

pgimeno wrote:
Sat Sep 21, 2019 10:12 am
(sorry for the double post, the forum has problems when trying to attach more than one file):
Is that your secret? Like how you made those "unbelievable" 1700+ posts! :rofl:
My Github- your contribution is highly appreciated

User avatar
ingsoc451
Citizen
Posts: 87
Joined: Sat Feb 06, 2016 9:42 pm
Location: Oceania

Re: Template for using Löve as if it were not event-oriented

Post by ingsoc451 » Fri Sep 27, 2019 5:05 pm

From the image, it looks like it also optimizes the path
eom

neku
Prole
Posts: 11
Joined: Thu May 17, 2018 3:07 pm

Re: Template for using Löve as if it were not event-oriented

Post by neku » Fri Sep 27, 2019 8:05 pm

ingsoc451 wrote:
Fri Sep 27, 2019 5:05 pm
From the image, it looks like it also optimizes the path
maybe I'm not right, but I do not see any other way there. only this one.
look at the code. :monocle:

Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests