Game mechanics¶
This page details the core gameplay mechanics implemented on the server side:
vision (Look), inventory, incantation / elevation, broadcast
and ejection. The algorithms live in server/srcs/algorithms/; incantation
orchestration in server/srcs/core/IncantationHandler.cpp.
Vision — Look¶
VisionAlgo::getTiles builds the player's vision cone, for depths
0..level, with 2*depth+1 tiles per row, oriented by Direction
(server/srcs/algorithms/VisionAlgo.cpp:6-24):
for (int depth = 0; depth <= p.level; depth++) {
for (int offset = -depth; offset <= depth; offset++) {
int x = p.x, y = p.y;
if (p.direction == Direction::North) { x = p.x + offset; y = p.y - depth; }
if (p.direction == Direction::South) { x = p.x - offset; y = p.y + depth; }
if (p.direction == Direction::East) { x = p.x + depth; y = p.y + offset; }
if (p.direction == Direction::West) { x = p.x - depth; y = p.y - offset; }
tiles.push_back(&world.getTile(x, y));
}
}
Why a widening cone
Row 0 contains only the current tile. Each subsequent row advances one
step in the gaze direction and widens by two tiles. The player's level sets
the range: a level-N player sees (N+1)² tiles. Since world.getTile is
toroidal, the edges wrap around.
formatLook serialises tiles in brackets, separated by ,
(VisionAlgo.cpp:56-69). Each tile first lists one player token per present
player, then each resource repeated by its quantity, all space-separated
(VisionAlgo.cpp:34-54):
Example: [player, , linemate, food food, ...]. See
AI ↔ Server protocol for the exact grammar.
Inventory¶
CmdInventory::execute returns the list of resources in the player's inventory
(server/srcs/commands/CmdInventory.cpp:9-19):
food multiplied by 126
Food is reported × 126 (CmdInventory.cpp:12), not in raw units. It is
a conversion into remaining ticks, inconsistent with the GUI pin event.
See Known limitations.
Incantation / elevation¶
An incantation raises a group of players to the next level if the conditions are met on the tile.
Requirements table¶
IncantationAlgo::TABLE indexes requirements by level-1
(server/srcs/algorithms/IncantationAlgo.cpp:5-13). Each row gives the number
of same-level players required and the number of stones to gather on the
tile.
| Elevation | Players | linemate | deraumere | sibur | mendiane | phiras | thystame |
|---|---|---|---|---|---|---|---|
| 1 → 2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
| 2 → 3 | 2 | 1 | 1 | 1 | 0 | 0 | 0 |
| 3 → 4 | 2 | 2 | 0 | 1 | 0 | 2 | 0 |
| 4 → 5 | 4 | 1 | 1 | 2 | 0 | 1 | 0 |
| 5 → 6 | 4 | 1 | 2 | 1 | 3 | 0 | 0 |
| 6 → 7 | 6 | 1 | 2 | 3 | 0 | 1 | 0 |
| 7 → 8 | 6 | 2 | 2 | 2 | 2 | 2 | 1 |
Double validation¶
IncantationAlgo::check verifies there are enough players at the exact level
on the tile, then that each stone is present in sufficient quantity
(IncantationAlgo.cpp:15-38). This check runs twice: at the start and
at the end (re-validation), to prevent cheating during the 300-tick wait.
execute consumes the stones and increments each participant's level
(IncantationAlgo.cpp:40-57).
Flow — IncantationHandler¶
Startup is driven by IncantationHandler::start
(server/srcs/core/IncantationHandler.cpp:14-189). CmdIncantation::execute is
itself a no-op: all logic is in the handler.
- Gather on the tile the players that are same-level and not busy
(
isBusy() == false) —IncantationHandler.cpp:34-48. check; on failure, replykoand abort.- Success:
isIncanting = truefor all, the initiator becomesbusy, each participant receivesElevation underway, broadcastpicto GUIs (IncantationHandler.cpp:57-73). - Schedule completion after 300 ticks (
cmd->getDelay()). - At deadline: re-resolve players by
id, verify none moved, died or changed tile, then re-validatecheck(IncantationHandler.cpp:126-144). - Success:
executeconsumes the stones andlevel++; each participant receivesCurrent level: N; broadcastpie 1,bct,plv(IncantationHandler.cpp:146-173). - Failure at re-validation:
isIncanting = false,koto all, broadcastpie 0(IncantationHandler.cpp:100-124).
flowchart TD
A["Incantation dequeued"] --> B["Gather players<br/>same level, not busy"]
B --> C{"check() ?"}
C -->|no| K1["ko"]
C -->|yes| D["isIncanting = true<br/>Elevation underway<br/>broadcast pic"]
D --> E["Wait 300 ticks"]
E --> F{"Players intact<br/>+ re-check() ?"}
F -->|no| K2["ko + pie 0"]
F -->|yes| G["execute: -stones, level++"]
G --> H["Current level: N<br/>broadcast pie 1 / bct / plv"]
H --> I{"≥ 6 players level 8 ?"}
I -->|yes| W["seg + stop"]
I -->|no| Z["end"]
Win condition¶
After a successful elevation, if the team counts ≥ 6 level-8 players, the
server broadcasts seg <team> and schedules shutdown
(_stopWhenFlushed = true, IncantationHandler.cpp:161-176).
bool gameWon = countLevel8Players(initiator->teamName) >= 6;
...
if (gameWon) protocol.sendSeg(winningTeam);
...
if (gameWon) stopWhenFlushed = true;
Broadcast¶
When a player emits Broadcast <text>, every other player receives the message
together with a sound direction K (0..8) indicating where the sound comes
from relative to its orientation (0 = same tile).
BroadcastAlgo::computeK computes the shortest toroidal vector from the
emitter to the receiver, the angle atan2(dy, dx), shifts it by the receiver's
orientation, then rounds onto the 8 sectors
(server/srcs/algorithms/BroadcastAlgo.cpp:14-57):
if (emitter.x == receiver.x && emitter.y == receiver.y)
return 0; // same tile
...
double alpha = std::atan2(dy, dx); // receiver -> emitter vector
// theta from receiver direction (N=-pi/2, E=0, S=pi/2, W=pi)
double rel_alpha = alpha - theta;
int index = (int)std::round(-rel_alpha / (M_PI / 4));
int k = 1 + index; // wrapped into 1..8
CmdBroadcast::execute iterates over all tiles and computes K for each
other player (server/srcs/commands/CmdBroadcast.cpp:15-29). The server then
sends each receiver message K, <text> and broadcasts pbc to the GUIs
(server/srcs/core/Server.cpp:344-364).
K reference frame
K = 1 corresponds to a sound coming from the front; indices increase
counter-clockwise. K = 0 is reserved for the "emitter on the same tile"
case. The toroidal projection guarantees the sound is always heard via the
shortest path.
Ejection — Eject¶
Eject pushes all other players present on the tile one cell in the
ejector's gaze direction (toroidal move), and succeeds if it pushed at least
one player (server/srcs/commands/CmdEject.cpp:21-53):
int dx = 0, dy = 0;
if (player.direction == Direction::North) dy = -1;
if (player.direction == Direction::South) dy = 1;
if (player.direction == Direction::East) dx = 1;
if (player.direction == Direction::West) dx = -1;
...
for (Player* p : this->_ejectedPlayers) {
currentTile.removePlayer(p);
p->x = ((p->x + dx) % w + w) % w;
p->y = ((p->y + dy) % h + h) % h;
world.getTile(p->x, p->y).addPlayer(p);
}
_success = !this->_ejectedPlayers.empty();
On the server side (server/srcs/core/Server.cpp:268-309), for each victim an
eject: K is sent (K computed by ejectDirectionToK from the push direction
and the victim's direction), then all eggs on the tile are destroyed
(edi + delete), and pex (ejector) plus ppo (moved victims) are
broadcast.
Eggs destroyed by ejection
Any egg present on the tile at ejection time is removed, reducing the connection slots of the affected team accordingly. See Players, teams & eggs and Known limitations.
See also¶
- Players, teams & eggs — entities handled by these mechanics.
- Commands & delays — tick cost of each action.
- AI ↔ Server protocol — exact wire formats.