The Idea Behind doodlz.io
What happens when you combine the drawing skills of random internet strangers with the creative captioning powers of the masses? You get doodlz.io - a multiplayer game that I built to bring people together through collaborative creativity and humor.
The concept is simple but engaging: players draw images and write slogans, then mix and match them to find the funniest combinations. It's like a digital mashup of Pictionary and Cards Against Humanity, with real-time interaction that makes each session unique and unpredictable.

Technical Architecture
Building a real-time multiplayer game comes with interesting technical challenges. Here's how I approached the architecture of doodlz.io:
SvelteKit as the Foundation
I chose SvelteKit as the framework for several key reasons:
- Performance: Svelte's compilation approach means less JavaScript sent to the browser and faster runtime performance.
- Seamless transitions: SvelteKit's page transitions create a smooth, app-like experience.
- Developer experience: The reactive programming model makes state management intuitive.
// Example of Svelte's reactive declarations
let drawings = [];
let captions = [];
let combinations = [];
// This automatically updates when the source arrays change
$: combinations = drawings.flatMap(drawing =>
captions.map(caption => ({ drawing, caption }))
);
Real-time Communication with WebSockets
For the real-time multiplayer functionality, WebSockets were the obvious choice. They allow:
- Instant drawing updates as players create their masterpieces
- Real-time voting on combinations
- Immediate notifications when players join or leave
- Low latency for a responsive game experience
// Server-side WebSocket implementation
const wss = new WebSocketServer({ server });
wss.on('connection', ws => {
// Assign player to a game room
const roomId = assignRoom(ws);
ws.on('message', message => {
const data = JSON.parse(message);
// Broadcast drawing updates to other players
if (data.type === 'drawing-update') {
broadcastToRoom(
roomId,
{
type: 'drawing-update',
playerId: data.playerId,
points: data.points,
},
ws
);
}
// Handle other message types...
});
});
The Drawing System
Creating an intuitive drawing system that works well across devices was a fun challenge. I implemented a canvas-based solution with touch and mouse support:
Canvas Drawing Logic
function setupCanvas(canvas) {
const ctx = canvas.getContext('2d');
let drawing = false;
// Handle both mouse and touch events
const startDrawing = e => {
drawing = true;
const { x, y } = getPointerPosition(e);
ctx.beginPath();
ctx.moveTo(x, y);
sendDrawingUpdate({ type: 'start', x, y });
};
const draw = e => {
if (!drawing) return;
const { x, y } = getPointerPosition(e);
ctx.lineTo(x, y);
ctx.stroke();
sendDrawingUpdate({ type: 'move', x, y });
};
const stopDrawing = () => {
drawing = false;
sendDrawingUpdate({ type: 'end' });
};
// Set up event listeners...
}
Stroke Optimization
To ensure smooth multiplayer drawing experiences, I implemented stroke optimization that balances detail and network efficiency:
- Points are sampled based on movement speed
- Redundant points are eliminated
- Strokes are compressed before sending over the network
- Client-side interpolation smooths out any network lag
Game Flow and State Management
The game progresses through several distinct phases, each with its own UI and interaction patterns:
- Lobby Phase: Players join and wait for enough participants
- Drawing Phase: Time-limited round where everyone draws an image
- Caption Phase: Players write funny captions without seeing the drawings
- Combination Phase: Drawings and captions are randomly paired
- Voting Phase: Players vote on the funniest combinations
- Results Phase: Scores are tallied and winners announced
Managing this complex state flow required careful planning:
// Game state machine
const gameStates = {
LOBBY: 'lobby',
DRAWING: 'drawing',
CAPTION: 'caption',
COMBINATION: 'combination',
VOTING: 'voting',
RESULTS: 'results',
};
function createGameStore() {
const { subscribe, set, update } = writable({
state: gameStates.LOBBY,
players: [],
drawings: [],
captions: [],
combinations: [],
votes: {},
timeRemaining: 0,
});
// State transition functions
function advanceState() {
update(game => {
switch (game.state) {
case gameStates.LOBBY:
return { ...game, state: gameStates.DRAWING, timeRemaining: 60 };
case gameStates.DRAWING:
return { ...game, state: gameStates.CAPTION, timeRemaining: 45 };
// Other state transitions...
}
});
}
// Other methods...
return {
subscribe,
advanceState,
// Other exported methods...
};
}
Challenges and Solutions
Synchronization Across Clients
One of the biggest challenges was keeping the game state synchronized across clients with varying connection speeds. I implemented:
- A central authority model where the server is the source of truth
- Optimistic UI updates that show immediate feedback
- Conflict resolution when client predictions don't match server state
- Catchup mechanisms for clients that experience connection issues
Performance Optimization
Drawing applications can be resource-intensive, especially on mobile devices. To ensure smooth performance:
- Canvas operations are optimized to minimize redraws
- WebSocket message sizes are kept minimal
- Drawings are stored as vector paths rather than bitmap data
- Client rendering is throttled based on device capabilities
Mobile Touch Support
Supporting touch devices required special consideration:
- Preventing unwanted scrolling and zooming during drawing
- Calibrating touch precision for accurate drawing
- Optimizing UI layout for smaller screens
- Handling multi-touch gestures for special drawing tools
Lessons Learned
Building doodlz.io taught me valuable lessons about real-time web applications:
"The complexity of multiplayer games lies not in the features themselves, but in making them work seamlessly across network conditions and device types."
What Worked Well
- SvelteKit: The framework's simplicity and performance made development enjoyable
- WebSockets: Providing real-time communication without unnecessary complexity
- Phased gameplay: Breaking the game into distinct phases kept players engaged
What I'd Do Differently
If I were to start over, I might:
- Use a specialized game state synchronization library
- Implement more aggressive client-side prediction
- Build a custom drawing library optimized for this specific use case
Try It Yourself!
doodlz.io is live and free to play! Gather some friends, join a game room, and experience the chaotic fun of collaborative drawing and caption creation.
The game works best with 4-8 players and each session typically lasts about 15-20 minutes. It's perfect for virtual hangouts, team building, or just a fun way to spend time with friends.
So what are you waiting for? Your artistic (or hilariously non-artistic) skills are needed at doodlz.io!