Write love2d programs as if love was not event-oriented - Useful e.g. for algorithm visualization.

Pedro Gimeno 9d078dbe39 Adjustments to README 9 months ago
.gitignore 2c0a8a701e Add .gitignore 10 months ago
8queens.lua 4082723450 Almos rewritten from scratch; lots of new features, no HUMP 10 months ago
Nqueens.lua 4082723450 Almos rewritten from scratch; lots of new features, no HUMP 10 months ago
README.md 9d078dbe39 Adjustments to README 9 months ago
conf.lua 4082723450 Almos rewritten from scratch; lots of new features, no HUMP 10 months ago
main.lua ee59a5e180 Save/restore canvas properly in love.draw() 10 months ago
queen-black-40x40.png 4082723450 Almos rewritten from scratch; lots of new features, no HUMP 10 months ago
queen-black-64x64.png 23ff1c2108 Add example 8queens.lua 10 months ago


Algorithm Visualizer


This is a Löve program that allows you to use the Löve framework, but instead of using event callbacks, you poll the events via function calls.

It requires a love2d version from 0.9 up.

It was inspired by forum member Neku, who was used to PyGame and had trouble with event-oriented programming when applied to the visualization of the algorithms he wrote for solving certain tasks.

And that's indeed the main application of this library: algorithm visualization. Even if you're deep in nested loops or recursion levels, calling sleep() will update your display thanks to the magic of coroutines.

A lot of inspiration for this version also came from the TimelineEvents library by Babulous, regarding the design and implementation of the whole event set instead of just a very limited set of functions and events.


Just place your code in the file mycode.lua. Alternatively, you can call love2d with the name of the Lua file to run; for example, invoking love . 8queens.lua from the directory where this repository is, will execute 8queens.lua.

A window is NOT automatically created; your code needs to call love.graphics.setMode first before calling any window functions.

Most Löve functions can be called directly. Note that there's an underlying canvas used as the framebuffer in which your graphics are rendered, therefore using love.graphics.setCanvas() may result in unintended consequences. This framebuffer canvas is available via a global variable called fbcanvas.

NOTE: Functions love.graphics.setMode and love.event.pump have been patched to work properly with this canvas, but other functions may not work as intended; let me know in case of problems so I can add a workaround.

NOTE: Redefining love2d events can have unintended consequences.


In addition to Löve functions, there are several additional global functions:

  • sleep(t): Stops execution of your script during the given amount of time. Any events that happened during the sleep period are lost.
  • pause(t): Similar to sleep, but if the user presses any key or a mouse button, it will return before the time expires, and if the user presses ESC, the program will be immediately closed.
  • wait.<eventname>(): This is actually a table with functions. For each Löve event, there is a corresponding function in this table. The function will stop execution of your script until the corresponding event is triggered, and then it will return whatever the arguments were for that event. These functions don't take any arguments.
    • For example, wait.keypressed() will wait until the keypressed event is triggered, and will return the key, scancode and isRepeat values that are the arguments of love.keypressed.
  • peek: Another table with functions. Similar to wait but this one will return nothing instead of blocking your script, and will return the boolean true followed by the event's arguments if the current event is that one. Note, however, that calling a peek function will not advance to the next event; you need to call a function that does so yourself. For example, if you call peek. inside a loop without calling an event advancing function such as poll() or wait.<something>(), and your loop depends on some event in order to be interrupted, your program will hang indefinitely.
  • In addition to the functions with the same name as the Löve events, the tables wait and peek contain another function called event. You can pass the name of an event to this function, and it will do the same as the function with the name of that event. Internally, every other function in the table calls this function. For example, wait.keypressed() and wait.event("keypressed") do the same. Note that the functions wait.event() and peek.event() should not be confused with the global function event().
  • poll() advances to the next event.
  • event() is like poll() but it also returns the event name and its parameters. For example, if the next event to trigger is keypressed, and you call event(), it will return a tuple of the form "keypressed", key, scancode, isRepeat.
  • timer(seconds) allows you to set a timer that will expire after the given number of seconds have passed (with 1 frame granularity) and generate the timer event.
  • quit() immediately quits the program. It's a shortcut for love.event.quit(); wait.quit(); poll().

In addition to the normal Löve events, there are three more events:

  • newframe - Triggered at the start of a new frame. No parameters.
  • timer - Triggered when timer() expires. No parameters.
  • sleep - Generated internally by the sleep() call.

Advanced usage

The functions that advance events all accept return parameters for the current event. The actual signatures are:

  • void sleep(t, ...) [*]
  • void pause(t, ...) [*]
  • void poll(...)
  • tuple event(...)
  • void wait.<eventname>(...) [*]
  • void wait.event(eventname, ...) [*]

The functions marked with [*] may skip more than one event. In that case, the return value only applies to the first event.

Note that as of this writing, only the quit event handles a return value.

The draw event is called after drawing the framebuffer, but before the frame is displayed. This allows you to draw to the screen directly rather to the framebuffer, as long as you unset the canvas first with love.graphics.setCanvas(). Remember to restore the framebuffer if necessary by calling love.graphics.setCanvas(fbcanvas).

Here's one example of the use of the draw event:

love.window.setMode(800, 600)
love.graphics.print("** Background", 0, 8) -- drawn to the framebuffer
  if peek.draw() then
    -- We're in the draw event
    love.graphics.setCanvas() -- Unset the canvas
    -- This text overlaps the background temporarily:
    love.graphics.print("I appear for 3 seconds", 30, 0)
    love.graphics.setCanvas(fbcanvas) -- Restore the framebuffer
until peek.timer() -- keep looping until the timer expires
love.graphics.print("Done", 0, 40)  -- drawn to the framebuffer again


Simple example:

print("We're at the top level")
wait.load()  -- Wait for love.load() to trigger
print("Now we're inside love.load")
local dt = wait.update()  -- Wait for love.update() to trigger
print("Now we're inside love.update(), dt is " .. dt)
love.event.quit()  -- Send quit event
wait.quit()  -- Wait for love.quit() to trigger
print("Now we're inside love.quit(), but we don't want to quit")
poll(true)  -- Return true to love.quit, thus blocking the quit
print("See? We're still here");
love.event.quit()  -- Send quit event again
wait.quit()  -- Wait for love.quit() once more
print("Now we're inside love.quit() again, but this time we're quitting")
poll()  -- Return nothing to love.quit; this allows it to really quit
print("You can't see this!")  -- Program has terminated; this isn't reached

The example file 8queens.lua is a program to solve the Eight Queens problem by brute force, with visualization of the intermediate positions and solutions found, using eight nested loops for demonstration purposes. An event-oriented version of this program would need to progress in small increments, making it less trivial to make than this straightforward version.

The example file Nqueens.lua is similar, but it implements a simple input function (with no cursor movement) to specify the number of queens, then solves the problem for any given number of queens from 4 to 16 instead of being fixed to 8. This variant is recursive and does N nested recursive calls.

Every file in this repository, including the images, is donated to the public domain or, if your jurisdiction does not contemplate donating things to the public domain, you have my permission to do anything you want with the files to the maximum extent permitted by law.