Tables and first class functions allow us to further abstract related components of a script into more complex building blocks.
Say we wanted to create a shop vendor who sells some items.
A crude way to do this is a to create variables to store the vendor’s inventory stock and price.
Then for purchasing duty, since we don’t want to rewrite the same code over again, we will create a function that handles that for us.
local giantBeetleStock = 2
local giantBeetlePrice = 10
local function buyGiantBeetle(gold)
if giantBeetleStock > 0 then
if gold >= giantBeetlePrice then
giantBeetleStock = giantBeetleStock - 1
print("Thanks for the purchase!")
return "giant beetle"
else
print("Not enough gold!")
return false, gold
end
end
print("giant beetle out of stock")
end
local result = buyGiantBeetle(5) --Output: Not enough gold!
result = buyGiantBeetle(10) --Output: Thanks for the purchase!
result = buyGiantBeetle(10) --Output: Thanks for the purchase!
result = buyGiantBeetle(10) --giant beetle out of stock
However, this has a few shortcomings.
First, as the merchant’s shop expands, we would have to implement this code for every type of item that gets added to the inventory. If there are dozens or hundreds of possible vendor items, this is not practical.
Second, the scope for stock and price variables can be accessed and modified from anywhere in the script. For a larger, more complex script this makes maintenance difficult and a nightmare if you have to chase bugs.
Instead, we can utilize a table to group all of the relevant components together.
local alchemyVendor = {}
alchemyVendor["inventory"] = {
["giant beetle"] = {["price"] = 10, ["stock"] = 2},
["wooden stick"] = {["price"] = 5, ["stock"] = 6},
}
alchemyVendor["buy item"] = function(item, gold)
local inventory = alchemyVendor["inventory"]
if not inventory[item] or inventory[item]["stock"] <= 0 then
print(item, "out of stock") return
end
if gold < inventory[item]["price"] then print("Not enough gold!") return false, gold end
print("Thanks for the purchase!")
inventory[item]["stock"] = inventory[item]["stock"] - 1
return item
end
local result = alchemyVendor["buy item"]("giant beetle", 5) --Output: Not enough gold!
result = alchemyVendor["buy item"]("giant beetle", 10) --Output: Thanks for the purchase!
result = alchemyVendor["buy item"]("wooden stick", 5) --Output: Thanks for the purchase!
result = alchemyVendor["buy item"]("giant beetle", 10) --Output: Thanks for the purchase!
result = alchemyVendor["buy item"]("giant beetle", 10) --giant beetle out of stock
Now the merchant has his own inventory kept inside a table that can store any number of items and along with the proper functionality to handle shop duty.
While much better, this still has some problems.
The first is that the function references the variable alchemyVendor inside itself.
And tables get reassigned often, meaning if this variable is reassigned but the function depends on this specific variable identifier, the code will likely break.
...
...
local result = alchemyVendor["buy item"]("giant beetle", 5) --Output: Not enough gold!
result = alchemyVendor["buy item"]("giant beetle", 10) --Output: Thanks for the purchase!
local reassigedVendor = alchemyVendor
alchemyVendor = {} -- vendor is now unusable
result = reassigedVendor["buy item"]("giant beetle", 10) --Error: attempt to index nil with 'inventory'
result = reassigedVendor["buy item"]("giant beetle", 10) --Error
...
...
Second, although the merchant’s inventory will only be modified by the provided functions, the inventory is still visible throughout the script and can still be inadvertently modified elsewhere.
Fortunately, we do have a way to hide away tables and variables and make them only accessible in some scope. We’ve met them before.
Lexical scope and closures.
local alchemyVendor
do
local temporaryTable = {}
local inventory = { --upvalue
["giant beetle"] = {["price"] = 10, ["stock"] = 2},
["wooden stick"] = {["price"] = 5, ["stock"] = 6},
}
temporaryTable["buy item"] = function (item, gold)
if not inventory[item] or inventory[item]["stock"] <= 0 then
print(item, "out of stock") return
end
if gold < inventory[item]["price"] then print("Not enough gold!") return false, gold end
print("Thanks for the purchase!")
inventory[item]["stock"] = inventory[item]["stock"] - 1
return item
end
alchemyVendor = temporaryTable
end
local result = alchemyVendor["buy item"]("giant beetle", 5) --Output: Not enough gold!
result = alchemyVendor["buy item"]("giant beetle", 10) --Output: Thanks for the purchase!
local reassignedVendor = alchemyVendor
alchemyVendor = nil
result = reassignedVendor["buy item"]("giant beetle", 10) --Output: Thanks for the purchase!
local buyFunction = reassignedVendor["buy item"]
local buyGiantBeetle = function(gold) buyFunction("giant beetle", gold) end
result = buyGiantBeetle(10) --Output: giant beetle out of stock
The vendor’s inventory is now hidden from the rest of the script and we provide only an interface for other scripts to work with.
And just like that, you my friend accidentally stepped into a pile of…
-
- Functions
- Arguments
- Table Arguments
- Returning Results
- First Class Functions
- Closures
- Anonymous Functions
- Events
- Working with the UI
- Integrating Tables and Functions
- ModuleScripts
- A Shop and Currency System
- Functions