{"id":51145038,"url":"https://github.com/jucimarjr/asteroids_multiplayer","last_synced_at":"2026-06-26T02:02:31.978Z","repository":{"id":360030932,"uuid":"1248432954","full_name":"jucimarjr/asteroids_multiplayer","owner":"jucimarjr","description":"LAN deathmatch multiplayer. Didactic client-server project. Fork of asteroids_single-player.","archived":false,"fork":false,"pushed_at":"2026-05-31T21:29:23.000Z","size":491,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T23:16:44.668Z","etag":null,"topics":["asteroids","asyncio","client-server","educational-project","game","lan-gaming","multiplayer","pygame","python","websockets"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jucimarjr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-24T16:28:13.000Z","updated_at":"2026-05-31T21:24:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jucimarjr/asteroids_multiplayer","commit_stats":null,"previous_names":["jucimarjr/asteroids_multiplayer"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jucimarjr/asteroids_multiplayer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jucimarjr%2Fasteroids_multiplayer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jucimarjr%2Fasteroids_multiplayer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jucimarjr%2Fasteroids_multiplayer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jucimarjr%2Fasteroids_multiplayer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jucimarjr","download_url":"https://codeload.github.com/jucimarjr/asteroids_multiplayer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jucimarjr%2Fasteroids_multiplayer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34799570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asteroids","asyncio","client-server","educational-project","game","lan-gaming","multiplayer","pygame","python","websockets"],"created_at":"2026-06-26T02:02:31.325Z","updated_at":"2026-06-26T02:02:31.970Z","avatar_url":"https://github.com/jucimarjr.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Asteroids Multiplayer\n\nLAN deathmatch multiplayer for up to 8 players. Didactic client-server project in Python with `asyncio` and WebSockets. Fork of [asteroids_single-player](https://github.com/jucimarjr/asteroids_single-player) — the parent repository is frozen at `v0.1.0` and this fork starts from the same state.\n\nA single-player mode runs from this repository too: every multiplayer-prep refactor leaves `python main.py` working as in the parent repo.\n\n## Quick start\n\nRequires Python 3.10 or newer (3.13 recommended via `.python-version`).\n\n```\npip install -r requirements.txt\npython main.py\n```\n\n### Run multiplayer deathmatch\n\n```\n# Terminal 0 — copy the example token file first\ncp tokens.example.txt tokens.txt\n\n# Terminal 1 — server with two rooms\npython -m server --rooms 2 --port 8765\n\n# Terminals 2-5 — players in two rooms\npython -m multiplayer.player --host localhost --port 8765 --name Alice --room 0 --token dev-token-1\npython -m multiplayer.player --host localhost --port 8765 --name Bob   --room 0 --token dev-token-2\npython -m multiplayer.player --host localhost --port 8765 --name Carol --room 1 --token dev-token-3\npython -m multiplayer.player --host localhost --port 8765 --name Dave  --room 1 --token dev-token-4\n\n# Terminal 6 — spectator on room 0\npython -m multiplayer.spectator --host localhost --port 8765 --room 0 --token dev-token-1 --width 1024 --height 768\n```\n\nThe server prints one line per significant event to stdout, so the terminal doubles as a live transcript of the match:\n\n```\nasteroids server listening on ws://0.0.0.0:8765\nship spawned: Alice (pid=1, room=0)\nship spawned: Bob (pid=2, room=0)\nmatch started: room=0\nship killed: Bob (pid=2, room=0)\nmatch ended: room=0, winner=Alice\n```\n\nThe server is authoritative and runs each room's `World` headlessly at 60 Hz; each client connects, receives snapshots at 30 Hz, sends input every frame (players only), and renders through the same client renderer used by single-player. The networked player client adds a local HUD with score, deaths and room id plus a scoreboard listing every connected player in the same room, with a `RESPAWN X.Xs` countdown shown next to anyone waiting to respawn. Audio starts **muted** on the networked client; press `R Shift` in any player window to toggle SFX on or off.\n\nA match begins as soon as two players are connected to a room (`MIN_PLAYERS_TO_START`); it ends on the first to 5 frags or after 2 minutes of clock (`FRAG_LIMIT`, `MATCH_DURATION` in `core/config.py`). Any connected player presses `ENTER` on the match-end screen to reset that room's world and start the next match. Rooms are independent — matches in room 0 do not affect room 1.\n\nThe spectator client is read-only: it never spawns a ship, never sends input, and fits the entire 3840×2160 world into the configured `--width`/`--height` window via the `SpectatorCamera` (letterbox padding when the aspect ratio differs from the world's 16:9). Set `--rooms N` on the server to host N concurrent rooms; the default is 1, which matches the F4 single-room behavior.\n\n**Hosting on a VPS?** To run the server as a managed `systemd` service on a Linux host — background, auto-restart, journald logs — see [`docs/DEPLOY.md`](docs/DEPLOY.md).\n\n## Controls\n\n| Key       | Action |\n| --------- | ------ |\n| `←` `→`   | Rotate |\n| `↑`       | Thrust |\n| `↓`       | Shield (3 s active, 10 s cooldown) |\n| `Space`   | Shoot  |\n| `L Shift` | Hyperspace (costs 250 points) |\n| `R Shift` | Toggle audio (networked client only; muted at start) |\n| `Enter`   | Restart match after `MATCH OVER` (networked client only) |\n| `Esc` `Q` | Quit |\n\n## How it works\n\nFour top-level packages, each with a single concern:\n\n- [`core/`](core/) holds game state, entities, collisions, and the per-frame update. Plain Python, no pygame. Emits string events (`\"player_shoot\"`, `\"asteroid_explosion\"`) that consumers react to.\n- [`client/`](client/) wires pygame to the simulation. Maps input, runs the 60 FPS loop, renders polygons, plays audio from `world.events`.\n- [`server/`](server/) hosts the authoritative `World`. Runs the simulation at 60 Hz and broadcasts a snapshot to every connected client at 30 Hz over a single WebSocket per client.\n- [`multiplayer/`](multiplayer/) is the networked player. Connects to a server, applies snapshots to a local read-only `World`, sends input every frame, renders through the existing `client/` renderer.\n\nThe single-player path (`python main.py`) uses `core/` + `client/` directly and is preserved across every phase. The multiplayer path (server + multiplayer client) layers `server/` + `multiplayer/` on top without touching the others.\n\nKey files:\n\n- [`core/world.py`](core/world.py): simulation tick, wave spawning, score, lives, deathmatch flag, respawn loop, match lifecycle.\n- [`core/entities.py`](core/entities.py): `Ship`, `Asteroid`, `Bullet`, `UFO`, `Particle`.\n- [`core/collisions.py`](core/collisions.py): `CollisionManager` resolves every collision in a single pass and returns a `CollisionResult`.\n- [`client/game.py`](client/game.py): game loop and scene transitions (menu, play, game over).\n- [`client/spectator_camera.py`](client/spectator_camera.py): scaled fit-the-world-into-the-window camera used by the spectator client.\n- [`multiplayer/hud.py`](multiplayer/hud.py): networked-client HUD and scoreboard.\n- [`multiplayer/spectator.py`](multiplayer/spectator.py): read-only spectator client.\n- [`server/auth.py`](server/auth.py): token allowlist loader.\n\n## Roadmap\n\n| Phase | Content | Status |\n|---|---|---|\n| F1 — Foundation | Decouple `core/` from pygame, viewport vs world split, camera, testing infra | done |\n| F2 — Server lonely | WebSocket asyncio server, single player connects and sees own state | done |\n| F3 — Multi-player 1 room | N players in deathmatch, respawn, frag/score, scoreboard HUD | done |\n| F4 — Match lifecycle | Timer / frag limit / match end, ENTER-to-restart, lobby gate | done |\n| F5 — Multi-room | Token allowlist, N concurrent rooms, dedicated spectator client | done |\n| F6 — Netcode | Client-side prediction, server reconciliation (input ack), remote-ship interpolation | done |\n| F7 — Performance | Client frame-time profiling (`--profile-frames`); a measured baseline found no hotspot worth optimizing | done |\n\n## Project layout\n\n```\nasteroids_multiplayer/\n├── main.py                   # single-player entrypoint (preserved)\n├── pyproject.toml\n├── core/                     # game state, rules, entities, collisions\n├── client/                   # pygame loop, renderer, input, audio\n├── server/                   # authoritative World + WebSocket broadcast\n├── multiplayer/              # networked player client\n├── assets/                   # WAV sound effects\n├── tests/                    # pytest suite (snapshot, protocol, vec, ...)\n└── docs/\n    ├── ARCHITECTURE.md\n    └── DEVELOPMENT_WORKFLOW.md\n```\n\n## Contributing\n\nRead [`docs/DEVELOPMENT_WORKFLOW.md`](docs/DEVELOPMENT_WORKFLOW.md) before opening a PR. It defines branch prefixes, commit conventions in en-US, and the review checklist. [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) walks through module dependencies.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjucimarjr%2Fasteroids_multiplayer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjucimarjr%2Fasteroids_multiplayer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjucimarjr%2Fasteroids_multiplayer/lists"}