Part 2
Tutorial · ~10 min · Any AI agent
Competitive Pac-Man
with ghosts and power-ups
Last time we built a drawing game. This time: a 2-player Pac-Man where you race to eat more dots,
dodge four AI ghosts, and chomp power pellets — all generated by one prompt, running on your laptop.
🎮
Competitive Pac-Man
Two players race through the same maze. Every dot you eat is a dot they can't. 90 seconds on the clock.
👻
Four AI ghosts
Red chases, Pink ambushes, Blue patrols, Orange wanders. Get caught and you lose 5 points.
🔒
PIN-locked access
Share the URL and a 4-digit PIN. Your friend enters the PIN to join — no account needed.
What changed
Part 1 was a warm-up
In Part 1, we asked an AI agent to build a Pictionary game. Simple WebSocket server, drawing canvas, word guessing. It worked, and it was fun.
This time we're pushing harder. The prompt asks for a server-authoritative game engine: a proper game loop with fixed tick rates, ghost AI with four distinct personalities, power-ups that flip the hunt, and competitive scoring between two players racing through the same maze.
We're also adding single-player mode. Because sometimes you just want to chase a high score while waiting for your friend to join.
|
Part 1: Pictionary |
Part 2: Pac-Man |
| Game loop |
Event-driven (draw & guess) |
Server-authoritative, 10 ticks/sec |
| AI |
None |
4 ghosts with unique behaviors |
| Players |
2+ cooperative |
1-2 competitive |
| Mechanics |
Drawing + guessing |
Movement, power-ups, respawning |
| Access control |
SSO login gate |
PIN code |
Prerequisites
Before you start
- An AI coding agent — Claude Code, Gemini CLI, Cursor, Aider, or any other
- Python 3.9+ installed on your machine
- HLE installed — run
pipx install hle-client
- An HLE API key — run
hle auth login to open the dashboard and save your key
- A friend to compete against (or play solo first)
New to HLE?
First-time setup takes 2 minutes
HLE is a free tool that gives your local server a public URL with built-in authentication. Here's the one-time setup:
pipx install hle-client
hle auth login
Running hle auth login opens hle.world/dashboard in your browser. Create a free account using Google or GitHub, generate an API key, and it saves automatically. After this one-time setup, your local HLE client has everything it needs.
To verify setup worked: hle auth status should show your saved API key. If it's empty, something went wrong — run hle auth login again.
The magic part
The Prompt
Copy this and paste it into your AI agent. It builds the game, installs dependencies, starts everything, and creates a reusable start script.
Build a multiplayer competitive Pac-Man game as a single Python file (game.py) using websockets 13.x.
## Game Design
1-2 players compete on the SAME maze simultaneously. If only one player joins, the game starts in single-player mode after 10 seconds (or immediately with a "Start Solo" button). When a second player joins, the next round becomes competitive. The goal: eat more dots than your opponent before time runs out (90 seconds). In single player, just go for the high score.
### Maze & Dots
- Generate a classic pac-man style maze (walls, corridors, open areas) — at least 25x25 grid
- Fill corridors with regular dots (1 point each)
- Place 8 bonus dots (big flashing ones, 10 points each) in strategic locations
- Dots disappear when ANY player eats them — it's a race
### Players
- Player 1: yellow pac-man, starts top-left
- Player 2: cyan pac-man, starts bottom-right
- Smooth movement with WASD keys
- Mouth animation (classic open/close)
- Players can pass through each other (no collision between players)
### Ghosts (4 total)
- Each ghost has a different color and personality:
- Red: chases the nearest player directly
- Pink: tries to ambush (targets 4 tiles ahead of nearest player)
- Blue: random patrol, switches to chase if player is within 5 tiles
- Orange: alternates between chase and scatter every 10 seconds
- If a ghost catches you: 3-second respawn at your start position + lose 5 points
- Ghosts respawn from center after 5 seconds if stuck
### Bonus Power-ups (4 on the map, respawn every 30s)
- Eating a power pellet makes ghosts vulnerable for 8 seconds (turn blue, move slower)
- Eating a vulnerable ghost: +20 points, ghost respawns at center
### Game Speed — IMPORTANT
- The server game loop MUST run at a FIXED tick rate using delta-time, not as fast as the CPU allows
- Target: 10 ticks per second (100ms per tick). Use a proper game loop that sleeps for the remainder of each tick
- Movement: players move exactly 1 tile per tick when holding a direction key — no faster
- Client renders at 15 FPS interpolating between server states for smoothness
- Do NOT use asyncio.sleep(0) or tight loops — use asyncio.sleep(tick_interval - elapsed) to maintain consistent speed regardless of server performance
### UI Requirements
- Dark background (#0a0a0a), neon-colored maze walls
- Live scoreboard showing both players' scores, time remaining
- "PLAYER 1 WINS" / "PLAYER 2 WINS" / "TIE" screen at end with rematch button
- In single player: show "YOUR SCORE" and personal best
- Lobby screen: shows connected players, "Start Solo" button, game auto-starts when 2 players ready
- Mobile-friendly: add on-screen d-pad controls for touch
- Sound effects using Web Audio API (dot eat, ghost eat, death, game start, game end)
- Show the HLE tunnel URL and PIN prominently on the game page — display a small info bar or banner (top or bottom of the page) showing "Share this URL: <tunnel-url>" and "PIN: <pin>" so the host can easily share access with friends. The server should accept these as command-line arguments: `python game.py --url <tunnel-url> --pin <pin>`
### Technical
- Python websockets 13.x server on port 19265 (non-standard to avoid conflicts)
- ALL HTML/CSS/JS inline in the Python file (served via HTTP on same port)
- Game state runs server-side (authoritative server — no cheating)
- Client sends inputs, server sends game state at the fixed tick rate via websocket
- Handle disconnection gracefully (in 2P: other player wins if opponent leaves; in 1P: game just ends)
## Step 1: Environment discovery
BEFORE writing any code, create and run a discovery script (`check_env.sh`) that detects the local environment:
```bash
#!/usr/bin/env bash
echo "=== Environment Discovery ==="
echo "Python3: $(which python3 2>/dev/null || echo 'NOT FOUND')"
echo "Python: $(which python 2>/dev/null || echo 'NOT FOUND')"
echo "Pip: $(which pip3 2>/dev/null || which pip 2>/dev/null || echo 'NOT FOUND')"
echo "HLE: $(which hle 2>/dev/null || echo 'NOT FOUND')"
echo "Curl: $(which curl 2>/dev/null || echo 'NOT FOUND')"
echo "Python version: $(python3 --version 2>/dev/null || python --version 2>/dev/null)"
# Check if HLE is already configured
if [ -f "$HOME/.config/hle/config.toml" ]; then
echo "HLE config: FOUND at ~/.config/hle/config.toml"
grep -o 'api_key = "hle_[a-f0-9]\{4\}' "$HOME/.config/hle/config.toml" 2>/dev/null && echo "...key present" || echo "...no key"
else
echo "HLE config: NOT FOUND"
fi
echo "Port 19265: $(lsof -i :19265 2>/dev/null && echo 'IN USE' || echo 'available')"
```
Use the detected paths (e.g., `python3` vs `python`) for ALL subsequent commands. Store them as variables for the start script. If python3 isn't found, stop and tell me. Do NOT ask me to confirm tool paths — use whatever was detected.
## Step 2: Set up venv and install dependencies
Create a virtual environment and install dependencies. Do NOT install packages globally.
```
python3 -m venv .venv
source .venv/bin/activate
pip install 'websockets>=13,<14' hle-client
```
After installing, verify both are available:
```
timeout 5 .venv/bin/python -c "import websockets; print('websockets OK')"
timeout 5 .venv/bin/hle --version
```
Use `timeout` (or `gtimeout` on macOS) as a prefix for ANY command that should complete quickly, to avoid hanging forever. If `timeout` isn't available, use Python's subprocess with a timeout instead.
## Step 3: Verify HLE is set up
Run `timeout 10 .venv/bin/hle auth status` to check if HLE is configured with a valid API key.
If NOT configured (no API key found), or if the command fails, guide me through this one-time setup:
1. Run: `.venv/bin/hle auth login`
- This opens my browser to hle.world/dashboard
- Create a free account with Google or GitHub
- Generate an API key and save it when prompted
- This key is now saved locally — I won't need to do this again
2. Verify it worked: `timeout 10 .venv/bin/hle auth status` should show the saved key
## Step 4: Build the game
Create game.py with the full implementation described above.
## Step 5: Create the start script
Create `start.sh` that automates everything end-to-end. Running `./start.sh` should be the ONLY thing needed:
```bash
#!/usr/bin/env bash
set -euo pipefail
GAME_PORT=19265
LABEL="pacman"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
# --- Venv setup ---
if [ ! -d .venv ]; then
python3 -m venv .venv
.venv/bin/pip install -q 'websockets>=13,<14' hle-client
fi
PYTHON=".venv/bin/python"
HLE=".venv/bin/hle"
# --- Check HLE auth ---
if ! timeout 10 "$HLE" auth status >/dev/null 2>&1; then
echo "HLE not configured. Run: $HLE auth login"
exit 1
fi
# --- Read API key from config for direct API calls ---
API_KEY=$(grep -oP 'api_key = "\K[^"]+' "$HOME/.config/hle/config.toml" 2>/dev/null || echo "")
if [ -z "$API_KEY" ]; then
echo "Could not read API key from ~/.config/hle/config.toml"
exit 1
fi
# --- Generate random 6-digit PIN ---
PIN=$(printf "%06d" $((RANDOM % 1000000)))
# --- Start game server ---
"$PYTHON" game.py &
GAME_PID=$!
sleep 2
# --- Start HLE tunnel in background, capture URL ---
"$HLE" expose --service "http://localhost:$GAME_PORT" --label "$LABEL" > hle.log 2>&1 &
HLE_PID=$!
# Wait for tunnel URL to appear in log (up to 15s)
TUNNEL_URL=""
for i in $(seq 1 30); do
TUNNEL_URL=$(grep -oP 'https://[^ ]+\.hle\.world' hle.log 2>/dev/null | head -1 || true)
[ -n "$TUNNEL_URL" ] && break
sleep 0.5
done
if [ -z "$TUNNEL_URL" ]; then
echo "Failed to get tunnel URL. Check hle.log"
kill $GAME_PID $HLE_PID 2>/dev/null
exit 1
fi
# --- Set PIN via HLE API (non-interactive, no CLI prompts) ---
# Extract subdomain from URL (e.g., pacman-x7k from https://pacman-x7k.hle.world)
SUBDOMAIN=$(echo "$TUNNEL_URL" | sed 's|https://||;s|\.hle\.world.*||')
curl -s -X PUT "https://hle.world/api/tunnels/$SUBDOMAIN/pin" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"pin\": \"$PIN\"}"
# --- Restart game with URL and PIN displayed ---
kill $GAME_PID 2>/dev/null; wait $GAME_PID 2>/dev/null
"$PYTHON" game.py --url "$TUNNEL_URL" --pin "$PIN" &
GAME_PID=$!
# --- Print sharing info ---
echo ""
echo "╔══════════════════════════════════════════╗"
echo "║ GAME READY — share with your friend: ║"
echo "║ ║"
echo "║ URL: $TUNNEL_URL"
echo "║ PIN: $PIN"
echo "║ ║"
echo "╚══════════════════════════════════════════╝"
echo ""
echo "Press Ctrl+C to stop."
# --- Cleanup on exit ---
cleanup() { kill $GAME_PID $HLE_PID 2>/dev/null; rm -f hle.log; }
trap cleanup EXIT INT TERM
wait
```
Key requirements for the script:
- Use full paths to `.venv/bin/python` and `.venv/bin/hle` — never rely on `PATH` or `source activate`
- Track process IDs with `$!` after backgrounding — NEVER use `pkill` or `killall`
- Set the PIN via the HLE REST API using curl (`PUT https://hle.world/api/tunnels/{subdomain}/pin` with `Authorization: Bearer {api_key}` header and `{"pin": "..."}` body) — do NOT use `hle pin set` as it requires interactive input that cannot be piped
- Use `timeout` prefix for any command expected to finish quickly
- Use port 19265 (not 8765) to avoid conflicts
Make start.sh executable with `chmod +x start.sh`.
## Step 6: Test it
Run `./start.sh` and show me the tunnel URL and PIN. I'll send them to my friend so they can join.
What does this prompt actually do?
- Generates a competitive Pac-Man game with ghost AI, power-ups, and scoring as a single Python file
- Discovers your local environment (Python path, installed tools, port availability)
- Creates a virtual environment and installs
websockets + hle-client inside it
- Checks if HLE is configured and guides you through free account creation if needed
- Writes a one-command start script (
./start.sh) that launches the game, creates the tunnel, sets a random PIN, and displays the sharing info
Agent-specific
Tips for your agent
The prompt works with any AI coding agent. Here's what to expect:
Fully auto
Claude Code
Claude Code runs commands directly in your terminal. Paste the prompt and it will write the game,
install packages, start the server, expose it with HLE, and set the PIN — all automatically.
The game is more complex than Part 1 (server-authoritative game loop, ghost AI), so the agent might take
3-5 minutes to generate. That's normal.
Semi-auto
Gemini CLI / Aider / Amp
These agents will generate game.py and may run some commands. If the agent stalls after creating the file,
run the remaining steps yourself:
pip install 'websockets>=13,<14'
python game.py &
sleep 2
hle expose --service http://localhost:8765 --label pacman
Then set the PIN in another terminal:
hle pin set pacman
Code + terminal
GitHub Copilot / Cursor / Windsurf
IDE-based agents will generate game.py in your editor. Open a terminal and run:
pip install 'websockets>=13,<14'
python game.py &
Then in a second terminal:
hle expose --service http://localhost:8765 --label pacman
And set the PIN:
hle pin set pacman
The flow
What happens next
Once you paste the prompt, your agent works through these steps:
-
Agent writes game.py
A single Python file with the full Pac-Man game — maze generation, ghost AI, power-ups, scoring, and a neon-themed UI. All HTML/CSS/JS served inline. Expect 400-600 lines.
-
Installs dependencies
Just one package:
websockets. HLE should already be installed from the prerequisites.
-
Checks your HLE setup
Runs hle auth status to verify your API key. If it's missing, it walks you through the free signup.
-
Starts the game & exposes it
Launches the game server, then runs hle expose --service http://localhost:8765 --label pacman to create your tunnel.
-
Locks it with a PIN
Runs hle pin set pacman — you pick a 4-8 digit code. Your friend will need this to access the game.
-
Creates start.sh
Next time, just run ./start.sh and you're back in business.
The fun part
Send the link and the PIN
Unlike Part 1 where your friend had to log in with Google or GitHub, this time you're using a PIN. Simpler for casual gaming — no accounts, no SSO, just four digits.
haha what
ok give me a sec
Just now
wait this is actually good??
1 min ago
They click the link, enter the PIN, and they're in the lobby. When both players hit ready, the 90-second countdown starts.
How the PIN gate works
Authentication is enabled by default on all HLE tunnels. When you expose a service, HLE requires visitors to authenticate before accessing it. The PIN is the simplest way to share with a friend — no accounts, no sign-ups, just a 4-digit code.
When you set a PIN on a tunnel, anyone visiting the URL sees a PIN entry screen before they can reach your app. No code changes needed — it works at the tunnel level.
hle pin set pacman
hle pin status pacman
hle pin remove pacman
Alternatively, you can use SSO (Google or GitHub login) instead — run hle expose --service http://localhost:8765 --label pacman without the --pin flag to use SSO. But PIN is faster for casual sharing.
Under the hood
How HLE makes this work
↔
HLE Relay
Encrypted tunnel
↔
Your game runs entirely on your machine. HLE creates a WebSocket tunnel between your laptop and its relay server. When your friend visits the URL and enters the PIN, their traffic flows through the tunnel to your local game server.
The game state is authoritative — your laptop is the server, so no one can cheat by modifying the client. The fixed tick rate keeps things fair regardless of each player's connection speed. And because HLE natively proxies WebSocket traffic, the real-time game state streams smoothly in both directions.
Solo mode
Don't want to wait for a friend?
The game starts in single-player mode if no one else joins within 10 seconds (or you can hit the "Start Solo" button right away). Same maze, same ghosts, same power-ups — just you versus the AI, chasing your personal best.
When a second player connects, the next round automatically switches to competitive mode. So you can start practicing while your friend figures out what a PIN is.
Ready to play?
Install HLE, paste the prompt, send the link. Game night in 10 minutes.