A multiplayer chess game with FPS battle mode for captures, now using WebRTC peer-to-peer connections for LAN gameplay!
- Modern web browser (Chrome, Firefox, Safari, Edge)
- Node.js (for running Vite dev server)
- Both players on the same LAN (Wi-Fi or Ethernet)
npm installnpm run devThis starts Vite on http://localhost:5173 (or similar port).
Player 1 (Host):
- Open the game in your browser
- Click "Host Game (White)"
- Your local IP addresses will be displayed (e.g.,
192.168.1.100) - Share one of these IPs with the other player
- Wait for guest to connect
You'll see something like:
🌐 Your Local IPs:
192.168.1.100 ⭐ (en0)
10.0.0.5 (eth0)
The star ⭐ indicates your primary network interface (usually Wi-Fi).
Player 2 (Guest):
- Open the game in your browser (can be on a different device)
- Enter the host's IP address in the input field
- Click "Join Game (Black)"
- Wait for connection to establish
Once connected:
- White (Host) moves first
- Click pieces to select and move (standard chess rules)
- Captures trigger FPS battles!
- WASD to move
- Mouse to aim
- Click to shoot
- Win the battle to win the square
- Default mode is LAN host candidates (
iceTransportPolicy: "all") with no TURN required. Ensure both players are on the same subnet and can reach port 8080 on the host. - If you previously set a TURN override in
localStorage.turn_config, clear it for LAN play:localStorage.removeItem('turn_config'); location.reload();
- If you do need TURN later, set
localStorage.turn_configorwindow.TURN_CONFIGbefore loading the page:Replace with your TURN host/IP/creds and open/forward the TURN ports if you’re not on LAN.localStorage.setItem('turn_config', JSON.stringify({ iceServers: [ { urls: ['turn:YOUR_TURN_IP:3478?transport=udp', 'turn:YOUR_TURN_IP:3478?transport=tcp'], username: 'user', credential: 'pass' } ], iceTransportPolicy: 'relay' })); location.reload();
- Host must allow inbound TCP 8080 (signaling) on the host machine.
- Browser must be allowed through OS firewall.
- TURN not needed on LAN; leave it unset unless you’re crossing networks.
- WASD: Move camera
- Space/Shift: Up/Down
- Mouse: Look around
- Click: Select/move pieces (when it's your turn)
- WASD: Move in arena
- Mouse: Aim
- Click: Shoot
- Health: 100 HP (20 damage per hit)
1. HOST STARTUP:
├─ Start HTTP signaling server (port 8080)
├─ Display local IP addresses
├─ Create WebRTC offer
└─ Wait for guest
2. GUEST CONNECTION:
├─ Enter host IP
├─ Fetch SDP offer via HTTP
├─ Create WebRTC answer
└─ Exchange ICE candidates
3. P2P ESTABLISHED:
├─ Direct data channel connection
├─ HTTP signaling server shuts down
└─ All game data flows peer-to-peer
- Host (White): Game state authority, validates all moves
- Guest (Black): Sends move requests, receives authoritative state
- Battle Mode: Distributed authority (each player calculates own damage)
-
"game-state" (Reliable, Ordered)
- Chess moves
- Battle start/end
- Game state updates
-
"battle-updates" (Unreliable, Unordered)
- Real-time position updates (50ms interval)
- Shooting/health data
- Lower latency for smooth FPS gameplay
Causes:
- Host hasn't started yet
- Wrong IP address entered
- Firewall blocking port 8080
- Not on same network
Solutions:
- Verify both devices on same Wi-Fi/ethernet
- Double-check IP address is correct
- Try pinging host IP:
ping 192.168.1.100 - Disable firewall temporarily or allow browser through firewall
macOS:
System Preferences → Security & Privacy → Firewall
→ Firewall Options → Allow your browser
Windows:
Windows Defender Firewall → Allow an app
→ Add your browser (Chrome/Firefox/etc)
Linux:
sudo ufw allow 8080/tcp- Check you're connected to a network (Wi-Fi or ethernet)
- Try restarting the browser
- Check network adapter is enabled
- Ensure strong Wi-Fi signal (or use ethernet)
- Close bandwidth-heavy apps (streaming, downloads)
- Reduce distance between devices on LAN
- Check for network interference
If board states differ between players:
- Host periodically sends full state sync (every 5 seconds)
- Guest always accepts host's state as truth
- Connection drops will require reconnection
You can test with two browser windows on the same machine:
- Open first window: Click "Host Game"
- Note the IP (will show
127.0.0.1or local IP) - Open second window (or incognito): Enter
localhostor127.0.0.1 - Click "Join Game"
fps-chess/
├── main.js # Client game logic & UI
├── chess.js # Pure chess game rules
├── battle.js # FPS battle arena system
├── network-manager.js # High-level networking API
├── webrtc-connection.js # WebRTC peer connection wrapper
├── signaling-server.js # HTTP signaling for connection setup
├── package.json # Dependencies
├── index.html # Entry point
└── README.md # This file
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]STUN servers help with NAT traversal, even on LANs.
All messages are JSON over WebRTC Data Channels:
// Guest sends move request
{ type: 'moveRequest', fromRow: 6, fromCol: 4, toRow: 4, toCol: 4 }
// Host broadcasts game state
{ type: 'gameState', board: [[...]], currentTurn: 'black' }
// Battle update (50ms interval)
{ type: 'battleUpdate', position: {x,y,z}, rotation: {x,y,z}, shot: {...}, health: 80 }- Connection Time: < 5 seconds
- Move Latency: < 200ms
- Battle Update Rate: 20 updates/sec (50ms interval)
- Battle Latency: < 100ms
- Data Usage: ~2KB per move, ~150 bytes per battle update
- No internet gameplay (LAN only)
- No spectator mode
- No game save/resume
- No reconnection after disconnect (must restart)
- No check/checkmate detection (simplified chess rules)
- No en passant, castling, or pawn promotion
MIT License