Example Game - The Haunted Tower

This complete game demonstrates all major features of the Booktasy engine.

Code




-- Booktasy Tutorial Game: The Haunted Tower

-- ==========================================
-- 1. GLOBAL VARIABLES
-- ==========================================
-- These track game progress.
HAS_KEY = false
HAS_TORCH = false
BOSS_DEFEATED = false
TURN_COUNT = 0

-- Register globals for saving
Booktasy.save("HAS_KEY")
Booktasy.save("HAS_TORCH")
Booktasy.save("BOSS_DEFEATED")
Booktasy.save("TURN_COUNT")

Booktasy.MAX_TURNS = 50 

-- ==========================================
-- 2. FACTORY FUNCTIONS
-- ==========================================
-- We define functions to create complex objects so we can call them in init().

function create_shadow_beast()
    local beast = Booktasy.Entity("Shadow Beast", "A shifting mass of darkness with glowing red eyes.", { })
    beast.hp = 10
    beast.max_hp = 10
    beast.damage = 3
    beast.is_enemy = true 
    return beast
end

function create_wizard()
    return Booktasy.Entity("Old Wizard", "A frail man in blue robes.", {
        dialogue = {
            "Wizard: 'Greetings, traveler.'",
            "Wizard: 'Beware the upper floors. A Shadow Beast lurks there.'",
            "Wizard: 'It hates the light. Remember that.'"
        }
    })
end

-- ==========================================
-- 3. CUSTOM COMMANDS
-- ==========================================
function get_room() return Booktasy.GAME_WORLD.current_room end

function command_use(target)
    if not target then print("Use what?"); return end
    local item = Booktasy.find_item_in_inventory(target)
    if not item then print("You don't have that."); return end

    if item.name == "Torch" then
        if not HAS_TORCH then
            HAS_TORCH = true
            print("You light the torch. It burns brightly.")
            item.description = "A burning torch. It provides light."
        else
            print("The torch is already lit.")
        end
    elseif item.name == "Health Potion" then
        print("You drink the potion. You feel revitalized!")
        local _, idx = Booktasy.find_item_in_inventory("Health Potion")
        table.remove(Booktasy.INVENTORY, idx)
    elseif item.name == "Iron Key" then
        print("It's a key. You need to find a locked door to use it on.")
    else
        print("You can't use that.")
    end
end

function custom_go(dir)
    local room = get_room()
    
    -- Darkness Logic
    if room.name == "Tower Entrance" and dir == "north" and not HAS_TORCH then
        print("#[R]{It's too dark in the hallway! You need a light source.}")
        return
    end
    
    -- Locked Door Logic
    if room.name == "Dark Hallway" and dir == "up" then
        if HAS_KEY then
            print("You unlock the heavy door with the Iron Key and proceed up.")
            Booktasy.core_go("up") 
            return
        else
            print("#[D]{The door to the upper level is locked.}")
            return
        end
    end
    
    -- Default Movement
    Booktasy.core_go(dir)
end

function command_search(target)
    if not target then print("Search what?"); return end
    local entity = Booktasy.find_entity_in_room(get_room(), target)
    if not entity then print("You don't see that here."); return end

    if #entity.inventory > 0 then
        print("You search the " .. entity.name .. " and find:")
        for i, item in ipairs(entity.inventory) do
            print("- #[I]{" .. item.name .. "}")
            table.insert(Booktasy.INVENTORY, item)
        end
        entity.inventory = {} 
        print("You take everything.")
        if Booktasy.find_item_in_inventory("Iron Key") then HAS_KEY = true end
    else
        print("You find nothing in the " .. entity.name .. ".")
    end
end

function command_attack(target)
    local entity = Booktasy.find_entity_in_room(get_room(), target)
    if not entity or not entity.is_enemy then print("You can't attack that."); return end

    local damage = 1 
    local weapon = Booktasy.find_item_in_inventory("Rusty Sword")
    if weapon then damage = weapon.damage end

    print("You swing at the " .. entity.name .. "!")
    local roll = Booktasy.rnd(1, 6)

    if roll > 2 then
        print("#[S]{HIT!} You deal " .. damage .. " damage.")
        entity.hp = entity.hp - damage
        if entity.hp <= 0 then
            print("The " .. entity.name .. " creates a terrible shriek and dissolves into smoke!")
            local r = get_room()
            for i, e in ipairs(r.entities) do
                if e == entity then table.remove(r.entities, i); break end
            end
            if entity.name == "Shadow Beast" then
                BOSS_DEFEATED = true
                Booktasy.check_events() 
            end
        else
            print("The beast has " .. entity.hp .. " HP left.")
        end
    else
        print("#[D]{MISS!} The beast dodges your attack.")
    end
end

-- Register Commands
Booktasy.commands.use = command_use
Booktasy.commands.search = command_search
Booktasy.commands.attack = command_attack
Booktasy.commands.go = custom_go 
Booktasy.commands.n = function() custom_go("north") end
Booktasy.commands.s = function() custom_go("south") end
Booktasy.commands.e = function() custom_go("east") end
Booktasy.commands.w = function() custom_go("west") end
Booktasy.commands.u = function() custom_go("up") end
Booktasy.commands.d = function() custom_go("down") end

-- ==========================================
-- 4. INITIALIZATION (RESTART SAFE)
-- ==========================================
function Booktasy.init()
    -- 1. Reset Global Flags
    HAS_KEY = false
    HAS_TORCH = false
    BOSS_DEFEATED = false
    TURN_COUNT = 0
    Booktasy.GAME_OVER = false
    Booktasy.GAME_OVER_MESSAGE = ""
    Booktasy.INVENTORY = {} -- Clear old inventory
    Booktasy.GAME_WORLD.rooms = {} -- Clear old rooms
    Booktasy.EVENTS = {}
    Booktasy.TIMERS = {}

    -- 2. REBUILD THE WORLD (Fresh Objects)
    
    -- Items
    local iron_key = Booktasy.Item("Iron Key", "A heavy, rusted iron key.", { usable = true })
    local torch = Booktasy.Item("Torch", "A wooden torch.", { usable = false, lit = false })
    local rusty_sword = Booktasy.Item("Rusty Sword", "An old blade.", { usable = false })
    rusty_sword.damage = 2
    local potion = Booktasy.Item("Health Potion", "A red bubbly liquid.", { usable = true })
    potion.heal_amount = 5

    -- Rooms
    local entrance = Booktasy.Room("Tower Entrance", "You stand before a large stone tower. The door is open.")
    table.insert(entrance.entities, create_wizard())
    table.insert(entrance.items, torch)

    local hallway = Booktasy.Room("Dark Hallway", "A long, dark corridor. It's hard to see.")

    local armory = Booktasy.Room("Armory", "Racks of old weapons line the walls.")
    table.insert(armory.items, rusty_sword)

    local lab = Booktasy.Room("Laboratory", "Beakers and books are scattered everywhere.")
    table.insert(lab.items, potion)
    local desk = Booktasy.Entity("Desk", "A cluttered wooden desk.", { inventory = { iron_key } })
    table.insert(lab.entities, desk)

    local boss_room = Booktasy.Room("Throne Room", "A cold room at the top of the tower.")
    local boss = create_shadow_beast()
    table.insert(boss_room.entities, boss)

    -- Register Rooms
    Booktasy.add_room(entrance)
    Booktasy.add_room(hallway)
    Booktasy.add_room(armory)
    Booktasy.add_room(lab)
    Booktasy.add_room(boss_room)

    -- Connect Rooms
    Booktasy.connect_rooms("Tower Entrance", "north", "Dark Hallway")
    Booktasy.connect_rooms("Dark Hallway", "west", "Armory")
    Booktasy.connect_rooms("Dark Hallway", "east", "Laboratory")
    
    boss_room.exits["down"] = "Dark Hallway"
    
    -- Manually set the 'up' exit for logic mapping, though locked logic handles access
    hallway.exits["up"] = "Throne Room"

    -- 3. Register Events
    Booktasy.add_event(Booktasy.Event("MonsterAttack",
        function() 
            local r = get_room(); if not r then return false end
            for _, e in ipairs(r.entities) do if e.is_enemy then return true end end
            return false
        end,
        function() 
            local r = get_room()
            for _, e in ipairs(r.entities) do
                if e.is_enemy then
                    print("#[R]{The " .. e.name .. " lashes out at you!}")
                    print("You take " .. e.damage .. " damage!")
                end
            end
        end,
        false
    ))

    Booktasy.add_event(Booktasy.Event("GameWon",
        function() return BOSS_DEFEATED end,
        function() Booktasy.end_game("You have cleansed the tower! VICTORY!") end,
        true 
    ))

    Booktasy.add_event(Booktasy.Event("StartTorchTimer",
        function() return HAS_TORCH and not Booktasy.has_timer("TorchTimer") end,
        function()
            print("#[D]{(Your torch begins to burn... it will last 20 turns)}")
            Booktasy.add_timer("TorchTimer", 20, function()
                HAS_TORCH = false
                print("#[R]{Your torch flickers and dies!}")
                local item = Booktasy.find_item_in_inventory("Torch")
                if item then item.description = "A burnt out wooden stick." end
            end, false)
        end,
        false
    ))

    -- 4. Start Game
    print("#[S]{Welcome to The Haunted Tower}")
    print("Find the key, light your way, and defeat the evil within.")
    print("---------------------------------------------------")

    Booktasy.GAME_WORLD.current_room = entrance
    entrance:describe()

    -- 5. Snapshot for Restart
    -- We save this clean state immediately
    if not Booktasy._INITIAL_STATE then
        Booktasy.save_initial_state()
    end
end

function Booktasy.turn_limit_reached()
    print("#[R]{The moon has set. The tower's magic seals forever.}")
    Booktasy.end_game("Time Out")
end