Skip to main content

FastAPI WebSocket Hex game server with matchmaking, plus reference clients

Project description

Hex Game Server

FastAPI-based Hex game server with WebSocket matchmaking, authoritative game state, win detection, a random-move test client, and a Vite/Tailwind/shadcn-ui frontend with a landing page, operational overview dashboard, and statistics leaderboard.

Packaged as hexgame on PyPI: pip install hexgame gives you the hexgame-server command (the FastAPI server) and the hexgame command (random, play, and gui clients).

The current implementation covers Phases 1-7 from PLAN.md:

  • Fixed in-memory game slots.
  • Board-size-aware matchmaking.
  • Best-of match series with configurable odd series lengths.
  • WebSocket real-time gameplay.
  • Server-authoritative move validation and turn tracking.
  • Hex win detection.
  • / project landing page, /docs usage guide, /overview monitoring page, and /statistics model leaderboard.
  • Model-driven clients, including a pygame GUI client for visual board output.
  • GUI niceties: winning-path highlight in gold, configurable pause between games of a series (--match-delay, SPACE to skip), opponent's model name and username shown in the side panel, version in the window title.
  • Reconnect tokens, /ws/reconnect, and a --reconnect-token CLI flag on the clients for resuming an interrupted match.
  • Optional Redis-backed slot, game, session, and reconnect-token state.
  • Optional PostgreSQL/SQLAlchemy completed-series history.

User accounts and ratings are intentionally not implemented yet.

Requirements

  • Python 3.10+ recommended.
  • Node.js 20+ recommended only if you rebuild the overview frontend yourself.
  • pygame (the [gui] extra) is required for the pygame GUI client.
  • Redis is optional (the [redis] extra). The default backend is in-process memory for local development.
  • PostgreSQL is optional (the [postgres] extra). Completed-series history is disabled unless HEX_DATABASE_URL is set.

Installation

The project is packaged as hexgame. Installing it provides two console commands: hexgame-server (the FastAPI server) and hexgame (the clients).

End users — install from PyPI:

pip install hexgame                 # server + random/model clients
pip install "hexgame[gui]"          # also the pygame GUI client
pip install "hexgame[all]"          # everything: redis, postgres, gui

Developers — editable install from a checkout:

python -m pip install -e ".[dev,all]"
# or, equivalently:
python -m pip install -r requirements.txt

Rebuilding the overview frontend (only if you change frontend/):

cd frontend
npm install
npm run build        # writes to src/hexgame/server/static/overview/

Using The Hosted Server

The model clients default to the hosted arena:

wss://hexgame.codingdojo.ai

That means users can install the package, add or choose a model, and connect directly without running their own FastAPI server:

pip install "hexgame[gui]"
hexgame play --model-name model_random --board-size 7
hexgame gui --model-name human --board-size 7

Open:

  • Landing page: https://hexgame.codingdojo.ai/
  • Documentation: https://hexgame.codingdojo.ai/docs
  • Overview dashboard: https://hexgame.codingdojo.ai/overview
  • Statistics leaderboard: https://hexgame.codingdojo.ai/statistics

Running A Local Server

From the repository root:

hexgame-server --port 8000

Then open:

  • Health: http://127.0.0.1:8000/health
  • Slot state JSON: http://127.0.0.1:8000/slots
  • Landing page: http://127.0.0.1:8000/
  • Documentation: http://127.0.0.1:8000/docs
  • Overview dashboard: http://127.0.0.1:8000/overview
  • Statistics leaderboard: http://127.0.0.1:8000/statistics
  • OpenAPI/Swagger UI: http://127.0.0.1:8000/api/docs

Point clients at the local server with --server:

hexgame play --model-name model_random --board-size 7 --server ws://localhost:8000
hexgame gui --model-name human --board-size 7 --server ws://localhost:8000

hexgame-server is a thin wrapper around uvicorn hexgame.server.main:app; pass --host, --port, --workers, --reload, or --log-level as needed. If WebSocket clients receive HTTP 404 on /ws/matchmake, reinstall the package (pip install -e ".[dev,all]") so uvicorn[standard]/websockets are present, then restart the server.

Docker Compose

Build and run the server with Redis and PostgreSQL:

docker compose up --build

Compose starts:

  • app: FastAPI server on http://127.0.0.1:8000
  • redis: active slot/game/session/reconnect-token state
  • postgres: completed-series history through SQLAlchemy ORM

Stop the stack:

docker compose down

Remove persisted Redis/PostgreSQL data:

docker compose down -v

Redis State Backend

By default, state is stored in memory:

hexgame-server --port 8000

To persist active slot state in Redis and share slot/game/session/reconnect state between server processes, start Redis and run:

HEX_STATE_BACKEND=redis \
HEX_REDIS_URL=redis://127.0.0.1:6379/0 \
hexgame-server --port 8000

Redis stores active slots, board state, series score, public model names, public usernames, connection status, and reconnect tokens. Raw WebSocket objects are never stored in Redis; after a server restart, persisted players are marked disconnected and can return through /ws/reconnect using their reconnect token.

Use HEX_REDIS_KEY_PREFIX to isolate environments that share the same Redis database.

Database History

Completed series can be written through SQLAlchemy ORM to PostgreSQL. Set HEX_DATABASE_URL before starting the server:

HEX_DATABASE_URL=postgresql+psycopg://hex:hex@127.0.0.1:5432/hexgame \
hexgame-server --port 8000

By default, HEX_DATABASE_AUTO_CREATE=1 creates the completed_series table on startup. Set HEX_DATABASE_AUTO_CREATE=0 if migrations or external schema management should own table creation.

The completed-series record stores slot id, board size, series length, winner, score, public model names, public usernames, final board, and the final public slot snapshot. It does not store reconnect tokens or WebSocket objects.

Frontend

The landing page and overview dashboard live in frontend/ and are built with:

  • Vite
  • React
  • TypeScript
  • Tailwind CSS
  • shadcn/ui-style local components

During frontend development:

cd frontend
npm run dev

The Vite dev server proxies /slots and /api/statistics to http://127.0.0.1:8000.

Build the production dashboard:

cd frontend
npm run build

The production build writes to src/hexgame/server/static/overview/. FastAPI serves the landing page at /, the documentation page at /docs, the dashboard at /overview, the leaderboard at /statistics, and assets from /overview/assets/....

API

HTTP

GET /health

Returns:

{"status": "ok"}

GET /slots

Returns the current state of all slots. It is safe for clients and dashboards: it does not include raw WebSocket objects or secrets.

Example:

[
  {
    "slot_id": 1,
    "state": "full",
    "board_size": 11,
    "series_length": 3,
    "player_count": 2,
    "connected_player_count": 2,
    "players": [-1, 1],
    "player_models": {"-1": "model_alphazero", "1": "human"},
    "player_usernames": {"-1": "alice", "1": "bob"},
    "connected_players": [-1, 1],
    "disconnected_players": [],
    "current_turn": -1,
    "winner": null,
    "move_count": 8,
    "board": [[null, -1]],
    "wins_required": 2,
    "current_game_number": 1,
    "player_1_wins": 0,
    "player_2_wins": 0,
    "series_winner": null
  }
]

GET /

Serves the built landing page.

GET /docs

Serves the built project documentation page.

GET /overview

Serves the built dashboard.

GET /statistics

Serves the built model statistics and leaderboard page.

GET /api/statistics

Returns completed-series statistics. If database history is disabled, the response is empty and persistence_enabled is false.

Example:

{
  "persistence_enabled": true,
  "totals": {"matches": 12, "games": 31, "models": 4, "model_entries": 24},
  "leaderboard": [
    {
      "model_name": "model_alphazero",
      "username": "alice",
      "matches": 8,
      "wins": 6,
      "losses": 2,
      "games_won": 14,
      "games_lost": 7,
      "win_rate": 0.75
    }
  ],
  "board_sizes": {"7": 6, "11": 6},
  "series_lengths": {"1": 8, "3": 4},
  "recent_matches": []
}

WebSocket

/ws/matchmake?board_size=11&series_length=3

Allowed board sizes:

7, 9, 11, 13, 19

Allowed series lengths:

1, 3, 5, 7, 9, 11, 13, 15

series_length defaults to 1. The server only matches players who request the same board size and the same series length.

Clients may include model_name and username to display non-secret model labels and owner names in /slots and /overview:

/ws/matchmake?board_size=11&series_length=3&model_name=model_alphazero&username=alice

/ws/join-slot?slot_id=1

Joins a specific waiting slot as player_2. The joining client inherits the board size, series length, wins required, and current score rules already set by player_1 in that slot.

/ws/reconnect?slot_id=1&token=<reconnect_token>

Reconnects a player to a reserved seat after a temporary disconnect. The token is issued only to that client in the joined payload. It is not exposed by /slots or the overview dashboard.

WebSocket Protocol

Every message uses this shape:

{
  "type": "message_type",
  "payload": {}
}

Client To Server

hello

{
  "type": "hello",
  "payload": {
    "protocol_version": 1,
    "client_name": "hex-client"
  }
}

move

Clients send only coordinates. The server assigns the player identity from the WebSocket connection.

{
  "type": "move",
  "payload": {
    "q": 3,
    "r": 5
  }
}

chat

{
  "type": "chat",
  "payload": {
    "message": "Good luck!"
  }
}

resign

{
  "type": "resign",
  "payload": {}
}

ping

{
  "type": "ping",
  "payload": {}
}

Server To Client

joined

{
  "type": "joined",
  "payload": {
    "slot_id": 1,
    "player": -1,
    "color": "red",
    "board_size": 11,
    "series_length": 3,
    "reconnect_token": "client-private-token",
    "protocol_version": 1
  }
}

Store reconnect_token client-side for the current match. Treat it like a short-lived secret: it proves ownership of the reserved seat.

reconnected

{
  "type": "reconnected",
  "payload": {
    "slot_id": 1,
    "player": 1,
    "color": "blue",
    "board_size": 11,
    "series_length": 3,
    "protocol_version": 1,
    "slot": {
      "slot_id": 1,
      "state": "full",
      "connected_players": [-1, 1],
      "disconnected_players": [],
      "current_turn": -1,
      "move_count": 8,
      "board": [[0, -1]],
      "player_models": {"-1": "model_alphazero", "1": "human"},
      "player_usernames": {"-1": "alice", "1": "bob"},
      "current_game_number": 1,
      "player_1_wins": 0,
      "player_2_wins": 0,
      "wins_required": 2
    }
  }
}

The reference clients use player_models / player_usernames from this snapshot to restore the Opponent label after a reconnect.

waiting_for_opponent

{
  "type": "waiting_for_opponent",
  "payload": {
    "slot_id": 1,
    "board_size": 11
  }
}

game_start

{
  "type": "game_start",
  "payload": {
    "slot_id": 1,
    "board_size": 11,
    "series_length": 3,
    "players": [-1, 1],
    "first_turn": -1,
    "current_game_number": 1,
    "player_1_wins": 0,
    "player_2_wins": 0,
    "wins_required": 2,
    "player_models": {"-1": "model_alphazero", "1": "human"},
    "player_usernames": {"-1": "alice", "1": "bob"}
  }
}

player_models and player_usernames are keyed by string player IDs ("-1" and "1"). Empty objects ({}) mean the players are anonymous. Clients should not assume both keys are present — match a side by its string key and fall back to None. This payload is sent both at series start and at the start of each subsequent game in a multi-game series.

move

{
  "type": "move",
  "payload": {
    "player": -1,
    "q": 3,
    "r": 5,
    "next_turn": 1
  }
}

move_rejected

{
  "type": "move_rejected",
  "payload": {
    "reason": "Not your turn"
  }
}

game_over

{
  "type": "game_over",
  "payload": {
    "winner": -1,
    "reason": "connected_sides"
  }
}

series_update

Sent after each completed game in a series.

{
  "type": "series_update",
  "payload": {
    "player_1_wins": 1,
    "player_2_wins": 0,
    "current_game_number": 2,
    "wins_required": 2,
    "series_length": 3
  }
}

series_over

Sent when a player reaches the required number of wins.

{
  "type": "series_over",
  "payload": {
    "winner": -1,
    "player_1_wins": 2,
    "player_2_wins": 0,
    "wins_required": 2,
    "series_length": 3
  }
}

Other server messages:

  • pong
  • chat
  • error
  • opponent_disconnected
  • opponent_reconnected

Player IDs

The protocol uses numeric IDs everywhere a player appears:

-1 = player_1 = red  = left-to-right
 1 = player_2 = blue = top-to-bottom

New clients should treat -1 and 1 as the canonical protocol values. The server still owns identity: clients do not send their player id in move messages, and any extra player field in a move payload is ignored.

Gameplay Rules

  • The current src/hexgame/server/config.py maps PLAYER_1 = -1 and PLAYER_2 = 1.
  • player_1 (-1, red) moves first.
  • A game is one Hex board.
  • A series is best-of 1, 3, 5, 7, 9, 11, 13, or 15 games between the same players.
  • The series ends as soon as a player reaches ceil(series_length / 2) wins.
  • First turn alternates by game number: odd games start with player_1 (-1), even games start with player_2 (1).
  • Coordinates are (q, r).
  • Board access is board[r][q].
  • player_1 (-1, red) wins by connecting left to right.
  • player_2 (1, blue) wins by connecting top to bottom.
  • Neighbors use the axial-like offsets:
(+1, 0), (-1, 0), (0, +1), (0, -1), (+1, -1), (-1, +1)

The server rejects moves when:

  • The game has not started.
  • The game is paused while a disconnected opponent is inside the reconnect window.
  • The game is already finished.
  • It is not the sender's turn.
  • Coordinates are outside the board.
  • The target cell is occupied.
  • The payload is malformed.

Reconnect Behavior

When a player disconnects, the server keeps the slot, game board, series score, and seat assignment in memory for RECONNECT_TIMEOUT_SECONDS from src/hexgame/server/config.py. The remaining player receives opponent_disconnected and the match is paused. During the pause, moves are rejected with Game paused for reconnect.

Getting the token

When a client connects via /ws/matchmake or /ws/join-slot, the server's joined payload includes a reconnect_token that's specific to that seat. Both reference clients (hexgame play and hexgame gui) print it to stdout on first join:

reconnect: slot 3 token a1b2c3d4e5

The token is also written to the JSONL replay log next to the joined event. Treat it like a short-lived secret — it's never exposed by /slots or the overview dashboard.

Reconnecting

Either CLI flag form works:

hexgame play --slot-id 3 --reconnect-token a1b2c3d4e5
hexgame gui  --slot-id 3 --reconnect-token a1b2c3d4e5

Both route to /ws/reconnect?slot_id=3&token=a1b2c3d4e5. The raw URL also works for custom clients:

ws://127.0.0.1:8000/ws/reconnect?slot_id=3&token=a1b2c3d4e5

If the token is valid and the reconnect timeout has not expired, the server sends reconnected with the current public slot snapshot (board, current turn, score, player_models, player_usernames) and notifies the opponent with opponent_reconnected. The GUI restores the full game state — including the opponent panel row — from that snapshot. If the timeout expires first, the slot is reset and the remaining player is notified and closed.

--reconnect-token without --slot-id is rejected with a clear error.

Clients

The model and GUI clients default to wss://hexgame.codingdojo.ai. Use --server ws://localhost:8000 only when you are running your own local server.

Random Client

The reference random client plays uniformly random legal moves. It is useful for smoke testing matchmaking, gameplay, and win detection.

Run two clients in separate terminals:

hexgame random --board-size 11 --seed 1
hexgame random --board-size 11 --seed 2

Useful options:

hexgame random \
  --server wss://hexgame.codingdojo.ai \
  --board-size 11 \
  --series-length 3 \
  --seed 42 \
  --move-delay 0.1

There is also a helper script:

bash src/hexgame/client/run_pair.sh

Model Client

hexgame play (module hexgame.client.model_client) loads a model module dynamically. The module must export:

def agent(board, action_set):
    ...

--model-name accepts any of four forms, resolved in this order:

  1. A filesystem path (./examples/model_dqn.py, /abs/path.py, or any value containing / or ending in .py). Loaded directly with importlib.util, so no sys.path / PYTHONPATH setup is needed. This is the most reliable form for unpackaged models.
  2. A bundled model name: hexgame.client.models.<NAME> — currently model_random and model_first.
  3. A top-level module name on sys.path — drop my_agent.py in your working directory and pass --model-name my_agent.
  4. examples.<NAME> — convenience fallback for repo checkouts run from the project root with PYTHONPATH=., so --model-name model_dqn finds examples/model_dqn.py.

A ModuleNotFoundError raised inside a resolved module (e.g. a missing torch import in the model file) is not swallowed — it propagates with its original message so the real cause is visible.

# my_agent.py  (in your current working directory)
from random import choice


def agent(board, action_set):
    # board contains 0, -1, and 1
    # action_set contains legal (row, col) moves
    return choice(list(action_set))
hexgame play --model-name my_agent --board-size 7
hexgame gui  --model-name my_agent --board-size 7
hexgame play --model-name ./my_agent.py --board-size 7      # explicit file path
hexgame play --model-name my_agent --server ws://localhost:8000

The model-facing board used by the WebSocket clients follows the server protocol convention:

0  = empty
-1 = red / model player 1
1  = blue / model player 2

The action_set passed to the model uses (row, col) coordinates. The client converts model output back to the server protocol shape {q, r}. Before sending a move, the client verifies that the model returned an integer (row, col) pair that is present in action_set. If a model returns an occupied cell, an out-of-bounds cell, a scalar, or coordinates in {q, r} order by mistake, the client stops with a clear model move error instead of sending an illegal move to the server.

Model clients also export a small JSONL replay log by default under replays/. The log records server messages, applied moves, model choices, rejected moves, terminal events, and board snapshots around model decisions. Use --replay-log off to disable it or --replay-log path.jsonl to choose an explicit export path.

Run two model clients:

hexgame play --model-name model_random --board-size 7 --series-length 1
hexgame play --model-name model_first --board-size 7 --series-length 1

To keep the same slot after a completed series and wait for another opponent, add --keep-slot:

hexgame play --model-name model_alphazero --board-size 7 --keep-slot

To join a specific waiting slot, add --slot-id. The client inherits the slot's board size and series length from the server:

hexgame play --model-name model_random --slot-id 3

To resume an interrupted match, pass --slot-id together with --reconnect-token. The token is printed to stdout on the first joined message (look for a line like reconnect: slot 3 token a1b2c3d4e5) and is also recorded in the replay log:

hexgame play --slot-id 3 --reconnect-token a1b2c3d4e5

The combination routes to /ws/reconnect?slot_id=...&token=... instead of the normal matchmaking endpoint. --reconnect-token without --slot-id is rejected with a clear error.

Bundled example models (in hexgame.client.models):

  • model_random
  • model_first

The repository's examples/ directory holds heavier, optional ML models (model_alphazero, model_dqn, ...) plus their weights and a C++ MCTS extension. Those are not part of the installed package; run them from a repo checkout with examples/ on PYTHONPATH.

Pygame GUI Model Client

hexgame gui (module hexgame.client.gui_client, needs the [gui] extra) is the graphical version of the model client. The window title shows the package version (Hex Client v<version>). The side panel shows:

  • Status — current state / countdown
  • Model — your model name
  • Opponent — the opposing player's model_name and username (server-supplied via the game_start / reconnected payload)
  • Slot, Game, Score, Moves, Replay
  • Players rows for player_1 (red, left↔right) and player_2 (blue, top↔bottom), with a chip on the side whose turn it is
  • Last move coordinates

On the board, the last move gets a yellow ring, and when a game ends the winning path is highlighted with a thick gold border on the cells that connect the two goal edges (computed locally with the same BFS the server uses).

When playing a multi-game series, the GUI pauses on the final board between games so you can see the result before it resets. The pause is --match-delay seconds (default 3.0, set to 0 to disable) and the status line counts down ("Next match in 1.7s — SPACE to skip"). Press SPACE at any time to skip ahead.

Run it with:

hexgame gui \
  --model-name model_random \
  --server wss://hexgame.codingdojo.ai \
  --board-size 7 \
  --series-length 3 \
  --match-delay 3 \
  --seed 42 \
  --move-delay 0.1 \
  --replay-log auto

Close the GUI with Esc, Q, or the window close button. Press SPACE during an inter-match pause to start the next game immediately.

To play as a human from the GUI, use --model-name human. When it is your turn, click an empty Hex cell to send the move:

hexgame gui --model-name human --board-size 7

To join a specific waiting slot from the GUI, add --slot-id. The GUI ignores its local --board-size and --series-length for gameplay after joining and uses the settings reported by the server:

hexgame gui --model-name human --slot-id 3

To keep the same slot after a completed series and wait for another opponent, add --keep-slot. The server resets the series score, keeps the player as player_1 in that slot, and moves the slot back to waiting:

hexgame gui --model-name human --board-size 7 --keep-slot

To resume an interrupted match, the GUI accepts the same --slot-id + --reconnect-token combination as hexgame play. The token is printed to stdout on the first joined message and recorded in the replay log; on reconnect the GUI restores the full board, turn, score, and opponent label from the server's snapshot:

hexgame gui --slot-id 3 --reconnect-token a1b2c3d4e5

Tests

Install the dev + optional dependencies, then run pytest from the repo root (pyproject.toml puts src/ on the path, so no install is strictly required, but the database/redis tests need those extras):

python -m pip install -e ".[dev,all]"
python -m pytest

Run frontend build verification:

cd frontend
npm run build

The current test suite covers:

  • Slot assignment and reset behavior.
  • Board-size-aware matchmaking.
  • Series-length-aware matchmaking and best-of scoring.
  • Protocol validation.
  • Move validation and turn order.
  • Hex win detection.
  • WebSocket matchmaking and gameplay.
  • Overview endpoint serving.

Project Layout

pyproject.toml             Package metadata, dependencies, console entry points
MANIFEST.in                Extra files to include in the source distribution

src/hexgame/
  server/                  -> console command: hexgame-server
    __main__.py            CLI wrapper around uvicorn (hexgame.server.main:app)
    main.py                FastAPI routes and global SlotManager
    config.py              Slot, board-size, protocol, and player constants
    models.py              GameSlot, PlayerConnection, SlotAssignment, HexGameState
    protocol.py            Message parsing and message factories
    slots.py               SlotManager and slot lifecycle
    redis_slots.py         Optional Redis-backed SlotManager
    database.py            SQLAlchemy ORM models and completed-series repository
    game.py                Move validation and win detection
    websocket_manager.py   WebSocket receive loop and gameplay handling
    static/overview/       Built Vite dashboard (shipped in the wheel)
  client/                  -> console command: hexgame {random,play,gui}
    __main__.py            Subcommand dispatcher
    random_client.py       Random-move client          (hexgame random)
    model_client.py        Model-driven client         (hexgame play)
    gui_client.py          Pygame model/human client   (hexgame gui, [gui] extra)
    client_safety.py       Model-output validation and JSONL replay logging
    hex_engine.py          Local Hex engine used by ML models
    models/                Bundled example model agents (model_random, model_first)
    run_pair.sh            Launches two random clients against a local server

frontend/
  src/                     Vite React overview source
  vite.config.ts           Builds into src/hexgame/server/static/overview/

examples/                  Heavy/optional ML extras (not part of the package):
  model_alphazero.py, model_dqn*.py, *.pt weights, hex_mcts.cpp, setup_mcts.py

tests/
  test_*.py                Unit and integration tests

Operational Notes

  • The default memory backend is single-process only.
  • Set HEX_STATE_BACKEND=redis before running hexgame-server --workers N (N > 1). Redis stores shared slot/game/session state, while WebSocket connections remain attached to the worker that accepted them.
  • /overview is an operational/debug dashboard. Protect or disable it before exposing this service beyond a trusted local network.
  • On disconnect, the slot is held for the reconnect timeout. Clients using --keep-slot can keep a slot after a finished series or after an opponent disconnects.

Troubleshooting

/, /docs, or /overview is blank

Rebuild the frontend:

cd frontend
npm run build

The built index.html must reference assets under /overview/assets/....

Random client gets HTTP 404

Reinstall the package (so uvicorn[standard]/websockets are present) and restart the server:

python -m pip install -e ".[dev,all]"
hexgame-server --port 8000

This usually means the server was started before WebSocket support was available, or another app is listening on port 8000.

Slot state looks stale

With the memory backend, restart the server. With Redis, inspect or clear keys under HEX_REDIS_KEY_PREFIX if you intentionally want to reset persisted slot state.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hexgame-0.2.0-py3-none-any.whl (135.8 kB view details)

Uploaded Python 3

File details

Details for the file hexgame-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: hexgame-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 135.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for hexgame-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 de7bd42e1a15adf54f4a31fc1122053a3dcc686f03a110144c57ae639f58cfaa
MD5 623479c805f418c73a2a6e498fe2149e
BLAKE2b-256 617d8173401b8b419cf44a82b6521edeba70eeb9491f35c07ddd124eb6dc1acc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page