AlgoDaily

Lesson 4: Game Server - Basics

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

Server Takes Authority

From connected client to real game

In Lesson 2: Game Client - Basics, we built a local client loop with world state and input-as-intent. In Lesson 3: Game Client - Networking, we added the networking boundary, packet framing, and thread-safe handoff.

Now we build the other half: the game server.

For an MMORPG, the server is not just a relay. It is the source of truth for:

  • who is connected
  • where entities really are
  • which actions are legal
  • what world updates each client receives

The client may render beautifully, predict movement, and eventually include god-mode editor tools. But the server decides what actually happened.

Server Takes Authority

Server Responsibilities

What belongs on the server?

Think of the server as four cooperating layers:

[ Network I/O ]  receives bytes, sends bytes
      ↓
[ Sessions ]     tracks connected players
      ↓
[ Simulation ]   advances authoritative world state
      ↓
[ Rules ]        validates actions and applies game logic

For this lesson, we keep the server in-memory and focused on basics. We are not saving accounts, maps, or characters yet — that belongs to Lesson 5: Game Server - Persistence.

A useful Java/Netty comparison: the network layer is like your channel pipeline, sessions resemble connection-scoped state, and the simulation/rules layer is your domain model. The difference is that game servers usually run on a strict tick cadence instead of purely request/response timing.

Server Responsibilities

Authoritative Tick

The server advances time

In the client, the main loop rendered frames and handed off network messages. On the server, the equivalent heartbeat is the simulation tick.

At each tick, the server usually:

  1. drains queued client messages
  2. validates player intent
  3. mutates authoritative world state
  4. emits snapshots/events to clients

Below is a tiny standalone simulation. Notice that the client does not set its final position. It sends desired movement, and the server clamps it to legal speed.

```cpp
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>

struct MoveIntent {
    int playerId;
    float desiredDx;
};

struct Player {
    int id;
    float x;
};

int main() {
    Player player{42, 0.0f};
    std::queue<MoveIntent> inbox;

    inbox.push({42, 3.5f});   // too fast; server will clamp
    inbox.push({42, -0.4f});  // legal movement

    const float maxMovePerTick = 1.0f;

    for (int tick = 1; tick <= 2; ++tick) {
        std::cout << "Tick " << tick << "\n";

        if (!inbox.empty()) {
            MoveIntent intent = inbox.front();
            inbox.pop();

            float legalDx = std::clamp(intent.desiredDx, -maxMovePerTick, maxMovePerTick);
            player.x += legalDx;

            std::cout << "  player " << intent.playerId
                      << " requested dx=" << intent.desiredDx
                      << ", applied dx=" << legalDx << "\n";
        }

        std::cout << "  authoritative x=" << player.x << "\n";
    }
}
```

Authority Check

A client sends a packet saying: myPosition = (9999, 9999).

What should an authoritative MMORPG server usually do?

Sessions And Players

Connections are not characters

A common early mistake is to blur these together:

  • Connection/session: a live network link to a client
  • Account: a long-term identity, not covered yet
  • Character/player entity: something inside the world simulation

For this lesson, we only need a minimal session table: when a connection joins, assign a session id; when it leaves, remove it.

This keeps the network boundary clean, just like we separated subsystems in Lesson 2.

```cpp
#include <iostream>
#include <string>
#include <unordered_map>

struct Session {
    int sessionId;
    std::string remoteAddress;
};

class SessionManager {
public:
    int add(const std::string& remoteAddress) {
        int id = nextId_++;
        sessions_[id] = Session{id, remoteAddress};
        std::cout << "connected session=" << id
                  << " from " << remoteAddress << "\n";
        return id;
    }

    void remove(int id) {
        auto erased = sessions_.erase(id);
        std::cout << "disconnect session=" << id
                  << " removed=" << (erased ? "yes" : "no") << "\n";
    }

    void list() const {
        std::cout << "active sessions: " << sessions_.size() << "\n";
        for (const auto& [id, session] : sessions_) {
            std::cout << "  #" << id << " " << session.remoteAddress << "\n";
        }
    }

private:
    int nextId_ = 1;
    std::unordered_map<int, Session> sessions_;
};

int main() {
    SessionManager sessions;
    int alice = sessions.add("127.0.0.1:50100");
    int bob = sessions.add("127.0.0.1:50101");

    sessions.list();
    sessions.remove(alice);
    sessions.list();

    (void)bob;
}
```

Thread Per Player?

True or false:

A good default MMORPG server design is to create one dedicated simulation thread per connected player.

Message Dispatch

Bytes become commands

In Lesson 3, the client learned packet framing: network bytes must be grouped into complete messages.

Once a full message reaches the server, the next step is dispatch:

raw packet → decoded message → handler → validated world mutation

For now, we can model messages with simple strings. Later, you might use binary packets, schemas, or generated serializers — but the routing idea stays the same.

```cpp
#include <iostream>
#include <sstream>
#include <string>

void handleLogin(int sessionId, const std::string& name) {
    std::cout << "session " << sessionId << " login name=" << name << "\n";
}

void handleMove(int sessionId, int dx, int dy) {
    std::cout << "session " << sessionId << " move intent dx=" << dx
              << " dy=" << dy << "\n";
}

void handlePacket(int sessionId, const std::string& packet) {
    std::istringstream in(packet);
    std::string type;
    in >> type;

    if (type == "LOGIN") {
        std::string name;
        in >> name;
        handleLogin(sessionId, name);
    } else if (type == "MOVE") {
        int dx = 0;
        int dy = 0;
        in >> dx >> dy;
        handleMove(sessionId, dx, dy);
    } else if (type == "PING") {
        std::cout << "session " << sessionId << " pong\n";
    } else {
        std::cout << "session " << sessionId << " unknown packet: " << packet << "\n";
    }
}

int main() {
    handlePacket(7, "LOGIN Ranger");
    handlePacket(7, "MOVE 1 0");
    handlePacket(7, "PING");
    handlePacket(7, "CAST_FIREBALL");
}
```

Intent Vocabulary

In an authoritative server model, the client should send movement _____ rather than directly setting final world positions.

World Ownership

The world lives on the server

Even in this basic version, the server should own the world model:

  • player entities
  • monster entities
  • map zones or regions
  • temporary events like attacks, movement, and spawns

The client owns presentation: rendering, animation, input collection, prediction, and UI.

A helpful split:

Client: "I want to move north."
Server: "You moved 1 tile north; nearby players and monsters are now..."

This split is what will eventually let the same client support both normal play and god-mode editor workflows without letting arbitrary clients rewrite the live world.

World Ownership

Server Foundation

What we built conceptually

You now have the basic mental model for an MMORPG server:

  • clients connect as sessions
  • sessions send framed messages
  • messages become validated commands
  • the server tick mutates authoritative state
  • clients receive the results, not permission to invent reality

This lesson intentionally stayed in memory and avoided durable storage. Next, in Lesson 5: Game Server - Persistence, we will add the ability to save and reload important server data without turning the simulation loop into a database tutorial.

Server Foundation

Open interactive view →