Files
gridpilot.gg/packages/racing/domain/entities/Track.ts
2025-12-08 23:52:36 +01:00

120 lines
2.9 KiB
TypeScript

/**
* Domain Entity: Track
*
* Represents a racing track/circuit in the GridPilot platform.
* Immutable entity with factory methods and domain validation.
*/
export type TrackCategory = 'oval' | 'road' | 'street' | 'dirt';
export type TrackDifficulty = 'beginner' | 'intermediate' | 'advanced' | 'expert';
export class Track {
readonly id: string;
readonly name: string;
readonly shortName: string;
readonly country: string;
readonly category: TrackCategory;
readonly difficulty: TrackDifficulty;
readonly lengthKm: number;
readonly turns: number;
readonly imageUrl?: string;
readonly gameId: string;
private constructor(props: {
id: string;
name: string;
shortName: string;
country: string;
category: TrackCategory;
difficulty: TrackDifficulty;
lengthKm: number;
turns: number;
imageUrl?: string;
gameId: string;
}) {
this.id = props.id;
this.name = props.name;
this.shortName = props.shortName;
this.country = props.country;
this.category = props.category;
this.difficulty = props.difficulty;
this.lengthKm = props.lengthKm;
this.turns = props.turns;
this.imageUrl = props.imageUrl;
this.gameId = props.gameId;
}
/**
* Factory method to create a new Track entity
*/
static create(props: {
id: string;
name: string;
shortName?: string;
country: string;
category?: TrackCategory;
difficulty?: TrackDifficulty;
lengthKm: number;
turns: number;
imageUrl?: string;
gameId: string;
}): Track {
this.validate(props);
return new Track({
id: props.id,
name: props.name,
shortName: props.shortName ?? props.name.slice(0, 3).toUpperCase(),
country: props.country,
category: props.category ?? 'road',
difficulty: props.difficulty ?? 'intermediate',
lengthKm: props.lengthKm,
turns: props.turns,
imageUrl: props.imageUrl,
gameId: props.gameId,
});
}
/**
* Domain validation logic
*/
private static validate(props: {
id: string;
name: string;
country: string;
lengthKm: number;
turns: number;
gameId: string;
}): void {
if (!props.id || props.id.trim().length === 0) {
throw new Error('Track ID is required');
}
if (!props.name || props.name.trim().length === 0) {
throw new Error('Track name is required');
}
if (!props.country || props.country.trim().length === 0) {
throw new Error('Track country is required');
}
if (props.lengthKm <= 0) {
throw new Error('Track length must be positive');
}
if (props.turns < 0) {
throw new Error('Track turns cannot be negative');
}
if (!props.gameId || props.gameId.trim().length === 0) {
throw new Error('Game ID is required');
}
}
/**
* Get formatted length string
*/
getFormattedLength(): string {
return `${this.lengthKm.toFixed(2)} km`;
}
}