Game: Infinite Treasure II

Game: Infinite Treasure II #

Infinite Treasure II is the sequel to famed cult classic game Infinite Treasure.

In Infinite Treasure II the concept of treasure has been expanded from merely collecting gold coins, to collecting all kinds of things, like old boots, and sandwiches.

Previously… #

Here’s where we left off with the code at the end of Infinite Treasure, which will serve as a starting point for the sequel.

Save this code in a file named infinite-treasure-ii.lua

coins = 0

function main_loop()
  print("Welcome to \"Infinite Treasure\"\n")

  math.randomseed( os.time() )
  game_over = false

  while not game_over do
    io.write("Enter command: ")

    player_command = string.lower(io.read())
    if player_command == "quit" then
      game_over = true
    elseif player_command == "loot" then
      loot_treasure()
    elseif player_command == "check" then
      check_inventory()
    end
  end
end

function loot_treasure()
  print("Looting treasure chest...")

  found_coins = math.random(500)
  print("You found " .. found_coins .. " gold coins!")

  coins = coins + found_coins
end

function check_inventory()
  print("You have " .. coins .. " gold coins!")
end

main_loop()

Don’t forget to update the title of the game:

function main_loop()
  print("Welcome to \"Infinite Treasure II\"\n")

More Than One Thing #

When the only thing we’re trying to keep track of is the number of coins we have, a simple variable storing a count will usually suffice. But what if we want to keep track of other kinds of things too? We might find all kinds of things in a treasure chest, like boots, sandwiches, swords, skulls, raccoons, the possibilities are endless!

We could start declaring individual variables for each kind of thing we want to keep track of:

coins = 0
boots = 0
sandwiches = 0
swords = 0
skulls = 0
raccoons = 0

And this would work! At least to start with, but as the game is developed, there might be hundreds, or thousands of different kinds of items to keep track of. And what if the boots could be different colours? Or if we want each sandwich to keep track of exactly how mouldy it is? It’s going to become increasingly difficult to manage that information without a better approach.

Most programming languages have a variety of types of container (or data structure) that you can use to store more complex information. You might encounter arrays, lists, maps, trees, hash tables, and so on. If you have more than one number you need to store, or a list of names, or a list of monsters, or multiple lines of dialogue, then you’re going to want to use some kind of container to store them in.

Lua, with its focus on simplicity, has only one, called tables.

Tables #

In Lua, a table is what you use if you want to store more than one value. Lua tables are associative arrays or maps, which just means they associate or map a key to a value.

You use the key to read or change the value, just like we’ve been doing with variables.

You can create a new table in Lua like this:

inventory = {}

The {} actually creates the table, and we store that table in a variable named inventory.

For now the table is empty, and doesn’t contain anything, so let’s put something in it! This works pretty much like assigning values to variables:

inventory["coins"] = 5

Here we see the key is the string "coins" and the new value is 5.

We can do something similar to read the current value out of the table:

print(inventory["coins"])

Which should print 5 to the screen.

Trying Things Interactively #

This is a perfect opportunity to load up lua.exe in interactive mode to try some things out.

Type in the following program:

inventory = {}
inventory["coins"] = 5
print("how many coins?", inventory["coins"])
print("how many sandwiches?", inventory["sandwiches"])

You should get output like:

how many coins? 5
how many sandwiches?    nil

5 coins because that’s what we put into the table, and nil sandwiches, because we didn’t put any sandwiches into the table.

Most programming languages have some concept of “empty”, “nothing”, or “nothing set”, which is different from 0. Think of it as a blank space in a form that hasn’t been filled out yet, there isn’t even a 0 written there, it’s just… empty.

Some languages call this a null value, but Lua calls it nil. They mean the same thing, just remember that you have to actually type nil when writing Lua code.

Alternative Syntax #

There’s another way of accessing keys in tables, which is a little bit easier on the eyes and slightly less typing, this program below, will work exactly the same way as the one above:

inventory = {}
inventory.coins = 5
print("how many coins?", inventory.coins)
print("how many sandwiches?", inventory.sandwiches)

So instead of inventory["coins"] we’re using inventory.coins. This method is provided by Lua as a convenience, and for aesthetic reasons, they both do exactly the same thing.

This is sometimes called syntactic sugar… the computer doesn’t care either way, but it’s nicer for the humans that need to read and write it.

There are some situations it can’t be used though, for example if your key has any spaces or punctuation in it, the full syntax will work, but the short version will not, because the Lua interpreter can no longer properly tell which letters are part of the key.

-- this will work
inventory["old boot"] = 4

-- but this won't
inventory.old boot = 5

and you might get an error long the lines of:

lua: [string "<eval>"]:5: '=' expected near 'boot'

Which is confusing, because there obviously is a = near boot, but that’s because Lua got confused by the space in old boot. Sadly, error messages aren’t always that good at pointing out the root of the problem.

Inventory Table #

Let’s change our game from storing only coins, and add an inventory table so that we’ll be able to store multiple things in it.

At the top of the program, remove

coins = 0

and replace it with:

inventory = {}

We also need to change the other places we were using the coins variable, specifically in the loot_treasure function:

  inventory.coins = inventory.coins + found_coins

and the check_inventory function:

  print("You have " .. inventory.coins .. " gold coins!")
Code So Far
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
inventory = {}

function main_loop()
  print("Welcome to \"Infinite Treasure II\"\n")

  math.randomseed( os.time() )
  game_over = false

  while not game_over do
    io.write("\nEnter command: ")

    player_command = io.read()
    if player_command == "quit" then
      game_over = true
    elseif player_command == "loot" then
      loot_treasure()
    elseif player_command == "check" then
      check_inventory()
    end
  end
end

function loot_treasure()
  print("Looting treasure chest...")

  found_coins = math.random(500)
  print("You found " .. found_coins .. " gold coins!")

  inventory.coins = inventory.coins + found_coins
end

function check_inventory()
  print("You have " .. inventory.coins .. " gold coins!")
end

main_loop()

Run the program, use the “loot” command, and you should see… an error:

Welcome to "Infinite Treasure II"


Enter command: loot
Looting treasure chest...
You found 221 gold coins!
..\bin\lua53: .\infinite-treasure-ii.lua:29: attempt to perform arithmetic on a nil value (field 'coins') 
stack traceback:
        .\infinite-treasure-ii.lua:29: in function 'loot_treasure'
        .\infinite-treasure-ii.lua:16: in function 'main_loop'
        .\infinite-treasure-ii.lua:36: in main chunk
        [C]: in ?

Wait, you said tables were better, but this is clearly worse!

Reading the error message we see a problem at line 29,

..\bin\lua53: .\infinite-treasure-ii.lua:29: attempt to perform arithmetic on a nil value (field 'coins') 
stack traceback:

It’s that darned nil, already causing problems right out of the gate.

Previously when we had our variable coins = 0, we were starting with a valid number of coins (0, zero). But now we’re starting with a completely empty table, so there is no coins key in the inventory table yet.

23
24
25
26
27
28
29
30
function loot_treasure()
  print("Looting treasure chest...")

  found_coins = math.random(500)
  print("You found " .. found_coins .. " gold coins!")

  inventory.coins = inventory.coins + found_coins
end

When we try and check the current value of inventory.coins, the result will be nil because there is no coins key in the table yet; and we can’t add found_coins and nil together, because nil isn’t valid number.

You should get a similar message if you try the ‘check’ command, because you also can’t concatenate a string and a nil value.

We’ll address this problem a bit more later, but for now, the simplest thing to do is just make sure to add the coins key to the table at the start of the program:

inventory = {}
inventory.coins = 0

This way we know that when we check the key later, it won’t be nil.

Now when you run the program, both the ’loot’ and ‘check’ commands should function correctly again:

Welcome to "Infinite Treasure II"


Enter command: loot
Looting treasure chest...
You found 201 gold coins!

Enter command: check
You have 201 gold coins!

Enter command:

Loot Table #

It’s Not Just About The Money #

Now we have an inventory table capable of storing more than just coins, so lets put some other things into it.

We could copy and paste the code inside our loot_treasure() function, and rename a few things to add “old boots” in addition to “coins”…

function loot_treasure()
  print("Looting treasure chest...")

  found_coins = math.random(500)
  print("You found " .. found_coins .. " gold coins!")

  inventory.coins = inventory.coins + found_coins

  found_boots = math.random(500)
  print("You found " .. found_boots .. " old boots!")

  inventory["old boots"] = inventory["old boots"] + found_boots
end

And then you could keep copying and pasting and adding more and more things this way, but there are better ways.

Not only is this a lot of work, it’s going to be really hard to make changes in the future. If you add a hundred different items this way, now you have 100 different copies of the code you’ll need to fix if you find a bug, or want to add a new feature.

Arrays #

For starters, we can make one central list of all of the things we might find inside a treasure chest. We can create a new table to store this list, and fill it when our program starts.

loot_data = { "coins", "old boots", "sandwiches", "raccoons" }

This time, instead of creating an empty table using {}, we’ve put a list of strings into the {}, separated by commas. This instructs lua to populate the table using the strings as values.

We provided the values, so where to the keys come from? When creating a table this way, the keys are automatically populated with integers to use as indexes.

An index in this context of tables is the same thing as a key but with the additional requirements that it is an integer, and that all of the indexes in the tables are sequential starting from 1.

The key loot_data[1] will contain the value "coins", loot_data[2] will contain the value "old boots", and so on.

We could have created the table this way instead, and the results would have been the same:

loot_data = { }
loot_data[1] = "coins"
loot_data[2] = "old boots"
loot_data[3] = "sandwiches"
loot_data[4] = "raccoons"

Here we’ve created an empty table like we did before, and added each key and value manually. There are times where you might want to do this, but there are some drawbacks. If you want to add or remove an item from the middle of the list, you’d have to manually change all of the indexes that followed it to maintain the correct sequence, with no duplicate numbers or gaps.

When tables are correctly set up with sequential integer keys we’re using them in a way that is consistent with the concept of an array in most programming languages. When we speak about creating an array in Lua, it means to create a table and use it with sequential integer indexes.

Array tables have a few useful properties…

We can easily find the number of items in the table using the # operator, in our case #loot_data will return the value 4, because there are 4 items in the array. Only sequential integer items will be included when you use #, so if you key is a string (as was the case for "coins" in our inventory table) these will not be included in the count.

If there are any gaps in the index sequence, # won’t work correctly. Worse, it may seem to work some of the time, hiding the problem. However it’s not guaranteed to work consistently unless all of the indexes are an unbroken sequence.

Indexed arrays always stay in the order you specify, and since all of the keys are integers, you can easily do things like ask for the first item in the array loot_data[1], the last item in the array loot_data[#loot_data], or you can access every item in order by starting at 1 and counting up (or in reverse order, by starting at the end and counting backwards to 1).

Choosing A Random Item #

Each time the player loots a chest, we want to choose a random item from the list of possibilities.

Previously we were using the math.random() function to choose how many coins to find, and we can use it again to select an index into the loot_data table.

looted_index = math.random( #loot_data )

By passing #loot_data to math.random we’re requesting a random number between 1 and the length of the loot_data array returned by the # operator (in our case 4, but it could be anything).

Once we’ve chosen an index, we can check which item was selected:

looted_item = loot_data[looted_index]

Here we pass the value of the looted_index as the key to retrieve the item from the loot_data table, eg. if the value of looted_index was 3, then we’d get the 3rd item from the array, "sandwiches".

Note that we’re using looted_index as the index, and not "looted_index", what’s the difference?

If we used "looted_index" we would be asking for the key with the string "looted_index", and there is no such key.

When using looted_index we’re passing the value contained in the variable looted_index as the key, which will be the random integer we chose from 1 to 4.

Putting It Together #

First, make sure you’ve added the loot_data table at the beginning of the program:

loot_data = { "coins", "old boots", "sandwiches", "raccoons" }

Here’s our old loot_treasure():

function loot_treasure()
  print("Looting treasure chest...")

  found_coins = math.random(500)
  print("You found " .. found_coins .. " gold coins!")

  inventory.coins = inventory.coins + found_coins
end

Let’s start fresh, first of all, we aren’t just looting coins anymore, so we’ll rename found_coins to loot_count:

function loot_treasure()
  print("Looting treasure chest...")

  loot_count = math.random(500)

end

Next we’ll choose which item to loot from the loot_data table:

function loot_treasure()
  print("Looting treasure chest...")

  loot_count = math.random(500)

  looted_index = math.random( #loot_data )
  looted_item = loot_data[looted_index]

end

And tell the player what they just looted:

function loot_treasure()
  print("Looting treasure chest...")

  loot_count = math.random(500)

  looted_index = math.random( #loot_data )
  looted_item = loot_data[looted_index]

  print("You found " .. loot_count .. " " .. looted_item .. "!")

end

Finally we need to add the item that they looted into their inventory table:

function loot_treasure()
  print("Looting treasure chest...")

  loot_count = math.random(500)

  looted_index = math.random( #loot_data )
  looted_item = loot_data[looted_index]

  print("You found " .. loot_count .. " " .. looted_item .. "!")

  inventory[looted_item] = inventory[looted_item] + loot_count

end

If you run the program at this point, there are (at least) two major problems:

Checking For nil #

Welcome to "Infinite Treasure II"


Enter command: loot
Looting treasure chest...
You found 58 coins!

Enter command: loot
Looting treasure chest...
You found 221 old boots!
..\bin\lua53: .\infinite-treasure-ii.lua:36: attempt to perform arithmetic on a nil value (field '?')
stack traceback:
        .\infinite-treasure-ii.lua:36: in function 'loot_treasure'
        .\infinite-treasure-ii.lua:19: in function 'main_loop'
        .\infinite-treasure-ii.lua:44: in main chunk
        [C]: in ?

The first problem is our old friend nil once again. Since we manually added the "coins" key into inventory at the beginning of our program, things will work ok as long as the randomly selected item is "coins". But if it’s anything else, inventory won’t contain that key, so our addition fails when we try to add the new items.

Time to deal with nil values properly, we can remove this line from the beginning of the program, which was the temporary solution used earlier:

inventory = {}
inventory.coins = 0  -- delete me

Instead, when we’re about to add the newly found items into our inventory table, we’ll first check to see if the existing value is nil and if so, set it to 0 instead:

function loot_treasure()
  print("Looting treasure chest...")

  loot_count = math.random(500)

  looted_index = math.random( #loot_data )
  looted_item = loot_data[looted_index]

  print("You found " .. loot_count .. " " .. looted_item .. "!")

  old_count = inventory[looted_item]
  if old_count == nil then
    old_count = 0
  end

  inventory[looted_item] = old_count + loot_count
end

We read the current count from inventory[looted_item] and store it into old_count, check if old_count is equal to nil, and if it is, we’ll set it to 0 instead.

Then we add old_count and loot_count together, and store the new total back into inventory.

Listing Our Inventory #

The second big problem is that after we’ve looted a few treasure chests, running the “check” command still only lists the number of coins we have, and nothing else. Pretty disappointing, what happened to all those raccoons?

Enter command: check
You have 397 gold coins!

Our check_inventory() function needs to change as well, as right now it only prints out the coin count:

function check_inventory()
  print("You have " .. inventory.coins .. " gold coins!")
end

Unlike the loot_treasure() function, we don’t want to choose just one item from the list, we want to print everything in our inventory.

To do this, we can use a for loop. Whereas a while loop typically runs indefinitely, until some condition becomes false, a for loop is usually used to loop a fixed number of times, or when you have a known number of items to iterate over.

A simple example:

loot_data = { "coins", "old boots", "sandwiches", "raccoons" }

for i=1,#loot_data do
  print ( loot_data[i] )
end

This uses the indexed or numeric for syntax. The variable i is created, and set to 1, then each time the loop runs, i is incremented (increased by 1), up to the number of items in loot_data which we find using #loot_data.

This works well for array tables, when we know the keys are integer indexes and can easily get the total count, but our inventory table isn’t an array, instead it is using strings as keys.

There’s a second syntax for for loops, and a helper function called pairs() to deal with this situation:

Re-writing out check_inventory() to use this syntax, we get something like this:

function check_inventory()
  for k,v in pairs(inventory) do
    print(k, v)
  end
end

Running the game, looting a few chests, and then using the check command, gives a result like this:

Enter command: check
sandwiches      576
coins   1636
old boots       403

When we use this type of for loop, we declare two new variables (k and v) to store each key in the table, and its value.

The pairs() function is provided by Lua, and is called by the for loop to request each key (and value) from the table we passed in (inventory). How this works exactly is a bit involved, for now just knowing how to use this syntax with for loops should be enough.

Because pairs() gives us each value along with each key, we don’t even need to go through the step of looking them up in the table. We could do this if we wanted to, and this would accomplish the same thing:

function check_inventory()
  for k in pairs(inventory) do
    print(k, inventory[k])
  end
end

Here we’ve ignored the v variable returned by pairs(), and just used k to look up the value from the table ourselves.

Better Formatting #

Our inventory listing is functional now for multiple items, but let’s give it another pass and improve the presentation.

function check_inventory()
  print("Inventory:")
  for k,v in pairs(inventory) do
    print( string.format("\t%d %s", v, k) )
  end
end

We print a nice header, and then list each item below that.

Instead of passing k and v directly to print(), or using .. to simply join several strings together, we’ve used string.format() to (as the name implies), format a new string.

string.format() takes at least one parameter, the format string, which controls how the new string will be constructed. Following that, we need to pass in each parameter that we want to use to make that new string.

Let’s break down the format string: "\t%d %s"

  • %d means, format the parameter I pass in as a decimal integer
  • %s means, format the parameter I pass in as a string

The tab character \t at the beginning, and the space between %d and %s aren’t changed, and are kept in the final result.

string.format() then reads the format string, and follows the instructions, inserting a tab character, replacing %d with the first parameter we passed in (v), and replacing %s with the next parameter we passed in (k).

Now our check command produces results like this:

Enter command: check
Inventory:
        629 raccoons
        54 coins
        181 sandwiches
        458 old boots

Selling Your Loot #

After all, what good is looting treasure chests if you can’t sell all that junk to a vendor for some sweet coins?

We can start by making a copy of check_inventory() renaming it to sell_inventory(), and changing the message inside to something more appropriate:

function sell_inventory()
  print("Selling...")
  for k,v in pairs(inventory) do
    print(string.format("\t%d %s", v, k))
  end
end

We’ll also need to add a new command in our main loop for the player to sell, which will call the sell_inventory() function:


    player_command = io.read()
    if player_command == "quit" then
      game_over = true
    elseif player_command == "loot" then
      loot_treasure()
    elseif player_command == "check" then
      check_inventory()
    elseif player_command == "sell" then
      sell_inventory()
    end
  end
end

If you run the game, loot a few chests and then use the new sell command, you should see output along these lines:

Enter command: sell
Selling...
        638 coins
        345 sandwiches
        233 racoons

Don’t Sell Coins #

Of course, we didn’t actually do any selling yet, but there’s one problem that stands out already. “coins” are listed as one of the things we’re selling, which isn’t what we want. We only want to sell non-coin things, and get coins in return.

This can be solved easy enough by checking if the item we’re looking at currently has the key "coins" or not.

function sell_inventory()
  print("Selling...")
  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))
    end
  end
end

We only want to process items that are not equal (~=) to "coins".

If you test the game again (and make sure you have some coins in your inventory), you should see that coins are no longer listed when using the sell command.

Getting Our Money #

Now that we’ve decided what we’re selling, it’s time to do it for real.

Your first instinct might be to simply add the count of each item to the number of coins we have. This makes sense, and it would work, but if we create a new variable to store the total of the items we’re currently selling, we can use that value to report the total transaction value to the player, we can check whether or not it was 0, and we can do some special handling for "coins" being nil.

So let’s create a new variable called sold_amount and initialize it to 0. Then we can sum up the values of each items as we process them in the loop.

function sell_inventory()
  print("Selling...")

  sold_amount = 0

  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))

      sold_amount = sold_amount + v
    end
  end
end

For the sake of simplicity, each item we sell will be worth 1 coin, but if you wanted you could multiply v by 5, or 42, or 100, to get a lot more coins per item:

sold_amount = sold_amount + v * 42

Then we’ll want to add sold_amount to inventory.coins and store the total back into inventory.coins.

function sell_inventory()
  print("Selling...")

  sold_amount = 0

  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))

      sold_amount = sold_amount + v
    end
  end

  inventory.coins = inventory.coins + sold_amount
end

But wait… there’s a potential problem here, it’s the same bug that we faced above.

If the "coins" key in the inventory table happens to be nil when we sell, then the line we just added will crash when trying to add inventory.coins + sold_amount, because we can’t add to a nil value.

One again we’ll need to check to see if "coins" is nil and, if it is, we can initialize it to zero before proceeding.

function sell_inventory()
  print("Selling...")

  sold_amount = 0

  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))

      sold_amount = sold_amount + v
    end
  end

  if inventory.coins == nil then
    inventory.coins = 0
  end

  inventory.coins = inventory.coins + sold_amount
end

Now when we use the sell command, the number of coins we have increases by the number of items we sold:

Enter command: check
Inventory:
        245 old boots
        935 coins
        375 racoons

Enter command: sell
Selling...
        245 old boots
        375 racoons

Enter command: check
Inventory:
        245 old boots
        1555 coins
        375 racoons

Removing The Items #

But we’re not done yet. Supposedly we “sold” the items, but they’re still in the inventory. As it is, we can keep selling the same items over and over, amassing an infinite fortune! You might be happy about this, but what of the poor shopkeepers? At the very least, this is sure to unbalance the game economy.

For each item that we sell, we can remove it from our inventory table by setting that key (stored in the variable k) to nil like so:

function sell_inventory()
  print("Selling...")

  sold_amount = 0

  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))

      sold_amount = sold_amount + v

      inventory[k] = nil
    end
  end

  if inventory.coins == nil then
    inventory.coins = 0
  end

  inventory.coins = inventory.coins + sold_amount
end

Now when we sell our items, they’re really gone from our inventory, and we have an appropriate number of coins added in exchange.

Final Presentation #

Finally, players will appreciate getting confirmation of their actions, so let’s report to the player the results of the sell command.

We’ll check if they didn’t have anything to sell in the first place and tell them so, otherwise we can report the number of coins they gained by selling.

function sell_inventory()
  print("Selling...")

  sold_amount = 0

  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))

      sold_amount = sold_amount + v

      inventory[k] = nil
    end
  end

  if sold_amount == 0 then
    print("You had nothing to sell.")
  else
    print(string.format("You got %d coins for your goods!", sold_amount))

    if inventory.coins == nil then
      inventory.coins = 0
    end

    inventory.coins = inventory.coins + sold_amount
  end
end

At the same time, we also can move the addition to inventory.coins inside the if else statement, so that it only happens if sold_amount is not zero.

Note that it doesn’t matter whether we report the sale total (sold_amount) to the player before or after we add the coins to the inventory, the report and the addition are separate actions and can happen in any order, and the value of sold_amount isn’t going to change at this point.

However, if you wanted to report the previous or new total amount of coins in the inventory, you might need to carefully think about the order of operations to make sure you were reporting the correct value, before or after the total changed.

Final Code: Infinite Treasure II
loot_data = { "coins", "old boots", "sandwiches", "racoons" }

inventory = {}

function main_loop()
  print("Welcome to \"Infinite Treasure II\"\n")

  math.randomseed( os.time() )
  game_over = false

  while not game_over do
    io.write("\nEnter command: ")

    player_command = io.read()
    if player_command == "quit" then
      game_over = true
    elseif player_command == "loot" then
      loot_treasure()
    elseif player_command == "check" then
      check_inventory()
    elseif player_command == "sell" then
      sell_inventory()
    end
  end
end

function loot_treasure()
  print("Looting treasure chest...")

  loot_count = math.random(500)

  looted_index = math.random( #loot_data )
  looted_item = loot_data[looted_index]

  print("You found " .. loot_count .. " " .. looted_item .. "!")

  old_count = inventory[looted_item]
  if old_count == nil then
    old_count = 0
  end

  inventory[looted_item] = old_count + loot_count
end

function check_inventory()
  print("Inventory:")
  for k,v in pairs(inventory) do
    print(string.format("\t%d %s", v, k))
  end
end

function sell_inventory()
  print("Selling...")

  sold_amount = 0

  for k,v in pairs(inventory) do
    if k ~= "coins" then
      print(string.format("\t%d %s", v, k))

      sold_amount = sold_amount + v

      inventory[k] = nil
    end
  end

  if sold_amount == 0 then
    print("You had nothing to sell.")
  else
    print(string.format("You got %d coins for your goods!", sold_amount))

    if inventory.coins == nil then
      inventory.coins = 0
    end

    inventory.coins = inventory.coins + sold_amount
  end
end

main_loop()

Challenges #

This might be tricky, and involves several steps…

  • Add a random chance when the player uses the loot command that the treasure chest is actually a mimic (a monster that pretends to be ordinary objects like treasure chests), which chooses one random item type, and eats all of them from the player’s inventory (if they have any of that type).
    • Remember that calling math.random() without passing in an integer will return a value between 0.0 and 1.0
    • Choose a chance percentage like 5% (0.05) or 10% (0.1)
    • Each time the player uses loot, generate a new random number and check if it’s lower than your chosen percentage. If it is, then perform the mimic function instead of the regular loot function.
    • In the mimic function, instead of adding a looted item to the player’s inventory, remove all of that item from the player’s inventory.
    • Make sure to print appropriate messages to tell the player what happened.
Enter command: loot
Looting treasure chest...
You found 18 racoons, for a total of 426!

Enter command: loot
Looting treasure chest... oh no, the chest was actually a mimic!
This mimic likes to eat racoons
The mimic eats all of your racoons!

Enter command: check
Inventory:
        1227 sandwiches
        1662 coins
        976 old boots

Next - Stranger Wings