Examples

Game Systems

Leaderboards, achievements, and matchmaking examples
These examples are AI-generated and has not been reviewed for accuracy. Use them as a starting point and verify correctness before deploying in production.

Game Systems

Examples for common game systems using MongoDB.

Leaderboard System

--[[
    Leaderboard System
    Score tracking and rankings
]]

local Leaderboard = {}

function Leaderboard.Initialize(db)
    Leaderboard.db = db
    Leaderboard.scores = db:Collection("leaderboard")

    -- Create indexes
    Leaderboard.scores:CreateIndex({ category = 1, score = -1 }, false, "category_score")
    Leaderboard.scores:CreateIndex({ steamid = 1, category = 1 }, true, "player_category")
end

-- Submit a score
function Leaderboard.SubmitScore(steamid, username, score, category, callback)
    category = category or "default"

    -- Upsert - update if higher, insert if new
    Leaderboard.scores:FindOneAsync(
        { steamid = steamid, category = category },
        function(err, existing)
            if existing and existing.score >= score then
                -- Existing score is higher
                if callback then callback(false, "Existing score is higher") end
                return
            end

            local doc = {
                steamid = steamid,
                username = username,
                category = category,
                score = score,
                submitted_at = os.time()
            }

            if existing then
                -- Update existing
                Leaderboard.scores:UpdateOneAsync(
                    { steamid = steamid, category = category },
                    { ["$set"] = doc },
                    function(err)
                        if callback then callback(not err, err) end
                    end
                )
            else
                -- Insert new
                Leaderboard.scores:InsertOneAsync(doc, function(err)
                    if callback then callback(not err, err) end
                end)
            end
        end
    )
end

-- Get top scores
function Leaderboard.GetTop(category, limit, callback)
    category = category or "default"
    limit = limit or 10

    Leaderboard.scores:AggregateAsync({
        { ["$match"] = { category = category } },
        { ["$sort"] = { score = -1 } },
        { ["$limit"] = limit },
        {
            ["$project"] = {
                steamid = 1,
                username = 1,
                score = 1,
                submitted_at = 1,
                _id = 0
            }
        }
    }, function(err, results)
        callback(results or {})
    end)
end

-- Get player rank
function Leaderboard.GetRank(steamid, category, callback)
    category = category or "default"

    Leaderboard.scores:FindOneAsync(
        { steamid = steamid, category = category },
        function(err, player)
            if not player then
                callback(nil, 0)
                return
            end

            -- Count players with higher scores
            Leaderboard.scores:CountAsync(
                { category = category, score = { ["$gt"] = player.score } },
                function(err, higher)
                    callback(higher + 1, player.score)
                end
            )
        end
    )
end

-- Get player's scores in all categories
function Leaderboard.GetPlayerScores(steamid, callback)
    Leaderboard.scores:FindAsync(
        { steamid = steamid },
        100,
        function(err, results)
            callback(results or {})
        end
    )
end

-- Get leaderboard with ranks around player
function Leaderboard.GetAroundPlayer(steamid, category, range, callback)
    category = category or "default"
    range = range or 5

    Leaderboard.GetRank(steamid, category, function(rank, score)
        if not rank then
            callback({}, nil)
            return
        end

        local skip = math.max(0, rank - range - 1)

        Leaderboard.scores:AggregateAsync({
            { ["$match"] = { category = category } },
            { ["$sort"] = { score = -1 } },
            { ["$skip"] = skip },
            { ["$limit"] = range * 2 + 1 },
            {
                ["$project"] = {
                    steamid = 1,
                    username = 1,
                    score = 1,
                    _id = 0
                }
            }
        }, function(err, results)
            -- Add rank numbers
            for i, entry in ipairs(results) do
                entry.rank = skip + i
            end
            callback(results, rank)
        end)
    end)
end

return Leaderboard

Achievement System

--[[
    Achievement System
    Track and award player achievements
]]

local Achievements = {}

-- Define achievements
Achievements.Definitions = {
    first_kill = {
        name = "First Blood",
        description = "Get your first kill",
        points = 10
    },
    kill_streak_5 = {
        name = "On Fire",
        description = "Get a 5 kill streak",
        points = 25
    },
    level_10 = {
        name = "Rising Star",
        description = "Reach level 10",
        points = 50
    },
    level_50 = {
        name = "Veteran",
        description = "Reach level 50",
        points = 100
    },
    playtime_10h = {
        name = "Dedicated",
        description = "Play for 10 hours",
        points = 75
    },
    credits_100k = {
        name = "Rich",
        description = "Accumulate 100,000 credits",
        points = 100
    }
}

function Achievements.Initialize(db)
    Achievements.db = db
    Achievements.col = db:Collection("player_achievements")

    Achievements.col:CreateIndex({ steamid = 1 }, false, "steamid_index")
end

-- Check if player has achievement
function Achievements.Has(steamid, achievementId, callback)
    Achievements.col:FindOneAsync(
        { steamid = steamid },
        function(err, doc)
            if doc and doc.achievements then
                for _, ach in ipairs(doc.achievements) do
                    if ach.id == achievementId then
                        callback(true, ach)
                        return
                    end
                end
            end
            callback(false, nil)
        end
    )
end

-- Award achievement
function Achievements.Award(steamid, achievementId, callback)
    local def = Achievements.Definitions[achievementId]
    if not def then
        if callback then callback(false, "Invalid achievement") end
        return
    end

    -- Check if already has it
    Achievements.Has(steamid, achievementId, function(has)
        if has then
            if callback then callback(false, "Already earned") end
            return
        end

        local achievement = {
            id = achievementId,
            name = def.name,
            points = def.points,
            earned_at = os.time()
        }

        Achievements.col:UpdateOneAsync(
            { steamid = steamid },
            {
                ["$push"] = { achievements = achievement },
                ["$inc"] = { total_points = def.points }
            },
            function(err)
                if callback then callback(not err, achievement) end
            end
        )
    end)
end

-- Get player achievements
function Achievements.GetAll(steamid, callback)
    Achievements.col:FindOneAsync(
        { steamid = steamid },
        function(err, doc)
            if doc then
                callback(doc.achievements or {}, doc.total_points or 0)
            else
                callback({}, 0)
            end
        end
    )
end

-- Get achievement progress/stats
function Achievements.GetProgress(steamid, callback)
    Achievements.GetAll(steamid, function(earned, points)
        local total = 0
        local totalPoints = 0

        for _, def in pairs(Achievements.Definitions) do
            total = total + 1
            totalPoints = totalPoints + def.points
        end

        callback({
            earned = #earned,
            total = total,
            points = points,
            max_points = totalPoints,
            percentage = (#earned / total) * 100
        })
    end)
end

-- Check achievements based on player stats
function Achievements.CheckAndAward(steamid, playerData, callback)
    local awarded = {}

    local function checkNext(checks, index)
        if index > #checks then
            callback(awarded)
            return
        end

        local check = checks[index]
        if check.condition then
            Achievements.Award(steamid, check.id, function(success, ach)
                if success then
                    table.insert(awarded, ach)
                end
                checkNext(checks, index + 1)
            end)
        else
            checkNext(checks, index + 1)
        end
    end

    local checks = {
        { id = "first_kill", condition = playerData.stats.kills >= 1 },
        { id = "level_10", condition = playerData.level >= 10 },
        { id = "level_50", condition = playerData.level >= 50 },
        { id = "playtime_10h", condition = playerData.playtime >= 36000 },
        { id = "credits_100k", condition = playerData.credits >= 100000 }
    }

    checkNext(checks, 1)
end

return Achievements

Matchmaking System

--[[
    Simple Matchmaking System
    Queue players and create balanced matches
]]

local Matchmaking = {}

function Matchmaking.Initialize(db)
    Matchmaking.db = db
    Matchmaking.queue = db:Collection("matchmaking_queue")
    Matchmaking.matches = db:Collection("matches")

    -- Index for efficient queries
    Matchmaking.queue:CreateIndex({ skill_rating = 1 }, false, "skill_index")
    Matchmaking.queue:CreateIndex({ queued_at = 1 }, false, "queue_time")
end

-- Add player to queue
function Matchmaking.JoinQueue(steamid, username, skillRating, callback)
    -- Remove from queue if already in it
    Matchmaking.queue:DeleteOneAsync({ steamid = steamid }, function()
        -- Add to queue
        Matchmaking.queue:InsertOneAsync({
            steamid = steamid,
            username = username,
            skill_rating = skillRating,
            queued_at = os.time()
        }, function(err)
            if callback then callback(not err) end
        end)
    end)
end

-- Remove player from queue
function Matchmaking.LeaveQueue(steamid, callback)
    Matchmaking.queue:DeleteOneAsync(
        { steamid = steamid },
        function(err, deleted)
            if callback then callback(deleted > 0) end
        end
    )
end

-- Find match for player
function Matchmaking.FindMatch(steamid, skillRating, skillRange, callback)
    skillRange = skillRange or 100

    Matchmaking.queue:FindAsync({
        steamid = { ["$ne"] = steamid },
        skill_rating = {
            ["$gte"] = skillRating - skillRange,
            ["$lte"] = skillRating + skillRange
        }
    }, 100, function(err, candidates)
        if #candidates == 0 then
            callback(nil, "No players in range")
            return
        end

        -- Sort by closest skill rating
        table.sort(candidates, function(a, b)
            return math.abs(a.skill_rating - skillRating) <
                   math.abs(b.skill_rating - skillRating)
        end)

        callback(candidates[1])
    end)
end

-- Create a match
function Matchmaking.CreateMatch(player1, player2, callback)
    local match = {
        players = {
            {
                steamid = player1.steamid,
                username = player1.username,
                skill_rating = player1.skill_rating
            },
            {
                steamid = player2.steamid,
                username = player2.username,
                skill_rating = player2.skill_rating
            }
        },
        status = "pending",
        created_at = os.time(),
        skill_diff = math.abs(player1.skill_rating - player2.skill_rating)
    }

    -- Remove players from queue
    Matchmaking.queue:DeleteManyAsync({
        steamid = { ["$in"] = { player1.steamid, player2.steamid } }
    }, function()
        -- Create match record
        Matchmaking.matches:InsertOneAsync(match, function(err, matchId)
            if err then
                callback(nil, err)
            else
                match._id = matchId
                callback(match)
            end
        end)
    end)
end

-- Get queue statistics
function Matchmaking.GetQueueStats(callback)
    Matchmaking.queue:AggregateAsync({
        {
            ["$group"] = {
                _id = nil,
                count = { ["$sum"] = 1 },
                avg_skill = { ["$avg"] = "$skill_rating" },
                min_skill = { ["$min"] = "$skill_rating" },
                max_skill = { ["$max"] = "$skill_rating" },
                avg_wait = { ["$avg"] = {
                    ["$subtract"] = { os.time(), "$queued_at" }
                }}
            }
        }
    }, function(err, results)
        if results and #results > 0 then
            callback(results[1])
        else
            callback({
                count = 0,
                avg_skill = 0,
                min_skill = 0,
                max_skill = 0,
                avg_wait = 0
            })
        end
    end)
end

-- Process queue and create matches
function Matchmaking.ProcessQueue(callback)
    local created = 0

    Matchmaking.queue:FindAsync({}, 1000, function(err, players)
        if #players < 2 then
            callback(0)
            return
        end

        -- Sort by queue time
        table.sort(players, function(a, b)
            return a.queued_at < b.queued_at
        end)

        local function processNext(index)
            if index > #players then
                callback(created)
                return
            end

            local player = players[index]

            -- Check if still in queue
            Matchmaking.queue:FindOneAsync({ steamid = player.steamid }, function(err, stillQueued)
                if not stillQueued then
                    processNext(index + 1)
                    return
                end

                -- Find match
                Matchmaking.FindMatch(player.steamid, player.skill_rating, 200, function(opponent)
                    if opponent then
                        Matchmaking.CreateMatch(player, opponent, function(match)
                            if match then
                                created = created + 1
                                print("Created match:", player.username, "vs", opponent.username)
                            end
                            processNext(index + 1)
                        end)
                    else
                        processNext(index + 1)
                    end
                end)
            end)
        end

        processNext(1)
    end)
end

return Matchmaking

Usage Example

-- Initialize systems
require("mongo")

local client = MongoDB.Client("mongodb://localhost:27017")
local db = client:Database("gameserver")

Leaderboard.Initialize(db)
Achievements.Initialize(db)
Matchmaking.Initialize(db)

-- Submit a score
Leaderboard.SubmitScore("STEAM_0:1:12345", "Player1", 1500, "kills", function(success)
    print("Score submitted:", success)
end)

-- Get top 10
Leaderboard.GetTop("kills", 10, function(top)
    print("=== Top 10 ===")
    for i, entry in ipairs(top) do
        print(i .. ". " .. entry.username .. " - " .. entry.score)
    end
end)

-- Award achievement
Achievements.Award("STEAM_0:1:12345", "first_kill", function(success, ach)
    if success then
        print("Achievement unlocked:", ach.name)
    end
end)

-- Matchmaking
Matchmaking.JoinQueue("STEAM_0:1:12345", "Player1", 1500)
Matchmaking.JoinQueue("STEAM_0:1:67890", "Player2", 1450)

timer.Create("ProcessMatchmaking", 5, 0, function()
    Matchmaking.ProcessQueue(function(created)
        if created > 0 then
            print("Created", created, "matches")
        end
    end)
end)

Next Steps