How to Build Your Own GridMind Client


Every team builds their own client — there's no shared default. The PYNQ_302-Referee_Match_Client notebook is a working reference implementation, not something everyone just runs as-is: start from it, extend it, or ignore it entirely and write your own from scratch in whatever language you like. This page is the part every team needs, regardless of approach — not an edge case for teams doing something unusual.

There are three layers to a working client, in order of how load-bearing they are:

1. Peer-to-peer transport — required. Your board needs some way to send and receive messages to the referee. The reference client uses the pynqp2p library's four functions: register(ip, key) (once, at startup), get_id() (your board's MAC address, used as your team's identity), send(id, message) (POST a JSON string to a recipient), and receive_all() (GET every message currently queued for you, as a list of raw JSON strings). Whatever language or library you use instead, you're talking to the same relay-server HTTP endpoints (/send, /receive_all) one way or another — this layer is mandatory for any client to function at all.
2. The GridMind wire protocol — required. The JSON message types in the flowchart below are what the referee actually sends and expects — this is the real contract, independent of pynqp2p or the reference notebook. Any client, in any language, must produce and consume these exact message shapes to play a match.
3. Genesis simulated-arm integration — optional, cosmetic. If wired up, pynqsim.SimulationClient's join_competition(team_id) / flip_card(row, col) / end_turn() mirror your flips onto a simulated robot arm for spectators. Purely visual — it never affects scoring and is safe to skip entirely.

The flow below covers layer 2, the part that's actually mandatory. Function names shown (flip_both, report_result, request_hint) are the reference notebook's own helper methods around pynqp2p.send() — if you're writing your own client, send the same wire messages however your code is structured; the names don't have to match.

Pre-Game
Referee → both teams: pregame_riddle — no response expected; solving it is entirely up to your team (the reference notebook auto-solves it with an LLM, but that's optional)
Referee → both teams: free_hint_fragment × N — assemble these yourself as they arrive; complete once every index from 0 to total - 1 has been seen
Referee → both teams: game_start — requires your client to have already sent a join message to the master's lobby (via pynqp2p.send()) before this arrives, so the operator can confirm your board
Active Team's Turn vs. Waiting Team

Active Team

Receive your_turn
Send flip_both (team, pos1, pos2) — the two grid positions your vision system chose to flip. Valid only as the first action of a turn — sending it after you've already flipped once this turn is a protocol error.
Receive card_revealed × 2
(optional) Send hint_request (team, object) — resolves immediately, but the request takes time off your own turn clock
Send report_result (team, pos1, pos2, cls1, cls2, claim) — your model's classification of both cards and whether you're claiming a match
Receive match (streak continues, back to top) or no_match (turn ends)
Receive your_turn again (streak) or wait (turn passed)

Waiting Team

Receive wait
Passively receive the same card_revealed × 2 from the active team's flips — your client should log these to its own memory even though it's not your turn
(recommended) Send hint_request now — queued server-side, resolves via hint_response/hint_rejected the instant your turn starts, before your clock begins
Receive your_turn when the active team's turn ends
End of Match
Referee → both teams: game_over — final scores, no further messages expected