convex-game-management
Manage game lifecycle, player turns, round rotation, scoring, and game state transitions. Use when implementing game creation, turn management, score updates, and game completion flows in PictionAI.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for managing multiplayer game lifecycles, player turns, and atomic scoring systems. Specifically optimized for Convex backends and React integrations, it streamlines the implementation of complex game states, round rotations, and real-time state transitions while ensuring data consistency through atomic mutations.
Use Cases
- Implementing real-time multiplayer game logic with automated turn rotation and round-robin player management.
- Developing atomic scoring systems that calculate points based on time-sensitive player actions and correct guesses.
- Managing game lobby transitions, including player joining, game start triggers, and final state determination.
- Integrating Convex backend mutations and queries into React-based gaming interfaces for seamless state synchronization.
- Handling edge cases in turn-based games such as timer expiration, manual winner selection, and late guess arrivals.
| name | convex-game-management |
|---|---|
| description | Manage game lifecycle, player turns, round rotation, scoring, and game state transitions. Use when implementing game creation, turn management, score updates, and game completion flows in PictionAI. |
| compatibility | Requires Convex backend with mutations and queries, React hooks (useQuery, useMutation) |
| author | PictionAI |
| category | backend |
| frameworks | Convex, Next.js |
Convex Game Management
Overview
This skill handles the complete game lifecycle for PictionAI's multiplayer Pictionary game, including game creation, player management, turn-based rotation, atomic turn completion with scoring, and game state transitions.
Core Concepts
Game States
- waiting - Game created, players joining in lobby
- started - Game in progress, turns rotating
- finished - All rounds complete, winner determined
Turn States
- drawing - Active turn, timer running (60-120 seconds)
- completed - Turn finished with correct guess
- time_up - Timer expired, no points awarded
Atomic Turn System
Turns are completed atomically to ensure data consistency:
- Start Turn: Drawer calls
startNewTurnmutation - Draw & Guess Phase: Real-time canvas sync, players submit guesses
- Turn Completion: Three atomic scenarios:
- ✅ Correct guess → Both guesser and drawer earn points
- 🏆 Manual winner selection → Drawer selects winner from guessers
- ⏱️ Time up → No points, proceed to next turn
- Score Updates: Dual scoring system (guesser time bonus + drawer base score)
- Next Turn: Auto round-robin rotation to next player
Scoring System
Guess Scoring (Correct Answer)
- Guesser: Base 50 points + time bonus (50 - elapsed_seconds, min 5 points)
- Drawer: 25% of guesser's score, minimum 10 points
Drawer Scoring (Manual Winner)
- Selected Guesser: 30 points
- Drawer: 25 points
Key Mutations
startNewTurn
Start a new drawing turn, assign card, initialize timer.
mutation startNewTurn {
args: {
game_id: Id<"games">
}
// Returns: {
// turn_id: Id<"turns">,
// card: { word: string, category: string },
// drawer_id: Id<"users">,
// time_limit: number
// }
}
submitGuessAndCompleteTurn
Submit a guess and complete the turn with atomic scoring.
mutation submitGuessAndCompleteTurn {
args: {
game_id: Id<"games">,
turn_id: Id<"turns">,
guesser_id: Id<"users">,
guess: string,
elapsed_time: number,
is_correct?: boolean
}
// Handles three scenarios atomically:
// 1. Correct guess → Award points to both
// 2. Manual selection → Drawer chooses winner
// 3. Time up → Skip to next turn
}
selectWinner (Manual Selection)
Drawer selects a guesser as the winner when time expires.
mutation selectWinner {
args: {
turn_id: Id<"turns">,
selected_guesser_id: Id<"users">
}
// Awards 30 points to guesser, 25 to drawer
}
Key Queries
getGame
Fetch complete game state with players, current turn, scores.
query getGame {
args: { game_id: Id<"games"> }
// Returns full game with nested players, turns, scores
}
getGameTurns
Get all turns in a game with guesses and results.
query getGameTurns {
args: { game_id: Id<"games"> }
// Returns array of turns with scoring details
}
React Integration
// Start a new turn
const startTurn = useMutation(api.mutations.game.startNewTurn);
await startTurn({ game_id: gameId });
// Submit guess and complete turn
const submitGuess = useMutation(api.mutations.game.submitGuessAndCompleteTurn);
await submitGuess({
game_id: gameId,
turn_id: turnId,
guesser_id: userId,
guess: "elephant",
elapsed_time: 45,
});
// Fetch game state
const game = useQuery(api.queries.games.getGame, { game_id: gameId });
Database Schema References
// Games table
games: defineTable({
code: v.string(), // Unique game code
creator_id: v.id("users"),
players: v.array(
v.object({
user_id: v.id("users"),
score: v.number(),
is_drawer: v.boolean(),
})
),
state: v.union(
v.literal("waiting"),
v.literal("started"),
v.literal("finished")
),
current_turn_index: v.number(),
created_at: v.optional(v.number()),
round_count: v.number(),
});
// Turns table
turns: defineTable({
game_id: v.id("games"),
drawer_id: v.id("users"),
card_id: v.id("cards"),
state: v.union(
v.literal("drawing"),
v.literal("completed"),
v.literal("time_up")
),
timer_started_at: v.number(),
time_limit: v.number(),
correct_guesser_id: v.optional(v.id("users")),
guesses: v.array(
v.object({
guesser_id: v.id("users"),
guess: v.string(),
timestamp: v.number(),
is_correct: v.optional(v.boolean()),
})
),
});
Common Patterns
Monitor active turn
const game = useQuery(api.queries.games.getGame, { game_id });
const currentTurn = game?.turns[game.current_turn_index];
const isMyTurn = currentTurn?.drawer_id === userId;
Handle turn completion
// When drawer completes turn with guess result
const completeWithCorrect = useMutation(
api.mutations.game.submitGuessAndCompleteTurn
);
await completeWithCorrect({
game_id: gameId,
turn_id: turnId,
guesser_id: guesserId,
guess: "elephant",
elapsed_time: 30,
is_correct: true,
});
Automatic next turn
The mutation automatically:
- Rotates drawer (round-robin through players)
- Assigns new card
- Starts timer
- Updates game state
Edge Cases
- Multiple guesses per player: Allowed, only latest is scored
- Guess submitted after timer: System checks elapsed time on server
- Late guess arrival: Real-time subscription updates guess feed
- Manual selection ambiguity: Drawer must explicitly select one winner
- Game with 2 players: Drawer rotation still works with round-robin
See Also
- TURN_MANAGEMENT_ANALYSIS.md - Detailed atomic turn analysis
convex/mutations/game.ts- Complete mutation implementationscomponents/game/game-board.tsx- UI integration