Overview
The Game Engine is the heart of Showdown Trivia, managing game state, question flow, answer collection, and scoring. It uses Go channels and goroutines to coordinate real-time gameplay across multiple players.Core Structures
Game Structure
Location:internal/core/game/game.go:14
Player Structure
Location:internal/core/game/domain.go:21
Players start with a score of 0. The score increments by 1 for each correct answer.
Answer Structure
Location:internal/core/game/domain.go:33
Message Structure
Location:internal/core/game/domain.go:9
Game Initialization
Location:game.go:96
Initialization Flow
Default Timer
Default Timer
Location: While defined, the actual timer is set by the game creator (typically 5-30 seconds).
game.go:11Game Lifecycle
Starting the Game
Location:game.go:25
The
Start() method is called in a goroutine from client.go:84 to avoid blocking the WebSocket read loop.Question Flow
Location:game.go:55
Flow Diagram
Answer Collection
Answers are received from theAnswerCh channel:
Answer Deduplication
Answer Deduplication
The map structure ensures each player can only submit one answer per question:If a player sends multiple answers, only the last one is kept. This allows players to change their answer before time expires.
Channel Source
Channel Source
Answers are sent from WebSocket clients:Location: This is an unbuffered channel, so sends block until the game’s select loop receives them.
ws/client.go:86Scoring Logic
Location:game.go:37
Scoring is simple: correct = +1 point, incorrect = 0 points. There is no time bonus or penalty system.
Ending the Game
Location:game.go:86
Winners Format
Room Cleanup After Game
Room Cleanup After Game
Location: The room is automatically cleaned up, and the closed
ws/rooms.go:64When MsgGameEnd is received by the room:Message channel terminates the room’s event listener goroutine.Player Management
Adding Players
Players are collected when the game starts: Location:ws/client.go:78
The player list is a snapshot taken at game start. Players who disconnect during the game remain in the
Players slice but stop receiving questions.Player State During Game
Each player maintains:- Username: Immutable identifier
- Score: Updated after each question
- Connection: Managed separately by Client/Room (not in Player struct)
Handling Disconnections
If a player disconnects mid-game:- Their WebSocket client is removed from the room
- Their Player object remains in
game.Players - They stop receiving questions
- They cannot submit answers
- Their score remains unchanged
- They appear in final results with their last score
Question Entity
Location:internal/core/entities/question.go
Question Sources
Questions come from the Trivia API client: Location:internal/trivia_api/trivia_api.go:18
Option Shuffling
Option Shuffling
Location: Options are shuffled so the correct answer isn’t always in the same position.
trivia_api.go:90Timing and Concurrency
Timer Mechanism
Location:game.go:57
- Created when question is asked
- Fires once after
timerSpanduration - Triggers scoring and progression
The timer is not paused or extended. Once it starts, answers must be submitted before it expires.
Channel Communication
Unbuffered Channels
All game channels are unbuffered:- Answers block client write until game receives them
- Messages block game until room receives them
- Provides natural backpressure
- Ensures synchronous event processing
Channel Flow Diagram
Goroutine Coordination
Game Start Goroutine
Game Start Goroutine
Location: Game logic runs in a separate goroutine to avoid blocking the WebSocket read loop. This allows clients to continue sending messages while the game progresses.
ws/client.go:84Room Event Listener
Room Event Listener
Location: Each room has a dedicated goroutine listening for game events and broadcasting them to clients.
ws/rooms.go:41Client Read/Write Loops
Client Read/Write Loops
Location: Each client has two goroutines for bidirectional WebSocket communication.
ws/client.go:46,100Race Condition Prevention
- RWMutex on Game: Protects concurrent access to game state
- Channel-based messaging: No shared memory for events
- Select statement: Atomic channel operations
- Immutable questions: Question slice never modified during game
- Player score updates: Only modified by game goroutine
Message Broadcasting
From Game to Clients
Message Rendering
Location:ws/rooms.go:43
Different message types are rendered differently:
Question Rendering
Question Rendering
- Question text and options
- Current question number (e.g., “3 of 10”)
- Timer duration
- Current player scores
Info Message Rendering
Info Message Rendering
Game End Rendering
Game End Rendering
Error Handling
Game-Level Errors
The game engine itself has minimal error handling:- Questions assumed to be valid
- Players assumed to exist
- Timers always fire
- Channels never close unexpectedly
Error handling is primarily done at the boundary layers (HTTP handlers, WebSocket clients, API calls), not within core game logic.
Validation Points
- Before game creation: Questions validated by Question Service
- Before game start: Players validated by WebSocket authentication
- During answer submission: Answer format validated by client read loop
- After game end: No validation needed (message channel closed)
Testing Considerations
Location:internal/core/game/game_test.go
The game logic is unit tested by:
- Mocking question data
- Creating test players
- Simulating answer submissions
- Verifying scoring logic
- Testing timer behavior
Example Test Structure
Example Test Structure
Performance Characteristics
Time Complexity
- Starting game: O(n) where n = number of questions
- Collecting answers: O(m) where m = number of players
- Scoring: O(m) per question
- Overall game: O(n × m)
Space Complexity
- Questions: O(n)
- Players: O(m)
- Answers per question: O(m)
- Total: O(n + m)
Concurrency
- Goroutines per game: 1 (main game loop) + 1 (room event listener)
- Goroutines per client: 2 (read + write loops)
- Total for 4-player game: 1 + 1 + (4 × 2) = 10 goroutines
Goroutines are lightweight, so even with 100 concurrent games, the overhead is minimal.
Design Patterns
Event-Driven Architecture
The game emits events rather than calling methods:- Decouples game logic from presentation
- Allows multiple listeners
- Enables easy testing and monitoring
Channel-Based State Machine
The select loop inAskQuestion() is a state machine:
- Collecting: Waiting for answers or timer
- Scoring: Timer expired, score answers
- Progressing: Move to next question or end game
Fire-and-Forget Messaging
Once a message is sent toMessage channel:
- Game doesn’t wait for acknowledgment
- Game doesn’t know which clients received it
- Responsibility transfers to Room layer
Integration Points
Question Service
Package:
internal/core/questionProvides questions for game initialization. Game doesn’t fetch questions itself.WebSocket Room
Package:
internal/web/wsListens to Message channel and broadcasts to clients. Game doesn’t know about WebSockets.Render Package
Package:
internal/web/RenderConverts game entities to HTML. Game produces data, render creates presentation.Metrics
Package:
internal/web/metricsTracks game-related metrics. Game is instrumented but doesn’t collect metrics itself.Extension Points
Adding New Message Types
- Define constant in
domain.go - Create payload struct
- Emit message in game logic
- Handle in room event listener
- Create renderer in Render package
Implementing Power-Ups
Adding Bonus Points
Implementing Categories/Difficulty
Questions already have category from Trivia API:Related Documentation
WebSocket System
Learn how game messages reach clients via WebSocket
System Overview
See how game engine fits into overall architecture