Networking and Communication
This page focuses on the data serialisation and listener protocol used by Seahorse. For the overall architecture, see the Architecture page.
JSON serialisation
All game states, players and actions must be converted to JSON-like structure before being sent over the network.
Seahorse provides a Serializable base class that every core object inherits from.
When you create a custom game state or action, you must implement two methods:
to_json– returns a JSON‑serialisable dictionary (or string) representing the object.from_json– a class method that reconstructs the object from JSON data.
The event hub automatically calls these methods when broadcasting and receiving data. This allows a generic method for data transmition applied for any game implementation as long as they can parse JSON and understand the expected structure.
Example
A StatelessAction stores a simple data dictionary.
Its to_json() returns that dictionary; from_json creates a new instance with it.
Listeners (spectators, recorders)
Any EventSlave can connect to the event hub without being a player. These listeners receive all broadcast events but never send actions. Common use cases:
- StateRecorder – logs every
"play"(game state) and"done"(final results) to a file for later replay or analysis. - Spectator GUI – displays the game live.
- Analytics tool – collects statistics on the fly.
Tip
At the time this documentation is written, only StateRecorder is available and implemented through Seahorse package. Other types of listeners are just a suggestion that you can create for your game. Any contribution regarding other listeners implementation in Seahorse are welcomed.
Implementing a custom listener
To create a listener, subclass EventSlave and override the event handlers you care about.
Event handlers hooks are leveraged through SocketIO clients and their on method.
Custom file save recorder
Create subclass MyRecorder
from seahorse.game.io_stream import EventSlave
class MyRecorder(EventSlave):
@self.sio.on("play")
async def on_play(self, data):
# data contains the serialised game state
save_to_file(data)
@self.sio.on("done")
async def on_done(self, data):
save_final_results(data)
Then, register your listener when starting the game:
recorder = MyRecorder()
# Activate and register the recorder when master start the game
master.record_game(listeners=[recorder])
The listener will automatically receive all "play" and "done" events.
Event names and payload structure
The event hub uses the following standard event names:
| Event | Direction | Target | Payload | Description |
|---|---|---|---|---|
identify |
Client → Hub | Hub only | {"identifier": str} |
Sent by a proxy to identify itself. |
update_id |
Hub → Client | Specific client | {"new_id": int} |
Sent by the master to synchronise a player's ID. |
play |
Master → Hub → Clients | Active player (specific) + all listeners (broadcast) | Serialised game state | Broadcast when a new state is available. |
action |
Client → Hub → Master | Hub and Master only (specific) | Serialized action and elapsed time | Returned by a proxy after computing a move. |
done |
Master → Hub → Clients | Broadcast to all | Endgame informations | Sent when the game ends. |
interact |
Interface → Hub → InteractivePlayerProxy | Specific interactive proxy | Humain interface input data | Used only for human players. |
All payloads are JSON strings or tuples of JSON‑compatible data. The hub does not interpret the content – it only routes messages based on session IDs and event names.
See also
- Architecture – for connection setup, identification, and message flow.
- Agent and Proxies – for how proxies use
EventSlave. - Time Limits and Fairness – for network‑related timeouts.
- API Reference – EventMaster
- API Reference – EventSlave