tables
add
add( table , value )
table | the variable name of the table |
value | the data to add to the table |
This function inserts the given value to a table under the next available numbered key.
Example
We can create a table that is a list of color strings first and add values immediately like this:
colors = { "red", "blue", "pink", "yellow" }
Colors | |
1 | "red" |
2 | "blue" |
3 | "pink" |
4 | "yellow" |
Then if we want to add more values to the table, we can add another color to the list like this:
add( colors, "purple" )
Colors | |
1 | "red" |
2 | "blue" |
3 | "pink" |
4 | "yellow" |
5 | "purple" |
1741
5 May 2023
del
del( table, value )
table | the variable name of the table |
value | the data to remove from the table |
This function will remove an entry from a table that matches the value given. If you have more than one entry holding the same value, only the first entry that is found to match will be removed.
Example
We can create a table that is a list of color strings first and add values immediately like this:
colors = { "red", "blue", "pink", "yellow" }
Colors | |
1 | "red" |
2 | "blue" |
3 | "pink" |
4 | "yellow" |
Then if we want to remove a value from the table, we can do this:
del( colors, "blue" )
Colors | |
1 | "red" |
2 | "pink" |
3 | "yellow" |
Notice that "blue"
was the value stored at key #2. Deleting "blue"
, also updated the rest of the list's keys.
This happens for tables that have numbered keys, but it does not change tables that have named keys.
Return Value
This function will return the deleted value. If the function did not delete anything, then it will return nil
.
table = { "a", "b", "c" }
returned = del(table,"a")
print(returned) --prints a
print(table[1]) --prints b
1058
5 May 2023
deli
deli( table, index )
table | the variable name of the table |
index | the numbered key to remove from the table |
This function will remove an entry from a table that matches the index given. This only works for tables that have numbered keys, but it does not change tables that have named keys.
We can create a table that is a list of color strings first and insert values immediately like this:
colors = { "red", "blue", "pink", "yellow" }
Colors | |
1 | "red" |
2 | "blue" |
3 | "pink" |
4 | "yellow" |
These values are automatically indexed (given numbered keys) so we can use those indexes to remove an entry like this:
deli( colors, 2 )
Colors | |
1 | "red" |
2 | "pink" |
3 | "yellow" |
Notice that "blue"
was the value stored at index 2
. Deleting "blue"
, also updated the rest of the list's keys so that there isn't a gap at index 2.
Return Value
This function will return the deleted item. If the function did not delete anything, then it will return nil
.
table = { "a", "b", "c" }
returned = deli(table,1)
print(returned) --prints a
print(table[1]) --prints b
942
5 May 2023
count
count( table )
table | the variable name of the table you want to count |
This function will return the highest number key in a table. To accurately return the total count, the table must be numbered and without any gaps.
Example
We can create a table that is a list of color strings first and add values immediately like this:
colors = { "red", "blue", "pink", "yellow" }
Colors | |
1 | "red" |
2 | "blue" |
3 | "pink" |
4 | "yellow" |
Then if we want to count the number of entries in the table, we can do this:
count( colors ) --returns 4
Shorthand
#colors --returns 4
1144
5 May 2023
A table is a way to store and organize sets of data in a flexible and dynamic way.
Think of a table as a collection of related information, like a list of groceries or a directory of phone numbers. You can imagine these tables like data tables: sets of information placed into rows and columns.
You can collect and organize your game information easily into tables. You can't view the tables in PICO-8, so you will have to imagine them, and we will display our examples here as data tables, such as:
Shopping List
Number | Food Item |
---|---|
1 | "apples" |
2 | "bananas" |
3 | "carrots" |
Player Stats
Attribute | Points |
---|---|
health | 10 |
magic | 20 |
defense | 13 |
Tables are a powerful tool for programmers, not just for organizing data for easy use, but also to make more efficient and dynamic code (More on that below). Tables and loops often go hand in hand, so having a good understanding of both is necessary to planning how to organize your data into tables well.
Terms Related to Tables
Key
The first column's data. A number or string used to find the data stored in Column 2.
Value
The second column's data. A number, string, boolean, table, or function that you want to store.
Column 1 (key) |
Column 2 (value) |
---|---|
1 | "A" |
2 | "B" |
3 | "C" |
Index
When a table has numbered keys (like the shopping list above), these numbers are referred to as indexes.
Sorting
The process of arranging the entries of the table in a specific order. A numbered table in Lua and PICO-8 is automatically sorted in ascending order, starting at 1 (note that most programming languages start at 0, so if Lua is not your first language, this may take some getting used to).
Count / Length
The "count" or "length" of a table is the total number of entries in the table. See count function.
Lookup
A table lookup is the process of searching for and getting a specific value or element within a table. The lookup process involves specifying a key or index, which is used to identify the desired element in the table. See "How to get a value from a Table" (coming soon).
Iteration
To iterate means to cycle through something one at a time. We can iterate through the values of a table easily using Loops. This is useful when you want to check or do something for every entry in a table.
Advanced Table Terms
Nested Tables
It is possible to have tables inside of other tables. When multiple things are organized inside of each other, we call them "nested". You can think of these like folders on your computer; you can store files inside of folders and also have folders inside of folders ("nested folders"). You can have as many nested tables (tables inside of other tables) as you want.
Parent & Child
When you have nested tables, we refer to the outer table as the parent and the inner table as the child.
enemies = {}
enemies[1] = {
name="goblin",
health=5,
attack=3
}
enemies[2] = {
name="ogre",
health=10,
attack=6
}
Enemies (Parent Table) | |||||||||
---|---|---|---|---|---|---|---|---|---|
1 |
|
||||||||
2 |
|
Another way to add more child tables and let them be automatically numbered is with add
.
add( enemies, { name="orc", health=8, attack=7 } )
Tables are "Efficienct and Dynamic"
Efficiency refers to how well a program uses system resources (such as memory, processing power, and input/output operations). An efficient program is one that can achieve its goals with minimal resource usage, while avoiding unnecessary operations.
Here is a comparison of code that is organized with and without tables.
Without Tables
--item variables
item_sprite = 1
item_x = 10
item_y = 20
--get value
print(item_sprite) --prints 1
--draw item
spr(item_sprite, item_x, item_y)
--17 tokens
With Tables
--item table
item = { sprite=1, x=10, y=20 }
--get value
print(item.sprite) --prints 1
--draw item
spr(item.sprite, item.x, item.y)
--24 tokens
So far, these don't look too different. And if we compare the tokens, the one without tables looks more efficient. However, this example is only for a single item in the entire game. So imagine if we add just 2 more items.
Well without tables, we have to write 3 new unique variables for every item. But thanks to tables, those variables turn into keys, and you can use the same key in different tables without getting their values confused. This also makes looping through the tables much easier because you can lookup the same exact key name such as sprite
and simply change the table that is being looked up to get the different sprite value for the different items.
Here is how we could have 3 items:
Without Tables
--item variables
item1_sprite = 1
item1_x = 10
item1_y = 20
item2_sprite = 2
item2_x = 30
item2_y = 50
item3_sprite = 3
item3_x = 70
item3_y = 90
--get one value
print(item1_sprite) --prints 1
--draw each item
spr(item1_sprite,item1_x,item1_y)
spr(item2_sprite,item2_x,item2_y)
spr(item3_sprite,item3_x,item3_y)
--45 tokens
With Nested Tables
--items table of tables
items = {
{ sprite=1, x=10, y=20 },
{ sprite=2, x=30, y=50 },
{ sprite=3, x=70, y=90 },
}
--get one value
print(items[1].sprite) --prints 1
--loop and draw each item
for item in all(items) do
spr(item.sprite,item.x,item.y)
end
--50 tokens
Look how fast the tokens of the code without tables caught up to the code with tables; that's just 3 items. Your game is sure to have many more than that! Not just items but your game will probably have many enemies, bullets, particles and more. It would be crazy to write out enemy1_sprite
, enemy1_x
, enemy1_y
for even just 10 enemies! And so, tables are efficient!
But what do we mean by tables being dynamic?
Dynamic refers to the way something can change or adapt while the program is running. Global variables are set one time, and they can be changed any time after that. But if we are talking about items that a player can pick up or enemies that can be killed, how do we handle those variables when the item or enemy should no longer be in the game at all?
Tables are dynamic because they can grow and shrink in size. We can add to and delete from a table at any time. This makes it easy to prepare a function for adding a new item or enemy to the game at certain parts of the game, and remove them from the game just as easily when they should no longer be there.
This dynamic organization of our game objects also makes it very easy for us to count how many of those objects there are by simply counting the table.
2434
28 Apr 2024
{}
Empty Table
To create an empty table, use the opening and closing curly braces {}
, like this:
mytable = {}
mytable | |
---|---|
This is useful for preparing a variable to hold the table in your _init()
function during set up. Then use add()
to insert data into the table later.
Below are examples of using tables in different ways and the terms that we use to refer to these different "types" of tables are often confusing because they can mean specific things in other programming languages. Just know that in Lua, these are all simply tables. We do use these terms to help specify how the table is configured, what type of data it holds, or how we will use it. But at the end of the day, they are all just tables.
List
To create a list type of table (a.k.a array), you can write the values inside the curly braces, separated by commas, and let the table automatically number them.
mytable = { "superman", "spiderman", "ironman" }
mytable | |
---|---|
1 | superman |
2 | spiderman |
3 | ironman |
You can also number them yourself, if you need to (and order them yourself):
mytable = { [2]="kirk",
[1]="spock", [3]="scotty" }
mytable | |
---|---|
1 | spock |
2 | kirk |
3 | scotty |
Or you can add numbered keys to any table already created by using square brackets []
after the table name, mytable[ key ]
, like this:
mytable = {} --empty table
mytable[1] = "picard"
mytable[2] = "laforge"
mytable[3] = "worf"
mytable | |
---|---|
1 | picard |
2 | laforge |
3 | worf |
Object
To create an object type of table (a.k.a "dictionary", or "key-value map"), you can name the keys and use any data type as the value; even other tables or functions.
mytable = { sprite=1, x=10, y=5, state="run" }
mytable | |
---|---|
sprite | 1 |
x | 10 |
y | 5 |
state | run |
Another way to write this is vertically, so that it is more readable for more complex object tables:
mytable = {
sprite = 1,
x = 10,
y = 5,
state = "run",
}
When writing your table entries vertically like this, make sure you still separate each entry with a comma. Forgetting a comma is often the cause of the "unclosed {
" error.
Another way to write this type of table vertically, that some people prefer, is to create the empty object table first, then insert each entry separately by using square brackets []
after the table name, mytable[ "key" ]
, like this:
mytable = {}
mytable["sprite"] = 1
mytable["x"] = 10
mytable["y"] = 5
mytable["state"] = "run"
Notice that this way does not require commas and is less likely to cause confusing errors, however it costs more tokens and characters. So if you already know what should be inside the table, it is better to insert the entries at the same time as creating the table.
Collection of Objects
To create a collection type of table (a.k.a. Matrices and Multi-Dimensional Arrays) that holds multiple objects, you will want to use nested tables. (What is a Nested Table?)
mytable= { --open outer table
{} --inner table
} --close outer table
mytable | |||||
---|---|---|---|---|---|
1 |
|
Nested tables can be hard to wrap your head around at first, but the more you work with them, the easier and more logical they will be to use. Here is what it looks like to have 2 object tables inside of a collection.
items= {
{ name="sword", attack="5" }, --inner table 1
{ name="sheild", armor="10" }, --inner table 2
}
items | |||||
---|---|---|---|---|---|
1 |
|
||||
2 |
|
A collection of objects is a great way to create, store, and control multiple objects in your game such as bullets, items, enemies, and particles.
Collection of Bullets:
bullets={ { x=65, y=51 }, { x=22, y=25 }, { x=33, y=15 }, }
Collection of Items:
coins={ { sprite=1, x=10, y=5, name="copper" }, { sprite=2, x=20, y=25, name="silver" }, { sprite=3, x=30, y=15, name="gold" }, }
Collection of Enemies:
enemies={ { sprite=10, x=30, y=15, name="goomba" }, { sprite=11, x=40, y=35, name="koopa" }, { sprite=12, x=50, y=65, name="boo" }, }
These collections of objects can be easily updated and drawn in your game using loops.
Shorthand
You can use the shorthand .
to refer to a table key instead of []
, like this:
mytable[key]
mytable = {}
mytable["sprite"] = 1
mytable["x"] = 10
mytable["y"] = 5
mytable["state"] = "run"
mytable.key
mytable = {}
mytable.sprite = 1
mytable.x = 10
mytable.y = 5
mytable.state = "run"
These two can also be used together to access inner table keys of nested tables, useful when you have a collection of objects (described above).
enemies[1].sprite
This will look at the enemies table, then find the first entry [1]
inside of that, then it will look inside of that inner table and find the sprite
key, and get the value stored there (which would be 6 in the below example table).
enemies | |||||||
---|---|---|---|---|---|---|---|
1 |
|
Related Topics
1731
28 Apr 2024
:
OOP Tables
Object-oriented programming (OOP) is a way of writing computer programs using objects that have both data (like numbers or words) and actions (like moving or making sounds). It helps in organizing and making programs easier to work with and reuse. Some programming languages are designed with OOP principles in mind, and have classes to easily make objects but Lua does not. However, Lua does support OOP with object tables, metatables, prototypes, and first-class functions. If you don't understand what those mean yet, don't worry, we'll explain!
To create an object table in PICO-8:
obj = {
sprite = 1,
x = 10,
y = 5,
}
obj | |
---|---|
sprite | 1 |
x | 10 |
y | 5 |
Tables in Lua can hold all types of data, including functions! In OOP, the term method refers to a function that is inside of an object table. Here are four ways you can add methods to your table (see Lua Docs):
--1 method inside table
obj = {
sprite = 1,
x = 10,
y = 5,
draw = function(self) ... end
}
--2 method outside table
obj.draw = function(self, a, b) ... end
--3 method outside table
function obj.draw(self, a, b) ... end
--4 method outside table, hidden self
function obj:draw(a, b) ... end
All four examples will do the same thing: add a draw()
method to table obj
. It is up to you which you prefer or which works best in your situation. Just notice that the fourth example uses a colon operator ( :
), which implicitly declares a self
parameter.
Now that you have created methods in your objects, here are four examples of how to call them:
--1 pass self argument
obj.draw(obj)
--2 implicitly pass self
obj:draw()
--3 pass self and arguments
obj.draw(obj, 1, 2)
--4 with arguments, implicitly pass self
obj:draw(1, 2)
Again, it is up to you and your case to determine which style you want to use because all four examples do the same thing: call the draw
method in obj
and pass arguments to the function.
Lua does not have classes (Lua Docs), but it does support prototype-based programming which is a style of OOP and achieves similar goals (code reuse and inheritance).
Now that you know how to create an object table with methods, here is an example of how you might create an enemy object in your game that has update
and draw
methods:
--object table
enemy = {
sp = 1,
x = 0,
y = 0,
dx = 0,
dy = 0,
update = function( self ) --method 1
self.x += self.dx
self.y += self.dy
end,
draw = function( self ) --method 2
spr(self.sp, self.x, self.y)
end
}
We can then turn this enemy
table into a prototype and create new enemy objects from it. This uses metatables and metamethods, so make sure you understand those before continuing.
--add method to prototype for copying/inheriting
function enemy:new(obj)
obj = obj or {} -- if obj isn't passed, create empty object table
--any key not found in obj,
--look for it in prototype (this enemy table)
setmetatable( obj, {__index = self} )
return obj
end
--create goblin from enemy prototype
goblin = enemy:new()
--specify values in goblin table
goblin.sprite = 3
goblin.x = 6
goblin.y = 10
--or you can pass the values to the "new" method
ogre = enemy:new( {sprite=4, x=40, y=50} )
This example is just to show how goblin
and ogre
both use the enemy
prototype's new()
method to be created which leaves enemy
unchanged and can continue to be used as the prototype for more enemies. We can add more variables and functions to the prototype enemy
table as default values for objects created from it. Since we use this prototype as a metatable, when goblin or ogre tables try to use a key that is not found in its own table, it will look for it in the enemy table and use that.
Prototype
enemy | |
---|---|
sprite | 1 |
x | 0 |
y | 0 |
new | function |
update | function |
draw | function |
Object
goblin | |
---|---|
sprite | 3 |
x | 6 |
y | 10 |
Object
ogre | |
---|---|
sprite | 4 |
x | 40 |
y | 50 |
With the metatable being the prototype, you can call goblin:draw()
and even though it is not in the goblin table, it won't error because it will find the draw
method in the prototype enemy
.
Further Reading
Take this to the next level by reading what Merwok wrote on the BBS that has more explanations and examples of taking OOP in PICO-8 another step farther.
After you've got experience working with OOP, and are feeling ready to tackle the next big step to using OOP while saving many tokens, then you're ready to learn about and use _env.
972
29 Apr 2024