This "standard starter" is the recommended implementation for RedwoodSDK. You get a Typescript project with:
- Vite
- database (Prisma via D1)
- Session Management (via DurableObjects)
- Passkey authentication (Webauthn)
- Storage (via R2)
npx create-rwsdk my-project-name
cd my-project-name
pnpm installpnpm devPoint your browser to the URL displayed in the terminal (e.g. http://localhost:5173/). You should see a "Hello World" message in your browser.
Within your project's wrangler.jsonc:
-
Replace the
__change_me__placeholders with a name for your application -
Create a new D1 database:
npx wrangler d1 create my-project-dbCopy the database ID provided and paste it into your project's wrangler.jsonc file:
For authentication setup and configuration, including optional bot protection, see the Authentication Documentation.
/**
- RISK-STYLE STRATEGY GAME - Durable Object Game State Manager
- TECH STACK:
-
- Cloudflare Workers & Durable Objects for distributed game state
-
- RedwoodJS SDK for full-stack framework
-
- WebSockets for real-time multiplayer communication
-
- TypeScript for type safety
- ARCHITECTURE:
-
- GameStateDO: Single source of truth for game state, handles all game logic
-
- WebSocket connections: Real-time bidirectional communication between clients and DO
-
- HTTP API: RESTful endpoints for game actions (POST/GET/PUT)
-
- AI Controller: Manages AI player actions with realistic delays
- GAME FLOW (Post-Restart):
-
- TERRITORY NUKING: Random territories get nuked based on nukeCount parameter
-
- SETUP PHASES: Turn-based setup with 4 sequential phases:
- a) UNITS PHASE: Each player places 3 units per turn on their territories
- b) LAND COMMANDER: Each player places 1 land commander on owned territory
- c) DIPLOMAT COMMANDER: Each player places 1 diplomat commander on owned territory
- d) SPACE BASE: Each player places 1 space base on owned territory
-
- MAIN GAME: Standard Risk-style gameplay with 7 phases per turn
- SETUP PHASE MECHANICS:
-
- Players alternate turns within each setup phase
-
- Phase only advances when ALL players complete their requirements
-
- AI players act automatically with delays for better UX
-
- Real-time updates via WebSocket broadcasts
-
- Turn validation prevents out-of-turn actions
- KEY FEATURES:
-
- Persistent game state in Durable Object storage
-
- Action replay system for game rewind functionality
-
- Robust error handling and validation
-
- AI player support with configurable difficulty
-
- Real-time multiplayer with WebSocket sync */
- Durable Object (DO) = Single source of truth for all game logic
- Server Actions = Thin wrappers that send events to DO
- WebSocket = Real-time state synchronization
- Client = UI layer that reacts to state changes
- Cloudflare Durable Objects - Distributed game state management
- RedwoodJS SDK - Full-stack framework
- WebSocket - Real-time multiplayer communication
- TypeScript - Type safety across the stack
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Client UI │───▶│ Server Actions │───▶│ Durable Object │
│ (React/TS) │ │ (Thin Layer) │ │ (Game Logic) │
└─────────────────┘ └──────────────────┘ └─────────────────────┘
▲ │
│ │
│ WebSocket Events │
└────────────────────────────────────────────────┘
- User Action → Client calls server action (e.g.,
placeUnit()) - Server Action → Calls
callGameDO()with action data - Durable Object → Processes action through reducer → Updates state
- WebSocket Broadcast → DO sends state update to all connected clients
- Client Update →
useGameSynchook receives update → UI re-renders
- Game Logic Processing - All rules, validation, and state transitions
- AI Player Management - Automated opponent actions with timing
- WebSocket Broadcasting - Real-time updates to all players
- State Persistence - Automatic save/restore in Cloudflare storage
- Action History - Complete audit trail with rewind capability
GameState {
// Core Game Info
id: string
status: 'setup' | 'bidding' | 'playing' | 'finished'
// Setup Phase Management
setupPhase: 'units' | 'land_commander' | 'diplomat_commander' | 'space_base' | 'complete'
// Turn Management
currentTurn: number (1-5)
currentPlayerPhase: number (1-6) // Main game phases
currentPlayerIndex: number
currentYear: number
// Players & Territories
players: Player[]
territories: Record<string, Territory>
turnOrder: string[]
activeTurnOrder: string[]
// Bidding System (Years 1-5)
bidding?: {
year: number
bidsSubmitted: Record<string, number>
bidsRevealed: boolean
playersWaitingToBid: string[]
finalTurnOrder?: string[]
highestBidder?: string
tiebreakRoll?: Record<string, number>
}
// Action History
actions: GameAction[]
currentActionIndex: number
}Server actions are thin wrappers that translate client requests into Durable Object events.
export async function actionName(gameId: string, playerId: string, ...params): Promise<GameState> {
try {
// 1. Validate inputs
console.log('actionName called:', { gameId, playerId, ...params })
// 2. Call Durable Object with standardized format
const result = await callGameDO(gameId, 'applyAction', {
type: 'action_type',
playerId,
data: { ...actionData }
})
// 3. Trigger real-time updates
await renderRealtimeClients({
durableObjectNamespace: env.REALTIME_DURABLE_OBJECT as any,
key: `/game/${gameId}`,
});
// 4. Return updated state
return result as GameState
} catch (error) {
console.error('actionName failed:', error)
throw new Error('Action failed')
}
}placeUnit(gameId, playerId, territoryId, count)- Deploy units during setupplaceCommander(gameId, playerId, territoryId, commanderType)- Place land/diplomat commandersplaceSpaceBase(gameId, playerId, territoryId)- Build space bases
submitBid(gameId, playerId, bidAmount)- Submit energy bid for turn orderstartYearTurns(gameId)- Begin main game after bidding
attackTerritory(gameId, playerId, fromId, toId, units)- Combat between territoriesfortifyTerritory(gameId, playerId, fromId, toId, units)- Move units between own territoriescollectEnergy(gameId, playerId, amount)- Gain resourcesspendEnergy(gameId, playerId, amount)- Use resources
restartGameWithNuking(gameId, player1Id, player2Id, nukeCount)- Reset game with random territory destructionrewindToAction(gameId, actionIndex)- Time-travel to previous game state
- Objective: Each player deploys starting units (3 per turn)
- Rules: Players alternate turns, can place max 3 units per turn on owned territories
- Completion: When all players have 0
remainingUnitsToPlace - AI Behavior: Places units randomly on owned territories with 800ms delays
- Objective: Each player places 1 land commander
- Rules: Must place on owned territory, only 1 per player
- Completion: When all players have a territory with
landCommander = playerId - AI Behavior: Places on strongest territory (most units)
- Objective: Each player places 1 diplomat commander
- Rules: Must place on owned territory, only 1 per player
- Completion: When all players have a territory with
diplomatCommander = playerId - AI Behavior: Places on random strong territory
- Objective: Each player builds 1 space base
- Rules: Must place on owned territory, only 1 per player
- Completion: When all players have a territory with
spaceBase = playerId - AI Behavior: Prefers territories with commanders, otherwise random
- Transition: After completion →
status: 'bidding'for Year 1
- Objective: Players bid energy to determine turn order for the year
- Rules:
- Each player submits secret bid (0 to current energy)
- Highest bidder goes first
- Ties broken by d20 dice roll
- All players lose bid energy regardless of outcome
- AI Behavior: Bids 20-40% of available energy
- Completion: When all bids submitted → auto-reveal → set turn order
- Transition: After turn order set →
status: 'playing'
- Collect & Deploy (
currentPlayerPhase: 1) - Gain income, place units - Build & Hire (
currentPlayerPhase: 2) - Construct buildings, hire commanders - Buy Cards (
currentPlayerPhase: 3) - Purchase command cards with energy - Play Cards (
currentPlayerPhase: 4) - Activate purchased cards - Invade (
currentPlayerPhase: 5) - Attack enemy territories - Fortify (
currentPlayerPhase: 6) - Redistribute units
- After Phase 6 → Next player starts Phase 1
- After all players complete 6 phases → Next year begins with bidding
- After 5 years → Game ends (
status: 'finished')
- Detection: Players with
name === 'AI Player' - Registration:
globalAIController.addAIPlayer(playerId, 'medium') - Timing: Configurable delays (
AI_TURN_SPEED_MS = 500ms)
// Setup Phase AI
orchestrateAISetupAction(gameState, onStateUpdate, onProgressionNeeded)
// Main Game AI
doAIMainGameAction() // Cycles through 6 phases
// Bidding AI
doAIBiddingAction() // Strategic energy bidding- Setup: Random unit placement, strategic commander placement
- Bidding: 20-40% energy bids with randomization
- Combat: Simple attacking logic (not fully implemented)
- Resource Management: Basic income optimization
- Connection: Client connects to
/__gsync?key=${gameId} - Routing:
gameRoutes.ts→ Direct toGameStateDO - Events: State updates, player joins, game restarts, errors
const { gameState, isConnected, isLoading, error } = useGameSync({
gameId,
playerId: currentUserId,
onStateUpdate: (newState) => {
// React to game state changes
console.log('Game updated:', newState.status, newState.setupPhase)
}
})Every Durable Object action automatically broadcasts to all connected clients:
this.broadcast({
type: 'state_update',
state: this.gameState
})- MobileGameUI.tsx - Main game container
- GameMap.tsx - Territory visualization and interaction
- BiddingOverlay.tsx - Energy bidding interface
- GameStats.tsx - Player information sidebar
- Setup: Territory-based actions (place units, commanders, bases)
- Bidding: Energy allocation interface
- Playing: Multi-mode interactions (attack, fortify, info)
- User clicks territory/button
- UI calls appropriate server action
useGameSyncreceives WebSocket update- UI re-renders with new state
- Action feedback via loading states
1. applyAction(actionData) // Entry point
2. createGameAction(actionData) // Add ID, timestamp, snapshot
3. reduceAction(gameState, action) // Apply game logic
4. persist() // Save to Cloudflare storage
5. broadcast() // WebSocket to all clients
6. handleProgression() // Check for phase/turn advancement
7. scheduleAI() // Queue AI actions if needed- Input Validation - Territory ownership, resource requirements
- Game Rules - Phase restrictions, turn order enforcement
- State Transitions - Automatic progression when phases complete
- History Tracking - Every action saved for rewind functionality
- Validation Failures - Return unchanged state
- Network Issues - Client reconnection with full state sync
- AI Errors - Graceful fallback with console logging
- 4-Phase Setup System - Fully working with AI
- Durable Object Architecture - State persistence and broadcasting
- WebSocket Real-time Sync - Multi-player state synchronization
- AI Player System - Automated opponents with strategic delays
- Action History - Complete audit trail with rewind
- Mobile-First UI - Touch-optimized game interface
- Bidding System - Logic complete, server action routing fix needed
- Main Game Phases - Structure in place, individual phase logic needed
- Combat System - Dice rolling, territory conquest
- Resource Management - Energy income, card purchasing
- Victory Conditions - 5-year game completion
- Advanced AI - Strategic decision making
- Define Server Action in
gameActions.ts - Add Action Type to GameState types
- Implement Reducer Case in
gameDurableObject.ts - Add UI Handler in game components
- Test Full Flow setup → action → update
- Console Logs - Extensive logging in DO and server actions
- Browser DevTools - Network tab for action monitoring
- WebSocket Messages - Real-time state change visibility
- Action History - Rewind to debug specific states
- Timeout Management - Careful cleanup to prevent race conditions
- Strategic Logic - Isolated functions for easy testing
- Timing Controls - Configurable delays for UX optimization
This architecture provides a solid foundation for a complex multiplayer strategy game with real-time synchronization, AI opponents, and robust state management.
{ "d1_databases": [ { "binding": "DB", "database_name": "my-project-db", "database_id": "your-database-id", }, ], }