🎓 A1Learner
🔍
0 of 52 lessons completed

Lua Project: Text Adventure

A text adventure is a game played by typing short commands like go north or take key. Its rooms, exits, and items are tables, and a fixed list of commands drives the game instead of typed input.

A room is an object with a describe method

The Room table below acts as a class through its __index metamethod. A new room prints its name, its description, and its item count.

local Room = {}
Room.__index = Room
function Room.new(name, desc)
  local room = {name = name, desc = desc, exits = {}, items = {}}
  setmetatable(room, Room)
  return room
end
function Room:describe()
  print(self.name)
  print(self.desc)
  print(#self.items)
end
local hall = Room.new("Hall", "A quiet hall.")
hall.items[1] = "key"
hall:describe()

An exit moves the player to another room

An exit is a table slot that maps a direction word to a room. Looking the direction up moves the player to that room.

local Room = {}
Room.__index = Room
function Room.new(name, desc)
  local room = {name = name, desc = desc, exits = {}, items = {}}
  setmetatable(room, Room)
  return room
end
local hall = Room.new("Hall", "A quiet hall.")
local garden = Room.new("Garden", "A sunny garden.")
hall.exits.north = garden
local current = hall
current = current.exits.north
print(current.name)

Taking moves an item to the inventory

Taking finds the named item in the room list, removes it there, and appends it to the inventory. Both list lengths prove the move.

local hallItems = {"key"}
local inventory = {}
for i = 1, #hallItems do
  if hallItems[i] == "key" then
    inventory[1] = hallItems[i]
    table.remove(hallItems, i)
    break
  end
end
print(#inventory)
print(inventory[1])
print(#hallItems)

Example

local Room = {}
Room.__index = Room
function Room.new(name, desc)
  local room = {name = name, desc = desc, exits = {}, items = {}}
  setmetatable(room, Room)
  return room
end
local Item = {}
Item.__index = Item
function Item.new(name)
  local item = {name = name}
  setmetatable(item, Item)
  return item
end
local entrance = Room.new("Entrance", "You stand in a dusty entrance.")
local garden = Room.new("Garden", "A garden with a locked vault to the east.")
local vault = Room.new("Vault", "A small vault.")
entrance.items[1] = Item.new("key")
entrance.exits.north = garden
garden.exits.south = entrance
garden.exits.east = vault
vault.exits.west = garden
vault.items[1] = Item.new("treasure")
local current = entrance
local inventory = {}
local function hasItem(name)
  for i = 1, #inventory do
    if inventory[i].name == name then
      return true
    end
  end
  return false
end
local function describe()
  print(current.name)
  print(current.desc)
  if #current.items == 0 then
    print("Items: none")
  else
    local names = {}
    for i = 1, #current.items do
      names[i] = current.items[i].name
    end
    print("Items: " .. table.concat(names, ", "))
  end
end
local function showInventory()
  if #inventory == 0 then
    print("Inventory: none")
  else
    local names = {}
    for i = 1, #inventory do
      names[i] = inventory[i].name
    end
    print("Inventory: " .. table.concat(names, ", "))
  end
end
local script = {"look", "go north", "go east", "go south", "take key", "go north", "go east", "take treasure", "inventory"}
for i = 1, #script do
  local cmd = script[i]
  print("> " .. cmd)
  local dir = cmd:match("^go%s+(%w+)$")
  local wanted = cmd:match("^take%s+(%w+)$")
  if cmd == "look" then
    describe()
  elseif cmd == "inventory" then
    showInventory()
  elseif dir ~= nil then
    local nextRoom = current.exits[dir]
    if nextRoom == nil then
      print("You cannot go that way.")
    elseif dir == "east" and current.name == "Garden" and not hasItem("key") then
      print("The vault door is locked. You need the key.")
    else
      current = nextRoom
      print("You go " .. dir .. ".")
      describe()
    end
  elseif wanted ~= nil then
    local found = nil
    local foundAt = 0
    for j = 1, #current.items do
      if current.items[j].name == wanted then
        found = current.items[j]
        foundAt = j
        break
      end
    end
    if found == nil then
      print("There is no " .. wanted .. " here.")
    else
      table.remove(current.items, foundAt)
      inventory[#inventory + 1] = found
      print("You take the " .. wanted .. ".")
    end
  else
    print("I do not understand that.")
  end
end
if hasItem("treasure") then
  print("You win!")
else
  print("The treasure is still out there.")
end

Key points

  • Room: a room is a table holding a name, a description, exits, and items.
  • Exit: an exit maps a direction word to another room table.
  • Item: an item is a small object with a name, kept in a room list or in the inventory list.
  • Command: a command is a string that the parser splits into a verb and a word with a pattern.
  • Loop: the game loop walks the fixed command list in order and prints what happens for each command.
  • Win: the game is won when the treasure item reaches the inventory list.

In this example, Room and Item are small classes built with metatables, and the three rooms link to each other through exit slots. The loop walks a fixed command list, parses each command with a pattern, and prints the result, including the locked vault door before the key is taken. Taking the key unlocks the east exit, taking the treasure fills the inventory, and the final check prints the win line.

Note

the command list is fixed in the code, so the same walkthrough prints the same story on every run.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

The adventure runs a fixed list of commands instead of reading typed input. What does that guarantee?