course n°12 · Découverte

The Multiplayer ARPG in Your Browser

A built world, a database that remembers, a server that answers, a websocket that pushes — and both your players in the same tab.

~11 min7 stations
▶ video🧩 exercises📄 cheat sheet

42 seconds · English captions included · 8.5 Mo

The 7 things you'll know how to do

Lesson 12: The Multiplayer ARPG

Regarde la leçon (42 s) : un monde bâti, une base qui se souvient, un serveur qui répond, un websocket qui pousse — quatre étapes, et ton ARPG tourne dans un onglet.

Build the world, cell by cell

Écris `cellule(x, y, sol)` : une case du monde — un Hash avec `"x"`, `"y"` et `"sol"` (ex: `"herbe"`). Le monde 8-bit commence par UNE case.

The database that remembers

Écris `charge(rows, id)` : lire la base — `rows` est un tableau de lignes Hash (avec `"id"`), renvoie LA ligne dont l'id vaut `id`, sinon `nil`. Sans ce lire, la partie recommence à zéro.

The server answers routes

Écris `route(chemin)` : le petit routeur — `"/joueurs"` → `:joueurs` · `"/monde"` → `:monde` · `"/parties"` → `:parties` · tout autre chemin → `nil`. Le serveur ne fait QUE ça : un chemin, un symbole.

The websocket pushes the moves

Écris `evenement(msg)` : le serveur reçoit `msg` (un Hash avec `"type"`) — `"tire"` → `:inflige` · `"parle"` → `:diffuse` · tout autre type → `:ignore`. Le websocket ne fait que ça : type → action.

Deliver to the RIGHT actor

Écris `remets?(element, acteur)` : un élément venu de la base porte `"pour"` (un id) et `"role"` (`"pnj"` ou `"joueur"`) ; l'acteur porte `"id"` et `"role"`. true SEULEMENT si l'id correspond ET le rôle correspond — au bon joueur, au bon PNJ.

Lesson 12: OK

Sans aide : `transfert(rows, id, acteur)` — 1) cherche la ligne `id` dans la base `rows` (absente → `false`) · 2) livre seulement si `"pour"` == `"id"` de l'acteur ET `"role"` == `"role"` de l'acteur. Le reste, c'est `false`.

Share the lesson

← all courses

📄 The cheat sheet

print it, stick it on your monitor, pretend it was your idea

📄 Cheat sheet — The Multiplayer ARPG in your browser (world · database · flow · websocket)

An 8-bit multiplayer ARPG in a tab is four moves: build the world, remember it in a database, answer calls on routes, push events over websocket — then deliver every element to the RIGHT actor.

The 4 moves of the journey

cell → database → route → websocket        (then: delivery to the right actor)
cellule()  charge()  route()  evenement()

1. The world: cells, not an engine

def cellule(x, y, sol)
  { "x" => x, "y" => y, "sol" => sol }
end
cellule(2, 5, "herbe")   # => {"x"=>2, "y"=>5, "sol"=>"herbe"}

The whole world = an array of these hashes (lessons 6 + 8, revisited). The tileset comes later — data first.

2. Memory: the database that remembers

def charge(rows, id)
  rows.find { |r| r["id"] == id }   # the row… or nil
end

Between two sessions, the database is the only thing that survives the tab. You load, you play, you save: three calls, and the world has a memory.

3. The flow: one path, one symbol

case chemin
when "/joueurs"  then :joueurs
when "/monde"    then :monde
when "/parties"  then :parties
else nil                 # unknown: the server says no
end

A request is nothing else: the client shouts a path, the server answers with a symbol (or nil — an honest 404).

4. The websocket: type → action, pushed

case msg["type"]
when "tire"  then :inflige     # the server applies…
when "parle" then :diffuse     # …and BROADCASTS to the others
else :ignore                   # not gameplay: we bin it
end

The difference with the route: the server speaks first. The socket stays open, the moves go to everyone in real time.

5. Delivery: to the right actor, never "almost"

def remets?(element, acteur)
  element["pour"] == acteur["id"] && element["role"] == acteur["role"]
end

An element from the database carries its destination (pour) and its audience (role: "pnj" or "joueur"). Both conditions — id alone delivers to the wrong NPC, role alone to anyone. That's the multiplayer sorting &&.

The full turn (what your server does per message)

def transfert(rows, id, acteur)
  el = rows.find { |r| r["id"] == id }   # 1. the database
  return false unless el                # 2. nothing found → nothing leaves
  el["pour"] == acteur["id"] && el["role"] == acteur["role"]   # 3. right recipient
end

Database → lookup → double condition → delivered. Five lines of Ruby for a whole MMO backend. (The real one adds persistence, secret ids, latency — but the shape is this.)

Never say again

  • "you need a game engine for multiplayer" → a database, a route, a socket
  • "the server decides everything" → it decides WHAT IS TRUE: the database says who has what
  • "websocket = real-time magic" → a case/when on messages, pushed in a loop

SlopCourse Microduck — from slop to clean code.