- Kotlin 100%
| .cursorj | ||
| .idea | ||
| client | ||
| gradle/wrapper | ||
| server | ||
| shared | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
HordeGame
A Kotlin-based virtual tabletop assistant for tabletop RPG sessions. HordeGame provides real-time dice rolling with visibility controls, chat, character sheet change tracking, and session history export.
Architecture
| Module | Description |
|---|---|
| shared | Shared data models, DTOs, and WebSocket message types |
| server | Ktor REST + WebSocket backend (port 8080) |
| client | Compose Desktop client + HTTP test utilities |
Stack: Kotlin 2.2.0, Ktor 3.0.1, Compose Desktop 1.7.1, kotlinx.serialization
Prerequisites
- JDK 17 or newer
- Gradle (wrapper included)
Running the Server
./gradlew :server:run
The server starts at http://localhost:8080.
Health check: GET /health
Running the Desktop Client
./gradlew :client:run
The client provides a full game UI:
- GM: see all connected players (online/offline), view each player's roll history, roll any dice type with count and visibility, ping players to roll
- Players: join session, roll dice (type + count), receive GM pings
- Persistence: rolls saved to
rolls/on server and~/.hordegame/rolls/on client; reload via Reload Rolls button orRELOAD_ROLLSWebSocket message
HTTP Test Clients
With the server running:
./gradlew :client:runTestClient
./gradlew :client:runHistoryTestClient
REST API
| Endpoint | Method | Description |
|---|---|---|
/api/session/create |
POST | Create a new GM session |
/api/session/join |
POST | Join session with invite code |
/api/session/{id} |
GET | Public session info |
/api/session/{id}/auth |
POST | GM authentication |
/api/session/{id}/rolls |
GET | Recent dice rolls (requires X-Player-ID) |
/api/session/{id}/history |
GET | Action history with filters |
/api/session/{id}/history/search |
GET | Search history (?q=term) |
/api/session/{id}/statistics |
GET | Session statistics |
/api/session/{id}/export |
GET | Export history (GM only, ?format=JSON|CSV|HTML) |
WebSocket Protocol
| Endpoint | Headers | Purpose |
|---|---|---|
/ws/gm/{sessionId} |
X-GM-Password |
GM connection |
/ws/player/{sessionId} |
X-Player-ID, X-Player-Name, X-Invite-Code |
Player connection |
Message prefixes:
ROLL:{json}— Roll dice (RollDiceRequestwithdiceType,count,visibility)PING_PLAYER:{json}— GM pings a player (PingPlayerRequest)RELOAD_ROLLS— Reload all rolls from server diskVISIBILITY:{rollId}:{visibility}— GM changes roll visibilityCHAT:{json}— Send chat messageCHARACTER:{json}— Record character sheet changePING— Keepalive (responds withPONG)
Server messages: JSON-encoded GameMessage variants (DiceRolled, PlayerJoined, ChatMessage, etc.)
Dice Types
D4, D6, D8, D10, D12, D20, D100, and Coin Flip (Heads/Tails). Roll 1–20 dice at once.
Dice Visibility
| Mode | Who sees the roll |
|---|---|
HIDDEN |
Rolling player only |
GM_ONLY |
Rolling player + GM |
ALL_VISIBLE |
Everyone in session |
Building & Testing
./gradlew build
./gradlew :server:test
Data Persistence
- Sessions and players are in-memory (lost on restart)
- Action history is persisted to
history/{sessionId}.jsonl
Project Status
Core server functionality is implemented: sessions, dice, WebSocket realtime, history, and export. The Compose Desktop client provides a minimal UI scaffold. Production hardening (persistent sessions, authentication, deployment) is not yet in place.