🧱 Enemy Cloner — Prototype (System Design Question — Solution)
This document is a solution write-up for the system design question: implement a game enemy cloning system where new enemies are created by cloning existing prototypes with slight variations. It presents the problem, a practical TypeScript implementation using the Prototype pattern, usage examples, and notes on production considerations.
Problem — Enemy Cloner (Prototype use-case)
Games often need to create many similar but slightly different enemies efficiently at runtime. The requirements for a robust enemy cloning system are:
- Maintain a registry of prototype enemies that serve as templates
- Create new enemies by cloning existing prototypes
- Apply controlled variations to cloned enemies (health, attack, speed)
- Ensure proper deep cloning of nested structures (inventory, equipment)
- Support deterministic variation for testing and multiplayer sync
- Optimize performance for frequent spawn/despawn cycles
Why does this map well to the Prototype pattern?
- Creating enemies from scratch is expensive and involves complex initialization
- Many enemies share common base attributes with small variations
- Runtime performance is critical - cloning is faster than construction
- The pattern supports both shallow and deep cloning strategies
- Prototypes can be loaded from data and modified at runtime
Example Inputs (for the tutorial)
A TypeScript interface defining the core Enemy contract:
export interface IEnemy<T> {
getProps(): {
id: string;
type: TEnemyType;
props: TEnemyProps;
};
setProp(key: keyof TEnemyProps, value: number | TPosition): void;
clone(): T;
}
export type TEnemyType = "goblin" | "archer";
export type TPosition = {
x: number;
y: number;
};
export type TEnemyProps = {
speed?: number;
health?: number;
attack?: number;
defense?: number;
maxHealth?: number;
position?: TPosition;
};Solution — TypeScript Enemy Cloning System
We'll implement these key components:
- BaseEnemy: Abstract base class implementing common clone logic
- PrototypeRegistry: Central store for enemy prototypes
- SpawnManager: Handles prototype selection and variation application
- VariationSystem: Applies controlled modifications to cloned enemies
Key Design Choices
- Generic type-safe interface for enemy cloning
- Copy constructor pattern for reliable cloning
- Immutable prototype instances
- Property getters/setters for encapsulation
- Position randomization per spawn
- Unique ID generation per clone
🧩 Implementation (Concise, Annotated)
export class GoblinSmall implements IEnemy<GoblinSmall> {
private id: string;
private speed: number;
private health: number;
private attack: number;
private defense: number;
private type: TEnemyType;
private maxHealth: number;
private position: TPosition;
public constructor(propsOrInstance: TEnemyProps | GoblinSmall = {}) {
this.type = "goblin";
this.id = generateRandomId("goblin-small");
if (propsOrInstance instanceof GoblinSmall) {
// Copy constructor logic
this.speed = propsOrInstance.speed;
this.health = propsOrInstance.health;
this.attack = propsOrInstance.attack;
this.defense = propsOrInstance.defense;
this.maxHealth = propsOrInstance.maxHealth;
this.position = propsOrInstance.position;
}
else {
this.speed = propsOrInstance.speed || 10;
this.health = propsOrInstance.health || 100;
this.attack = propsOrInstance.attack || 15;
this.defense = propsOrInstance.defense || 5;
this.maxHealth = propsOrInstance.maxHealth || 100;
this.position = propsOrInstance.position || { x: 0, y: 0 };
}
}
public getProps(): { id: string; type: TEnemyType; props: TEnemyProps; } {
return {
id: this.id,
type: this.type,
props: {
speed: this.speed,
health: this.health,
attack: this.attack,
defense: this.defense,
maxHealth: this.maxHealth,
position: this.position,
},
};
}
public setProp(key: keyof TEnemyProps, value: number | TPosition): void {
(this as any)[key] = value;
}
public clone(): GoblinSmall {
return new GoblinSmall(this);
}
}
export class GoblinElite implements IEnemy<GoblinElite> {
private id: string;
private speed: number;
private health: number;
private attack: number;
private defense: number;
private type: TEnemyType;
private maxHealth: number;
private position: TPosition;
public constructor(propsOrInstance: TEnemyProps | GoblinElite = {}) {
this.type = "goblin";
this.id = generateRandomId("goblin-elite");
if (propsOrInstance instanceof GoblinElite) {
// Copy constructor logic
this.speed = propsOrInstance.speed;
this.health = propsOrInstance.health;
this.attack = propsOrInstance.attack;
this.defense = propsOrInstance.defense;
this.maxHealth = propsOrInstance.maxHealth;
this.position = propsOrInstance.position;
}
else {
this.speed = propsOrInstance.speed || 15;
this.health = propsOrInstance.health || 200;
this.attack = propsOrInstance.attack || 30;
this.defense = propsOrInstance.defense || 10;
this.maxHealth = propsOrInstance.maxHealth || 200;
this.position = propsOrInstance.position || { x: 0, y: 0 };
}
}
// ... similar methods as GoblinSmall
}
export class Archer implements IEnemy<Archer> {
private id: string;
private speed: number;
private health: number;
private attack: number;
private defense: number;
private type: TEnemyType;
private maxHealth: number;
private position: TPosition;
public constructor(propsOrInstance: TEnemyProps | Archer = {}) {
this.type = "archer";
this.id = generateRandomId("archer");
if (propsOrInstance instanceof Archer) {
// Copy constructor logic
this.speed = propsOrInstance.speed;
this.health = propsOrInstance.health;
this.attack = propsOrInstance.attack;
this.defense = propsOrInstance.defense;
this.maxHealth = propsOrInstance.maxHealth;
this.position = propsOrInstance.position;
}
else {
this.speed = propsOrInstance.speed || 12;
this.health = propsOrInstance.health || 150;
this.attack = propsOrInstance.attack || 25;
this.defense = propsOrInstance.defense || 7;
this.maxHealth = propsOrInstance.maxHealth || 150;
this.position = propsOrInstance.position || { x: 0, y: 0 };
}
}
// ... similar methods as GoblinSmall
}🧠 Usage Example
export class Client {
private static spawnEnemy(e: IEnemy<GoblinSmall | GoblinElite | Archer>): void {
const enemy = e.clone();
// Give each spawned enemy a random position
const position = getRandomPosition();
enemy.setProp("position", position);
console.log(JSON.stringify(enemy.getProps(), null, 4));
}
public static run(): void {
// Create prototype instances
const archerPrototype = new Archer();
const goblinPrototype = new GoblinSmall();
const goblinElitePrototype = new GoblinElite();
// Spawn multiple instances of each type
for (let i = 0; i < 3; i++) {
console.log("\n=== Spawning Archer ===\n");
this.spawnEnemy(archerPrototype);
console.log("\n=== Spawning Goblin Small ===\n");
this.spawnEnemy(goblinPrototype);
console.log("\n=== Spawning Goblin Elite ===\n");
this.spawnEnemy(goblinElitePrototype);
}
}
}
// Example output:
/*
=== Spawning Archer ===
{
"id": "archer-5f3c1e2a7b9d",
"type": "archer",
"props": {
"speed": 12,
"health": 150,
"attack": 25,
"defense": 7,
"maxHealth": 150,
"position": {
"x": 45,
"y": 78
}
}
}
=== Spawning Goblin Small ===
{
"id": "goblin-small-9a4b2c3d5e6f",
"type": "goblin",
"props": {
"speed": 10,
"health": 100,
"attack": 15,
"defense": 5,
"maxHealth": 100,
"position": {
"x": 12,
"y": 34
}
}
}
*/Notes, Edge-Cases & Alternatives
-
Type Safety:
- Using generics ensures type-safe cloning
- Each enemy type returns its own specific type from clone()
- Properties are strictly typed with appropriate interfaces
-
Constructor Pattern:
- Copy constructor approach provides clean initialization
- Default values ensure valid state for new instances
- Clear separation between new creation and cloning
-
Property Management:
- Encapsulated properties prevent direct state manipulation
- getProps() provides a safe way to read enemy state
- setProp() allows controlled property updates
-
Position Handling:
- Random positioning on spawn adds gameplay variety
- Position is treated as a complex type (TPosition)
- Coordinates are bounded to valid game area
-
ID Generation:
- Each clone gets a unique, prefixed ID
- IDs encode enemy type for easy identification
- No possibility of ID collisions within enemy types
✅ Conclusion
The Prototype pattern provides an elegant solution for creating varied game enemies through cloning. The TypeScript implementation demonstrates how to use generics, copy constructors, and proper encapsulation for a type-safe and maintainable system.
Key benefits realized:
- Type-safe cloning with generics
- Clean object creation through copy constructors
- Proper encapsulation of enemy state
- Unique identification of enemy instances
- Flexible property management
- Easy extension for new enemy types
Read more
System Design Question — Configuration Manager (Singleton) — Solution
Solution: Implement a Singleton Configuration Manager in TypeScript — problem statement, implementation, usage, and notes.
System Design Question — Resume Builder (Builder) — Solution
Solution: Implement a Resume Builder in TypeScript — problem statement, implementation, usage, and notes.
Creational Design Patterns Explained with Real-World Examples
Understand the five key creational design patterns — Singleton, Factory Method, Abstract Factory, Builder, and Prototype — using real-world coding scenarios.