Introduction
Imagine a typing race game where participants compete in real-time. What if a commentator bot could enhance the experience — announcing racers, tracking progress, and throwing in quirky jokes? This isn't just a fun feature — it's a chance to apply real-world software design patterns and principles to create a maintainable, extensible solution.
In this article, I’ll walk you through designing a smart commentator bot, highlighting how established software practices can guide even seemingly playful features toward clean and scalable implementations.
Table of Contents
- The Problem: Commentating a Typing Race
- High-Level Architecture
- Choosing the Right Design Patterns
- Principles in Action: SOLID and DRY
- Handling Real-Time Updates via WebSockets
- Adding Personality: Jokes, Stories, and Behavior
- Conclusion
The Problem: Commentating a Typing Race
Your mission is to create a commentator bot that accompanies a real-time typing race. The bot should behave like a sports announcer — introducing participants, giving regular updates on positions, and reporting final results.
But it's not just about spitting out messages. The bot needs to:
- Welcome users and introduce itself
- Announce participants with details like name and vehicle
- Track position and report status every 30 seconds
- Notify when someone is approaching or crossing the finish line
- Share race results at the end
- Tell jokes or stories between announcements
- Avoid offensive language
And, importantly — it should do this live, using technologies like HTTP, REST, and WebSockets.
The challenge? Making this fun and dynamic feature well-structured, reusable, and testable. That’s where patterns and principles come in.
High-Level Architecture
Before jumping into code, let's define the architecture. Here's a high-level breakdown of how the commentator bot fits into the system:
[Client UI] <--> [WebSocket Server] <--> [Race Engine]
|
[Commentator Bot]
- Client UI – Shows the race and displays commentator messages.
- WebSocket Server – Broadcasts race updates to connected clients and to the bot.
- Race Engine – Simulates race logic (progress, positions, timing).
- Commentator Bot – Subscribes to race events and produces dynamic commentary.
The commentator bot acts as a consumer of race events and a producer of commentary messages, which are sent back through the same WebSocket pipeline to be rendered in the UI.
This separation keeps race logic clean, while allowing the commentator logic to evolve independently.
Choosing the Right Design Patterns
To keep our bot maintainable and adaptable, we can apply a few classic design patterns:
🧠 Strategy Pattern
The bot can use different strategies for formatting commentary — e.g., serious sports-style, humorous, or meme-based. Switching strategies becomes easy with the Strategy pattern.
interface CommentaryStrategy {
generateComment(event: RaceEvent): string;
}
class SportsStyleCommentary implements CommentaryStrategy {
generateComment(event) {
return `🚦 Racer ${event.racerName} is now in position ${event.position}!`;
}
}
class FunnyCommentary implements CommentaryStrategy {
generateComment(event) {
return `😄 ${event.racerName} just zoomed past like it's Monday morning!`;
}
}
📢 Observer Pattern
The bot needs to listen to race events. The Observer pattern allows it to subscribe to relevant updates without tightly coupling it to the race engine.
🧩 Decorator Pattern
Want to add jokes or fun facts dynamically? The Decorator pattern can help wrap standard comments with personality.
function withRandomJoke(baseComment: string): string {
const jokes = ["Did you know? Typing fast burns calories!", "This racer is powered by coffee ☕"];
return `${baseComment} — ${jokes[Math.floor(Math.random() * jokes.length)]}`;
}
Principles in Action: SOLID and DRY
Building the bot around key software principles makes it easier to test, scale, and extend.
- S: Single Responsibility — Each class (commentator, strategy, announcer) has one job.
- O: Open/Closed Principle — Add new commentary styles without modifying existing logic.
- L: Liskov Substitution — Interchange strategies or announcers freely.
-
I: Interface Segregation — Keep interfaces small and focused (e.g.,
RaceListener
,Announcer
). - D: Dependency Inversion — Use abstractions so we can swap out services (e.g., for tests or mocks).
And of course:
- Don't Repeat Yourself (DRY) — Reuse message templates, formatting logic, and event handling wherever possible.
Handling Real-Time Updates via WebSockets
A commentator bot is only as good as its timing. To be effective, it must react in real-time to race events — start, progress updates, finish — and WebSockets are the perfect tool for this.
Setting Up WebSocket Communication
The WebSocket server acts as a hub that delivers race events both to players (for UI updates) and to the bot (for commentary).
// Sample WebSocket event from the server
{
type: 'RACE_UPDATE',
payload: {
racerId: 'racer-1',
position: 2,
progress: 74,
distanceLeft: 26,
timeRemaining: 34
}
}
Subscribing to Race Events
The bot can connect as a client and listen to these events:
const socket = new WebSocket('ws://localhost:3000');
socket.onmessage = (message) => {
const event = JSON.parse(message.data);
const comment = commentator.generateComment(event);
sendToUI(comment);
};
Emitting Comments Back to the UI
Once a comment is generated, it’s pushed back to clients via a COMMENTARY_UPDATE
message:
function sendToUI(comment: string) {
socket.send(JSON.stringify({
type: 'COMMENTARY_UPDATE',
payload: { text: comment }
}));
}
This design creates a full-duplex loop:
- Players' actions update the race state.
- Race events trigger commentator reactions.
- Comments are sent back to the UI for real-time display.
It’s reactive, engaging, and scalable — even if more bots are added later (e.g., cheering fans, announcers in different languages, etc.).
Adding Personality: Jokes, Stories, and Behavior
A good commentator doesn’t just deliver dry stats — they entertain. To make the bot feel alive and fun, we can inject humor, trivia, and a bit of unpredictability.
Commentary Templates
Create reusable templates for different race events:
const templates = {
start: (racer) => `🔥 Racer ${racer.name} is at the starting line!`,
progress: (racer) => `🏁 ${racer.name} is ${racer.progress}% through the race.`,
finish: (racer) => `🎉 ${racer.name} has finished the race in ${racer.time} seconds!`
};
Random Jokes and Facts
Use a small pool of random inserts to spice things up:
const interjections = [
"Did you know? The world record for typing speed is 216 wpm.",
"This racer types faster than I can think!",
"Legends say this keyboard is still smoking..."
];
function insertFlavor(comment) {
const flavor = interjections[Math.floor(Math.random() * interjections.length)];
return `${comment} ${flavor}`;
}
Dynamic Timing and Frequency
To avoid sounding robotic, vary the timing between comments, and don’t repeat the same phrases too often:
const lastCommentTimestamps = new Map();
function shouldComment(racerId) {
const now = Date.now();
const last = lastCommentTimestamps.get(racerId) || 0;
if (now - last > 10000) { // at least 10 seconds between updates
lastCommentTimestamps.set(racerId, now);
return true;
}
return false;
}
By combining structured logic with randomized content, your bot becomes more engaging without losing clarity. Think of it like combining TypeScript types with improv comedy 🤹.
Conclusion
Building a commentator bot for a typing race might seem like a playful experiment — and it is — but it’s also a serious opportunity to practice clean software architecture, apply classic design patterns, and reinforce core engineering principles.
From the use of the Strategy and Observer patterns to the application of SOLID and DRY principles, every part of this bot is an example of thoughtful design. Add in WebSocket-based real-time communication and a dash of personality, and you have a fun, extensible, and maintainable project.
Whether you’re building games, internal tools, or complex SaaS platforms — these patterns and principles will serve you well. And maybe, just maybe, your next system could use a funny commentator too 😉
Thanks for reading! If you found this helpful or entertaining, share it with your team or follow me for more content on clean code, architecture, and real-time applications.
Top comments (0)