This Doodle Insights is all about code design in Lua! I’m sorry to say that if you’re not very interested in code, you won’t be very interested in this write-up.
There’s a few data structures (also known as ‘containers’) common in many programming languages and that I’m gonna assume you know for this Doodle Insights so here are a few definitions!
- An array is an ordered bunch of values that you can read and write to, accessing values at static indexes.
- A list is basically an array with dynamic indexes, when removing an item from a list, every item after that one has their index go -=1. When adding an item, this item is put at the end of the list.
- A map is an array where indexes can be something else than numbers, they could be strings or enumerations or even a boolean, any type of data really.
- An enumeration is a bunch of constants that you can define and then use whenever. These constants generally serve as identifiers or flags. Typically, you can either let the compiler assign underlying values to your constants or assign them yourself.
- A class is a data structure blueprint in which you can define fields for any type of values and ‘methods’ which are function callbacks stored in the structure. Here I’m gonna assume you are defining your classes yourself but most engines define the base classes for you. (GM:S, Unity, the list goes on… but not Pico-8!)
- An object is an instance of a class.
Lua doesn’t have any of these. Lua has tables.
Lua tables are great.
They’re basically every data structures at the same time. And they are still pretty fast.
It means you can do hybrids. It means you can come up with your own data structures.
All you gotta do is use them the right way.
That’s really cool, but it also means that tables are a big mess of possibilities and that there’s no default way to use them. So how to use them?
Let’s see by going through each of the classic data structures and see how to do them with tables in Lua and each time we’ll see a useful application with a Pico-8 Doodle!
array={0,1,2,3} v=array[1] -- v=0 v=#v -- v=4 (size of the array) array[5]=4 -- array={0,1,2,3,4} array[7]=5 -- array={0,1,2,3,4,nil,5}
Two things to note here. First, the default indexing in Lua goes from 1 up, not 0 (this will inevitably get you off-guard if you are used to 0-based indexing). Second, on that last line, what I wrote isn’t exactly right. If you set array[7] to 5 but don’t set array[6] to anything, the value 5 will be mapped to the index 7 but the index 6 will not exist at all. If you try reading the index 6 you will get nil but if you use the “all()” function, it will stop at the index 5 because index 6 is undefined. Likewise, if you define array[9999], the indexes 8 to 9998 will not exist, and they will not be allocated in the memory.
In this doodle, the algorithm is fed an array of arrays representing color palettes. The algorithm navigates in these different palettes to form a new one which will be used to draw the flower. The navigation is done by finding the last color of our generated palette in one of the palettes and then get the next or the previous value in that array.
Arrays are great for storing values, either code-generated, user-defined, or both!
list={} add(list,0) -- list={0} add(list,1) -- list={0,1} v=list[1] -- v=0 del(list,0) -- list={1} v=list[1] -- v=1 list={0,1,2,3,4} v=list[#list] -- v=4
The biggest difference with an actual list is that there’s no way to add an element at the beginning of the list. So here’s a quick function for that:
function add_front(list,v) local k=#list for i=k,1,-1 do list[i+1]=list[i] end list[1]=v end
Caution with this one, the longer the list will be, the slower this function will run. Don’t use it on lists of hundreds of elements.
During the full-board generation part of this doodle, each case of the grid is represented in the code by a list of all its possibilities. The algorithm then proceeds to randomly choose possibilities in cases with the least of them and remove possibilities from affected cases accordingly. By the end of that part, each case is a list with only one element in it, the final number for that case.
This Doodle makes a second use of lists as it stores a history of the last states of the grid in case the algorithm faces a paradox and needs to go back a few steps. That history is a list of grids, to which is added the new state of the grid each frame and from which is deleted the oldest state of the grid before there’s no memory left.
(the grid storing the possibility lists for the cases is just a big array)
Lists are great at storing code-generated results that you may want to get rid of later but you also may want to keep some of them.
map={ [3]=6, [8]="hello", ["hi"]=12 } v=map[3] -- v=6 v=map[8] -- v="hello" v=map["hi"] -- v=12 map["oh"]=8 v=map["oh"] -- v=8
Not much to add there! As far as I can tell, Lua maps are unordered, as going through them with the “pairs()” function will process them in a ‘random’ order. Because Lua variables are not type-restricted, you may use any type of value as map keys.
In this doodle, the content possibilities for every elements are stored in a json-like map with a key for each element leading to a nested map with a “regular” key for a list of the regular possibilites and a “rare” key for a list of the rare possibilities. In the generation code, it is super easy to navigate a map structure like this one by storing keys in variables.
Maps are particularly great for storing easy-to-access user-defined values but also code-generated values in some cases.
object={ x=64, y=64, method=foo } object.x+=32 -- object.x=96 object.method() -- calls foo() object:method() -- calls foo(object) object:method(1)-- calls foo(object,1) object.z=4 v=object.z -- v=4 v=object["y"] -- v=64
Note that “object:method()” is exactly the same as “object.method(object)”. Also note that field names are treated like string keys, so a field can be read and written like a mapped value, using braces like on the last line. (the opposite, “map.hi=12”, also works)
Each bomb is the representation of an object. (duh) They are drawn at their x and y fields and they are all updated and drawn by the same functions taking one bomb object as parameter. The fields of each bomb are its coordinates, its vertical speed and its color. All the bombs are stored in a list, until they explode and get deleted off the list.
The explosions are also objects! And before they exhaust out, they spawn smaller explosions around them, of the same color or of their darker shade (the darker shades are stored in an array) until they are too small.
Objects are the flour of ~95% of video games today. Almost every game on the featured Pico-8 games BBS page uses objects. They are a great way to organize your code in clean small tasks that process the sets of data that are objects.
enum={ red=0, blue=1, green=2 } v=enum.red
It’s basically a map. You have to define a value for each of your keys which is kind of a shame, but when you do, make sure to not use the same value twice because that would defeat the whole purpose.
I’m genuinely sorry but I rarely use enumerations in Lua myself, so I have no examples. But the classic uses for them is behaviour keys to store in objects. For example you can define an ‘occupation’ enum that goes ‘idle’,’walking’,’cutting_wood’, etc. When updating an object, you check its ‘occupation’ field and compare it to the ‘occupation’ enum. Once you figured what this object is doing, you can update it accordingly.
I personally prefer to directly set callbacks that I previously stored in an ‘occupation’ map for this kind of situation.
I think we’ve been through all the main ones! The array version is definitely the most easy and quick to use, but they all are tables and you can use them in all the ways at the same time if you want to.
Also, the grid data structure exists in a bunch of languages but really I just see it as an array of arrays that you can then access with grid[x][y].
Lua tables are great and super versatile but if you want your code to make sense, it is best to choose how you use each of them. As you probably noticed with the examples, it is very common to use the different structures together. An object system is barely any use without lists to store them. A map can become extremely useful once you start writing arrays in it. Again arrays are very fast, yet they are not as fast as reading and writing directly to memory using ‘peek()’ and ‘poke()’, which can prove tricky but faster than tables. Still, Lua tables are great.
I hope you enjoyed this doodle insights! This one was definitely code-heavy but I think some of you will like that!
If you have any questions or remarks about anything in this write-up, do let me know on the Patreon post!
These Doodle Insights are here thanks to the awesome people supporting it on Patreon! Here are their names!
Joseph White, Adam M. Smith, Ryan Malm, Matthew, Tim and Alexandra, Sasha Bilton, berkfrei, Nick Hughes, Christopher Mayfield, Jearl, Dave Hoffman, Thomas Wright, Morgan Jensen, Zach Bracken, Anne Le Clech, Flo Devaux, Emerson Smith, Cathal O’Keeffe, Dan Sanderson, Andrew Reist, vaporstack, Dzozef, Cole Smith, Jared Butowsky, Tony Sarkees and Justin TerAvest!
I have no idea what the next Doodle Insights will be about but this week I should write a public article about architecture in Pico-8 and your knowledge on tables will prove useful I hope! If you have ideas for the next Doodle Insights though, please tell me!
Thank you for reading and have fun with tables!
TRASEVOL_DOG
Leave a Reply