A comprehensive development guide for Lua programming with focus on game development, embedded systems, and scripting applications with best practices and performance optimization
This agents.md example showcases industry best practices for AI agent instruction. The agents.md example provides a comprehensive template that you can adapt for your specific project requirements.
Every element in this agents.md example has been carefully designed to optimize OpenAI Codex performance and ensure consistent AI agent behavior across your development workflow.
To use this agents.md example in your project, download the template and customize it according to your specific needs. This agents.md example serves as a solid foundation for your AI agent configuration.
Study the structure and conventions used in this agents.md example to understand how successful projects implement AI agent instruction for optimal results.
This comprehensive guide outlines best practices for Lua programming, covering its unique features and common use cases in game development, embedded systems, and scripting applications. The guide emphasizes clean, efficient code that leverages Lua's dynamic typing, powerful table features, and coroutines while maintaining excellent performance and readability.
# Install Lua (Ubuntu/Debian)
sudo apt-get install lua5.4 lua5.4-dev
# Install LuaRocks
sudo apt-get install luarocks
# Install essential packages
luarocks install busted # Testing framework
luarocks install ldoc # Documentation generator
luarocks install luacheck # Static analysis tool
# For game development
luarocks install love # LÖVE 2D framework
lua-project/
├── src/ # Source code
│ ├── main.lua # Entry point
│ ├── modules/ # Custom modules
│ │ ├── utils.lua
│ │ ├── config.lua
│ │ └── game_logic.lua
│ └── lib/ # Third-party libraries
├── tests/ # Test files
│ ├── test_utils.lua
│ └── test_game_logic.lua
├── docs/ # Documentation
├── assets/ # Game assets (if applicable)
│ ├── images/
│ ├── sounds/
│ └── fonts/
├── conf.lua # Configuration (LÖVE 2D)
├── rockspec # Package specification
└── README.md
-- Variables and functions: snake_case
local player_health = 100
local function calculate_damage(base_damage, armor)
return base_damage - armor
end
-- Classes/modules: PascalCase
local GameEngine = {}
local PlayerManager = require("modules.PlayerManager")
-- Constants: UPPERCASE
local MAX_PLAYERS = 4
local DEFAULT_SPEED = 200
-- Private functions/variables: prefix with underscore
local function _internal_helper()
-- Private implementation
end
local _private_cache = {}
-- modules/player.lua - Proper module structure
local Player = {}
Player.__index = Player
-- Module constants
local DEFAULT_HEALTH = 100
local MAX_LEVEL = 50
-- Private functions
local function _validate_level(level)
return level >= 1 and level <= MAX_LEVEL
end
-- Constructor
function Player.new(name, level)
assert(type(name) == "string", "Player name must be a string")
assert(_validate_level(level), "Invalid player level")
local self = setmetatable({}, Player)
self.name = name
self.level = level
self.health = DEFAULT_HEALTH
self.experience = 0
return self
end
-- Public methods
function Player:take_damage(damage)
assert(type(damage) == "number" and damage >= 0, "Invalid damage value")
self.health = math.max(0, self.health - damage)
return self.health <= 0
end
function Player:heal(amount)
assert(type(amount) == "number" and amount >= 0, "Invalid heal amount")
self.health = math.min(DEFAULT_HEALTH, self.health + amount)
end
function Player:gain_experience(exp)
assert(type(exp) == "number" and exp >= 0, "Invalid experience value")
self.experience = self.experience + exp
-- Check for level up
local required_exp = self.level * 100
if self.experience >= required_exp then
self:level_up()
end
end
function Player:level_up()
if self.level < MAX_LEVEL then
self.level = self.level + 1
self.experience = 0
self.health = DEFAULT_HEALTH -- Full heal on level up
print(string.format("%s leveled up to %d!", self.name, self.level))
end
end
-- Metamethods
function Player:__tostring()
return string.format("Player(%s, Level %d, HP: %d)",
self.name, self.level, self.health)
end
return Player
-- Proper error handling with pcall/xpcall
local function safe_divide(a, b)
local success, result = pcall(function()
if b == 0 then
error("Division by zero", 2)
end
return a / b
end)
if success then
return result
else
print("Error:", result)
return nil
end
end
-- Enhanced error handling with xpcall
local function enhanced_error_handler(err)
local trace = debug.traceback(err, 2)
print("Error occurred:", trace)
-- Log to file or send to error reporting service
return err
end
local function risky_operation(data)
local success, result = xpcall(function()
-- Complex operation that might fail
assert(type(data) == "table", "Data must be a table")
assert(data.value, "Data must have a value field")
return data.value * 2
end, enhanced_error_handler)
return success and result or nil
end
-- Input validation utility
local Validator = {}
function Validator.is_positive_number(value)
return type(value) == "number" and value > 0
end
function Validator.is_valid_string(value, min_length, max_length)
if type(value) ~= "string" then return false end
local len = #value
return len >= (min_length or 1) and len <= (max_length or math.huge)
end
function Validator.is_in_range(value, min_val, max_val)
return type(value) == "number" and value >= min_val and value <= max_val
end
-- Efficient table operations
local TableUtils = {}
-- Deep copy implementation
function TableUtils.deep_copy(original)
local copy = {}
for key, value in pairs(original) do
if type(value) == "table" then
copy[key] = TableUtils.deep_copy(value)
else
copy[key] = value
end
end
return copy
end
-- Merge tables
function TableUtils.merge(target, source)
for key, value in pairs(source) do
if type(value) == "table" and type(target[key]) == "table" then
TableUtils.merge(target[key], value)
else
target[key] = value
end
end
return target
end
-- Filter table elements
function TableUtils.filter(tbl, predicate)
local result = {}
for i, value in ipairs(tbl) do
if predicate(value) then
table.insert(result, value)
end
end
return result
end
-- Map function over table
function TableUtils.map(tbl, transform)
local result = {}
for i, value in ipairs(tbl) do
result[i] = transform(value)
end
return result
end
-- Example usage
local numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
local even_numbers = TableUtils.filter(numbers, function(n) return n % 2 == 0 end)
local squared_numbers = TableUtils.map(numbers, function(n) return n * n end)
-- Local variable optimization
local function optimized_loop(data)
-- Cache frequently used values as locals
local math_sin = math.sin
local math_cos = math.cos
local table_insert = table.insert
local results = {}
local data_length = #data
-- Pre-allocate table when size is known
for i = 1, data_length do
results[i] = 0 -- Pre-allocation
end
for i = 1, data_length do
local value = data[i]
results[i] = math_sin(value) + math_cos(value)
end
return results
end
-- String concatenation optimization
local function build_large_string(parts)
-- Use table.concat instead of string concatenation
local buffer = {}
for i, part in ipairs(parts) do
buffer[i] = tostring(part)
end
return table.concat(buffer)
end
-- Memory-efficient object pooling
local ObjectPool = {}
ObjectPool.__index = ObjectPool
function ObjectPool.new(create_func, reset_func)
local self = setmetatable({}, ObjectPool)
self.create_func = create_func
self.reset_func = reset_func
self.available = {}
self.in_use = {}
return self
end
function ObjectPool:acquire()
local obj = table.remove(self.available)
if not obj then
obj = self.create_func()
end
self.in_use[obj] = true
return obj
end
function ObjectPool:release(obj)
if self.in_use[obj] then
self.in_use[obj] = nil
if self.reset_func then
self.reset_func(obj)
end
table.insert(self.available, obj)
end
end
-- Simple coroutine example
local function countdown_coroutine(start)
for i = start, 1, -1 do
print("Countdown:", i)
coroutine.yield(i)
end
print("Blast off!")
return "completed"
end
-- Create and run coroutine
local co = coroutine.create(countdown_coroutine)
local success, value = coroutine.resume(co, 5)
while coroutine.status(co) ~= "dead" do
print("Received:", value)
success, value = coroutine.resume(co)
end
-- Advanced coroutine scheduler
local Scheduler = {}
Scheduler.__index = Scheduler
function Scheduler.new()
local self = setmetatable({}, Scheduler)
self.tasks = {}
self.current_time = 0
return self
end
function Scheduler:add_task(func, delay)
local co = coroutine.create(func)
table.insert(self.tasks, {
coroutine = co,
wake_time = self.current_time + (delay or 0)
})
end
function Scheduler:update(dt)
self.current_time = self.current_time + dt
for i = #self.tasks, 1, -1 do
local task = self.tasks[i]
if self.current_time >= task.wake_time then
local success, delay = coroutine.resume(task.coroutine)
if coroutine.status(task.coroutine) == "dead" then
table.remove(self.tasks, i)
elseif delay then
task.wake_time = self.current_time + delay
end
end
end
end
-- Usage example
local scheduler = Scheduler.new()
scheduler:add_task(function()
while true do
print("Task 1 running")
coroutine.yield(1.0) -- Wait 1 second
end
end)
scheduler:add_task(function()
for i = 1, 5 do
print("Task 2, iteration", i)
coroutine.yield(0.5) -- Wait 0.5 seconds
end
end)
-- Game state management
local GameState = {}
GameState.__index = GameState
function GameState.new(name)
local self = setmetatable({}, GameState)
self.name = name
return self
end
function GameState:enter() end
function GameState:exit() end
function GameState:update(dt) end
function GameState:draw() end
function GameState:keypressed(key) end
-- Game state manager
local StateManager = {}
StateManager.__index = StateManager
function StateManager.new()
local self = setmetatable({}, StateManager)
self.states = {}
self.current_state = nil
return self
end
function StateManager:add_state(name, state)
self.states[name] = state
end
function StateManager:change_state(name)
if self.current_state then
self.current_state:exit()
end
self.current_state = self.states[name]
if self.current_state then
self.current_state:enter()
end
end
function StateManager:update(dt)
if self.current_state then
self.current_state:update(dt)
end
end
function StateManager:draw()
if self.current_state then
self.current_state:draw()
end
end
-- Entity Component System (ECS) pattern
local Entity = {}
Entity.__index = Entity
function Entity.new(id)
local self = setmetatable({}, Entity)
self.id = id
self.components = {}
return self
end
function Entity:add_component(component_type, component)
self.components[component_type] = component
return self
end
function Entity:get_component(component_type)
return self.components[component_type]
end
function Entity:has_component(component_type)
return self.components[component_type] ~= nil
end
function Entity:remove_component(component_type)
self.components[component_type] = nil
end
-- Component examples
local PositionComponent = {}
function PositionComponent.new(x, y)
return {x = x or 0, y = y or 0}
end
local VelocityComponent = {}
function VelocityComponent.new(dx, dy)
return {dx = dx or 0, dy = dy or 0}
end
local HealthComponent = {}
function HealthComponent.new(max_health)
return {
current = max_health or 100,
maximum = max_health or 100
}
end
-- tests/test_player.lua
local Player = require("src.modules.player")
describe("Player", function()
local player
before_each(function()
player = Player.new("TestPlayer", 1)
end)
describe("initialization", function()
it("should create player with correct attributes", function()
assert.are.equal("TestPlayer", player.name)
assert.are.equal(1, player.level)
assert.are.equal(100, player.health)
assert.are.equal(0, player.experience)
end)
it("should validate input parameters", function()
assert.has_error(function()
Player.new(123, 1) -- Invalid name type
end)
assert.has_error(function()
Player.new("Test", 0) -- Invalid level
end)
end)
end)
describe("combat", function()
it("should take damage correctly", function()
local is_dead = player:take_damage(30)
assert.are.equal(70, player.health)
assert.is_false(is_dead)
end)
it("should die when health reaches zero", function()
local is_dead = player:take_damage(150)
assert.are.equal(0, player.health)
assert.is_true(is_dead)
end)
it("should heal correctly", function()
player:take_damage(50)
player:heal(20)
assert.are.equal(70, player.health)
end)
it("should not heal above maximum", function()
player:heal(50)
assert.are.equal(100, player.health)
end)
end)
describe("experience and leveling", function()
it("should gain experience", function()
player:gain_experience(50)
assert.are.equal(50, player.experience)
end)
it("should level up when experience threshold is reached", function()
player:gain_experience(100) -- Level 1 requires 100 exp
assert.are.equal(2, player.level)
assert.are.equal(0, player.experience)
assert.are.equal(100, player.health) -- Full heal on level up
end)
end)
end)
-- Performance testing utilities
local Profiler = {}
function Profiler.time_function(func, iterations)
iterations = iterations or 1000
local start_time = os.clock()
for i = 1, iterations do
func()
end
local end_time = os.clock()
local total_time = end_time - start_time
local avg_time = total_time / iterations
return {
total_time = total_time,
average_time = avg_time,
iterations = iterations
}
end
function Profiler.memory_usage()
collectgarbage("collect")
return collectgarbage("count")
end
-- Example performance test
local function test_table_operations()
local data = {}
for i = 1, 1000 do
data[i] = math.random(1, 100)
end
-- Test different sorting methods
local function bubble_sort(arr)
local n = #arr
for i = 1, n do
for j = 1, n - i do
if arr[j] > arr[j + 1] then
arr[j], arr[j + 1] = arr[j + 1], arr[j]
end
end
end
end
local function lua_sort(arr)
table.sort(arr)
end
-- Compare performance
local bubble_result = Profiler.time_function(function()
local copy = TableUtils.deep_copy(data)
bubble_sort(copy)
end, 10)
local lua_result = Profiler.time_function(function()
local copy = TableUtils.deep_copy(data)
lua_sort(copy)
end, 10)
print("Bubble sort average:", bubble_result.average_time)
print("Lua sort average:", lua_result.average_time)
end
-- Security utilities
local Security = {}
function Security.sanitize_string(input, max_length)
if type(input) ~= "string" then
return ""
end
-- Remove potentially dangerous characters
input = input:gsub("[<>\"'&]", "")
-- Limit length
if max_length and #input > max_length then
input = input:sub(1, max_length)
end
return input
end
function Security.validate_number_input(input, min_val, max_val)
local num = tonumber(input)
if not num then
return nil, "Invalid number format"
end
if min_val and num < min_val then
return nil, "Number too small"
end
if max_val and num > max_val then
return nil, "Number too large"
end
return num
end
function Security.hash_password(password, salt)
-- Simple example - use proper crypto library in production
local combined = password .. (salt or "default_salt")
local hash = 0
for i = 1, #combined do
hash = (hash * 31 + string.byte(combined, i)) % 2^32
end
return tostring(hash)
end
-- Safe loading of user scripts
function Security.safe_load_script(script_string, env)
env = env or {}
-- Restricted environment
local safe_env = {
-- Allow basic functions
print = print,
tostring = tostring,
tonumber = tonumber,
type = type,
pairs = pairs,
ipairs = ipairs,
next = next,
-- Math functions
math = {
abs = math.abs,
ceil = math.ceil,
floor = math.floor,
max = math.max,
min = math.min,
random = math.random,
sqrt = math.sqrt
},
-- String functions
string = {
len = string.len,
sub = string.sub,
find = string.find,
match = string.match,
gsub = string.gsub
}
}
-- Merge with provided environment
for k, v in pairs(env) do
safe_env[k] = v
end
local chunk, err = load(script_string, "user_script", "t", safe_env)
if not chunk then
return nil, err
end
return pcall(chunk)
end
-- Memory management utilities
local MemoryManager = {}
function MemoryManager.force_cleanup()
collectgarbage("collect")
end
function MemoryManager.get_memory_usage()
return collectgarbage("count")
end
function MemoryManager.set_gc_params(pause, stepmul)
-- pause: controls how long to wait before starting a new cycle
-- stepmul: controls the relative speed of the collector
collectgarbage("setpause", pause or 100)
collectgarbage("setstepmul", stepmul or 200)
end
-- Weak table implementation for caches
function MemoryManager.create_weak_cache(mode)
local cache = {}
setmetatable(cache, {__mode = mode or "v"}) -- "k", "v", or "kv"
return cache
end
-- Example: Image cache with weak references
local image_cache = MemoryManager.create_weak_cache("v")
local function load_image(filename)
if image_cache[filename] then
return image_cache[filename]
end
local image = love.graphics.newImage(filename) -- LÖVE 2D example
image_cache[filename] = image
return image
end
This comprehensive guide provides a solid foundation for developing efficient, maintainable Lua applications across various domains, from game development to embedded systems and scripting applications.