{}const=>[]async()letfn</>var
LuaDevelopment

Lua in one evening: from scratch to a text RPG quest in a few steps

Let's learn Lua from scratch — without being boring and with memes. We will install Lua, analyze variables, tables, functions and loops, and in the end we will write a full-fledged text RPG quest with a combat system, traps and inventory. All in one sitting.

К

Kodik

Author

10 min read

You know that feeling when you open a new programming language and for the first 20 minutes you think, "Well, that's easy"? And then it starts. The brackets are wrong, you forgot the semicolons, the compiler is yelling at you like a neighbor from upstairs at 3 in the morning.

Lua is different. Seriously. This language is so concise that you can learn its basics over a cup of coffee. And in the evening, you can assemble a fully working mini-project. Don't you believe me? Well, let's check it out.

What is Lua and why do I need it?

Lua (pronounced "Lou-a", not "Loua" - but, honestly, everyone says it however they like) is a lightweight scripting language that was born in Brazil in 1993. Yes, it is older than most JavaScript frameworks. And, unlike them, it is still not deprecated.

Where Lua is used:

  • Games - Roblox, World of Warcraft, Garry's Mod, Love2D. If you have ever written an add-on for WoW, then you are already a Lua developer, congratulations.

  • Nginx and OpenResty — high-load web servers run Lua scripts to process requests.

  • Neovim — if you're one of those people who spends more time setting up their editor than writing code, Lua is your new best friend.

  • Embedded systems and IoT — Lua is so lightweight that it works even where Python is suffocating.

In short, Lua is like a Swiss knife, only without a corkscrew. Compact, useful and surprisingly powerful.

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

Step 0: Installation (faster than npm install)

Linux/macOS:

# Ubuntu / Debian
sudo apt install lua5.4

# macOS
brew install lua

Windows:

Download LuaBinaries from official website or put through scoop:

scoop install lua

We check:

lua -v
# Lua 5.4.x  Copyright (C) ...

Done. No Docker, no virtual environment, no package.json on 300 lines. It just works.

Step 1: Hello World and the first basics

Create the hello.lua file:

print("Hello, World!")
print("Lua is easier than you thought")

Launch:

lua hello.lua

That's it. Without public static void main, without import sys, without use strict. You just write and run.

Variables — no types, no problems

local name = "Alexey"
local age = 25
local is_developer = true

print(name .. " — developer: " .. tostring(is_developer))

Note: .. is a concatenation of strings. Not +, not template strings. Two modest points. Lua is a minimalist.

Conditions - good old classic

local level = 42

if level >= 100 then
    print("You're a legend!")
elseif level >= 50 then
    print("Almost there!")
else
    print("Keep swinging")
end

Note: then and end instead of curly braces. It's unusual at first, but after an hour you don't notice it anymore.

Cycles — three types for all occasions

-- Цикл for (классический)
for i = 1, 5 do
    print("Iteration: " .. i)
end

-- Цикл while
local count = 0
while count < 3 do
    count = count + 1
    print("Counter: " .. count)
end

-- Перебор таблицы (о таблицах — ниже)
local fruits = {"apple", "banana", "mango"}
for index, fruit in ipairs(fruits) do
    print(index .. ". " .. fruit)
end

Step 2: Tables are the only data structure (and that's enough)

Lua has no arrays, dictionaries, objects, sets, tuples, decks... There are tables. And they replace everything. It's as if a JavaScript object and an array merged into one entity:

-- Как массив
local colors = {"Red", "Green", "Blue"}
print(colors[1])  -- "Red" (индексация с 1, да-да)

-- Как словарь
local player = {
    name = "Hero",
    hp = 100,
    level = 1
}
print(player.name)  -- "Hero"
print(player["hp"]) -- 100 (тоже работает)

-- Комбинированный вариант
local config = {
    title = "My Game",
    resolution = {1920, 1080},
    fullscreen = true
}

Indexing from 1 — yes, it's a shock after all the languages with zero indexation. But you'll get used to it. Probably. Someday.

Step 3: Functions — First-Class Citizens

-- Обычная функция
local function greet(name)
    return "Hi, " .. name .. "!"
end

print(greet("world"))

-- Функция как переменная (анонимная)
local square = function(x)
    return x * x
end

print(square(7))  -- 49

-- Множественный возврат (это Lua-суперсила!)
local function get_player_stats()
    return "Hero", 100, 15
end

local name, hp, attack = get_player_stats()
print(name .. " | HP: " .. hp .. " | ATK: " .. attack)

Multiple return values are one of those things in Lua that makes you sigh: "Why isn't it like that everywhere?"

Step 4: Assemble a mini-project — a text quest!

Theory is good, but we're not giving a lecture here. Let's write a real mini-project — a text RPG quest. You have to explore the dungeon, fight monsters and (possibly) survive.

Create the dungeon.lua file:

-- === ТЕКСТОВЫЙ КВЕСТ: ПОДЗЕМЕЛЬЕ ===

math.randomseed(os.time())

-- Состояние игрока
local player = {
    name = "",
    hp = 100,
    attack = 15,
    potions = 3
}

-- Список монстров
local monsters = {
    {name = "Goblin", hp = 30, attack = 8, xp = 10},
    {name = "Skeleton", hp = 50, attack = 12, xp = 20},
    {name = "Dark Mage", hp = 40, attack = 18, xp = 25},
    {name = "Ogr", hp = 80, attack = 10, xp = 30},
}

-- Вспомогательные функции
local function separator()
    print(string.rep("-", 40))
end

local function show_status()
    separator()
    print("HP: " .. player.hp .. " | Potions: " .. player.potions)
    separator()
end

local function ask(question, options)
    print("\n" .. question)
    for i, opt in ipairs(options) do
        print("  " .. i .. ") " .. opt)
    end
    io.write("Your choice: ")
    local choice = tonumber(io.read())
    if not choice or choice < 1 or choice > #options then
        print("Incomprehensible choice, try again.")
        return ask(question, options)
    end
    return choice
end

-- Система боя
local function battle(monster)
    print("\n⚔️  Appears in front of you " .. monster.name .. "! (HP: " .. monster.hp .. ")")

    local enemy_hp = monster.hp

    while enemy_hp > 0 and player.hp > 0 do
        show_status()
        local action = ask("What are you doing?", {"Attack", "Drink the potion", "Try to escape"})

        if action == 1 then
            local damage = math.random(player.attack - 5, player.attack + 5)
            enemy_hp = enemy_hp - damage
            print("You inflict " .. damage .. " damage!")

            if enemy_hp > 0 then
                local enemy_damage = math.random(monster.attack - 3, monster.attack + 3)
                player.hp = player.hp - enemy_damage
                print(monster.name .. " beats you on " .. enemy_damage .. " damage!")
            end

        elseif action == 2 then
            if player.potions > 0 then
                local heal = math.random(20, 35)
                player.hp = player.hp + heal
                player.potions = player.potions - 1
                print("You are restoring " .. heal .. " HP!")
            else
                print("You don't have any potions! You lose a turn...")
            end
            local enemy_damage = math.random(monster.attack - 3, monster.attack + 3)
            player.hp = player.hp - enemy_damage
            print(monster.name .. " beats you on " .. enemy_damage .. " damage!")

        elseif action == 3 then
            if math.random(1, 100) <= 40 then
                print("You managed to escape!")
                return false
            else
                print("Couldn't escape!")
                local enemy_damage = math.random(monster.attack - 3, monster.attack + 5)
                player.hp = player.hp - enemy_damage
                print(monster.name .. " beats you on " .. enemy_damage .. " damage!")
            end
        end
    end

    if enemy_hp <= 0 then
        print("\n🎉 " .. monster.name .. " defeated!")
        if math.random(1, 100) <= 30 then
            player.potions = player.potions + 1
            print("You find a potion!")
        end
        return true
    end

    return false
end

-- Комнаты подземелья
local function explore_room(room_number)
    separator()
    print("\n🚪 Room " .. room_number)

    local events = {"monster", "monster", "treasure", "trap", "empty"}
    local event = events[math.random(1, #events)]

    if event == "monster" then
        local monster = monsters[math.random(1, #monsters)]
        return battle(monster)

    elseif event == "treasure" then
        print("You find a treasure chest!")
        local bonus = ask("What are you taking?", {"Health Potion (+1)", "Grinding stone (+3 to attack)"})
        if bonus == 1 then
            player.potions = player.potions + 1
            print("Potions: " .. player.potions)
        else
            player.attack = player.attack + 3
            print("Attack now: " .. player.attack)
        end
        return true

    elseif event == "trap" then
        print("Trap! Thorns shoot out of the wall!")
        local trap_damage = math.random(5, 15)
        player.hp = player.hp - trap_damage
        print("You get " .. trap_damage .. " damage.")
        return true

    else
        print("An empty room. Silence and dust.")
        if math.random(1, 100) <= 20 then
            print("You find a potion in the corner!")
            player.potions = player.potions + 1
        end
        return true
    end
end

-- === НАЧАЛО ИГРЫ ===

print("╔══════════════════════════════════════╗")
print("║     DUNGEON: TEXT QUEST     ║")
print("║         Written in Lua              ║")
print("╚══════════════════════════════════════╝")

io.write("What's your name, adventurer? ")
player.name = io.read()
print("\nWelcome, " .. player.name .. "!")
print("You are facing the entrance to a dungeon with 5 rooms.")
print("Go through them all and become a legend.\n")

local rooms_cleared = 0

for room = 1, 5 do
    if player.hp <= 0 then
        break
    end

    explore_room(room)

    if player.hp <= 0 then
        break
    end

    rooms_cleared = rooms_cleared + 1

    if room < 5 then
        show_status()
        local choice = ask("Going further?", {"Go ahead!", "Enough for today"})
        if choice == 2 then
            print("\nYou decide to return. Sometimes retreating is also wise.")
            break
        end
    end
end

separator()
separator()

if player.hp <= 0 then
    print("\n💀 " .. player.name .. " fell into the dungeon...")
    print("Rooms passed: " .. rooms_cleared .. " of 5")
    print("Try again — the dungeon is waiting!")
else
    if rooms_cleared == 5 then
        print("\n🏆 " .. player.name .. " completed the entire dungeon!")
        print("HP: " .. player.hp .. " | Remaining potions: " .. player.potions)
        print("You are a true legend!")
    else
        print("\n🏠 " .. player.name .. " returned from the dungeon.")
        print("Rooms passed: " .. rooms_cleared .. " of 5")
    end
end

print("\nThank you for playing! 🎮")

Launch:

lua dungeon.lua

And now you have already written a full-fledged project on Lua. With a combat system, random, inventory and plot branching. Not some calculator, but a real game.

What's next? Where to grow

Lua is a great entry point, and here's where you can move:

  • Love2D — framework for 2D games on Lua. If you like the text quest, imagine what will happen with the graphics.

  • Neovim plugins — your own plugin for the editor, written in Lua, is a separate kind of pleasure.

  • OpenResty — embed Lua scripts directly into nginx and process millions of requests.

  • Roblox — seriously, Roblox earns real money, and all the logic is written in Lua.

Do you want to continue your studies?
Come to Kodik!

If you like this format — from theory to practice in one sitting — you will definitely like it Code. This is an application for learning programming, where each lesson is not just a wall of text, but tasks that you solve with your hands. Python, JavaScript, HTML, CSS and other technologies - all with real practice, without water and boredom.

And we also have Telegram channel, where useful posts are regularly published: concept reviews, cheat sheets, mini-tasks and memes for developers. This is a great way to repeat programming in a convenient format — on the way to work, during a lunch break, or when you have “five more minutes” before bed. Subscribe and let programming be a part of every day.

Lua is a language that proves that you don't have to be complex to be powerful. In one evening, you went from print("Hello") to a text RPG quest with a combat system. And at the same time, you didn't install a single dependency, didn't write a single config, and didn't break a single package-lock.json.

Now you have a working project, an understanding of the basics and a direction for growth. And this is already more than 90% of people who "were going to start learning programming next week."

Go for it. 🚀

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card