TournamentManagementService
Complete tournament lifecycle management service.
Overview
File: domain/services/TournamentManagementService.kt
Lines: ~850 lines
Purpose: Comprehensive tournament management from creation to completion
TournamentManagementService orchestrates all tournament operations including creation, participant management, scoring, and completion workflows.
API Reference
Tournament Lifecycle
class TournamentManagementService(
private val tournamentRepository: HybridTournamentRepository,
private val roundRepository: RoundRepository,
private val syncCoordinator: SyncCoordinator,
private val notificationService: NotificationService
) {
// Create tournament
suspend fun createTournament(
name: String,
location: String,
startDate: Long,
endDate: Long,
format: TournamentFormat,
organizer: String
): Result<Tournament>
// Update tournament
suspend fun updateTournament(tournament: Tournament): Result<Unit>
// Delete tournament
suspend fun deleteTournament(tournamentId: String): Result<Unit>
// Get tournament
suspend fun getTournament(tournamentId: String): Result<Tournament>
// List tournaments
suspend fun getAllTournaments(): Result<List<Tournament>>
suspend fun getUpcomingTournaments(): Result<List<Tournament>>
suspend fun getPastTournaments(): Result<List<Tournament>>
suspend fun getActiveTournaments(): Result<List<Tournament>>
// Tournament status
suspend fun startTournament(tournamentId: String): Result<Unit>
suspend fun completeTournament(tournamentId: String): Result<Unit>
suspend fun cancelTournament(tournamentId: String): Result<Unit>
}Participant Management
// Add participants
suspend fun addParticipant(
tournamentId: String,
participant: TournamentParticipant
): Result<Unit>
suspend fun addParticipants(
tournamentId: String,
participants: List<TournamentParticipant>
): Result<BulkAddResult>
// Remove participants
suspend fun removeParticipant(
tournamentId: String,
participantId: String
): Result<Unit>
// Get participants
suspend fun getParticipants(
tournamentId: String
): Result<List<TournamentParticipant>>
suspend fun getParticipant(
tournamentId: String,
participantId: String
): Result<TournamentParticipant>
// Update participant
suspend fun updateParticipant(
participant: TournamentParticipant
): Result<Unit>
data class BulkAddResult(
val successCount: Int,
val failedCount: Int,
val duplicates: List<String>,
val errors: List<String>
)Scoring Management
// Submit score
suspend fun submitScore(
tournamentId: String,
participantId: String,
score: TournamentScore
): Result<Unit>
// Update score
suspend fun updateScore(
scoreId: String,
newScore: TournamentScore
): Result<Unit>
// Get scores
suspend fun getScores(
tournamentId: String
): Result<List<TournamentScore>>
suspend fun getScoreForParticipant(
tournamentId: String,
participantId: String
): Result<TournamentScore?>
// Delete score
suspend fun deleteScore(scoreId: String): Result<Unit>
// Leaderboard
suspend fun getLeaderboard(
tournamentId: String,
division: String? = null
): Result<List<LeaderboardEntry>>
data class LeaderboardEntry(
val rank: Int,
val participant: TournamentParticipant,
val score: Int,
val xCount: Int,
val division: String
)Tournament Formats
enum class TournamentFormat {
WA_1440, // World Archery 1440 round
WA_720, // World Archery 720 round (70m)
INDOOR_18M, // Indoor 18m
INDOOR_25M, // Indoor 25m
FITA_OUTDOOR, // Outdoor FITA
IMPERIAL, // Imperial rounds
FIELD, // Field archery
CLOUT, // Clout shooting
CUSTOM // Custom format
}
data class TournamentFormatDetails(
val format: TournamentFormat,
val distances: List<Int>, // meters
val arrowsPerEnd: Int,
val endsPerDistance: Int,
val maxScore: Int
)
fun getTournamentFormat(format: TournamentFormat): TournamentFormatDetails {
return when (format) {
TournamentFormat.WA_720 -> TournamentFormatDetails(
format = WA_720,
distances = listOf(70),
arrowsPerEnd = 6,
endsPerDistance = 12,
maxScore = 720
)
// ... other formats
}
}Divisions and Categories
enum class Division {
RECURVE_MEN,
RECURVE_WOMEN,
COMPOUND_MEN,
COMPOUND_WOMEN,
BAREBOW_MEN,
BAREBOW_WOMEN,
LONGBOW_MEN,
LONGBOW_WOMEN
}
enum class AgeCategory {
UNDER_15,
UNDER_18,
UNDER_21,
SENIOR,
MASTER_50_PLUS,
MASTER_60_PLUS
}
data class TournamentParticipant(
val id: String,
val name: String,
val division: Division,
val ageCategory: AgeCategory,
val club: String? = null,
val email: String? = null,
val targetNumber: String? = null
)Live Scoring
// Enable live scoring
suspend fun enableLiveScoring(
tournamentId: String,
enabled: Boolean
): Result<Unit>
// Real-time score updates
fun observeScores(
tournamentId: String
): Flow<List<TournamentScore>>
fun observeLeaderboard(
tournamentId: String,
division: String? = null
): Flow<List<LeaderboardEntry>>
// Scoring validation
suspend fun validateScore(
score: TournamentScore,
format: TournamentFormat
): Result<ValidationResult>Usage Examples
Create and Setup Tournament
// Create tournament
val tournament = tournamentService.createTournament(
name = "Spring Championship 2025",
location = "National Archery Center",
startDate = parseDate("2025-05-15"),
endDate = parseDate("2025-05-17"),
format = TournamentFormat.WA_720,
organizer = "State Archery Association"
).getOrThrow()
// Add participants
val participants = listOf(
TournamentParticipant(
id = "p1",
name = "Alice Johnson",
division = Division.RECURVE_WOMEN,
ageCategory = AgeCategory.SENIOR,
club = "City Archers",
targetNumber = "1A"
),
TournamentParticipant(
id = "p2",
name = "Bob Smith",
division = Division.RECURVE_MEN,
ageCategory = AgeCategory.SENIOR,
club = "Valley Bowmen",
targetNumber = "1B"
)
)
val result = tournamentService.addParticipants(tournament.id, participants)
.getOrThrow()
println("Added ${result.successCount} participants")
// Enable live scoring
tournamentService.enableLiveScoring(tournament.id, enabled = true)
// Start tournament
tournamentService.startTournament(tournament.id)Submit and Track Scores
// Submit participant score
val score = TournamentScore(
id = UUID.randomUUID().toString(),
tournamentId = tournamentId,
participantId = "p1",
endScores = listOf(
EndScore(1, listOf(10, 10, 9, 9, 8, 8)), // End 1
EndScore(2, listOf(10, 9, 9, 8, 8, 7)), // End 2
// ... more ends
),
total = 654,
xCount = 45,
timestamp = System.currentTimeMillis()
)
tournamentService.submitScore(tournamentId, "p1", score)
.onSuccess {
println("Score submitted successfully")
}Live Leaderboard
@Composable
fun LiveLeaderboardScreen(
tournamentId: String,
service: TournamentManagementService
) {
val leaderboard by service.observeLeaderboard(tournamentId)
.collectAsState(initial = emptyList())
LazyColumn {
item {
Text(
"Live Leaderboard",
style = MaterialTheme.typography.headlineMedium
)
}
items(leaderboard) { entry ->
LeaderboardRow(
rank = entry.rank,
name = entry.participant.name,
score = entry.score,
xCount = entry.xCount,
division = entry.division
)
}
}
// Auto-refresh every 30 seconds
LaunchedEffect(tournamentId) {
while (true) {
delay(30000)
service.getLeaderboard(tournamentId)
}
}
}Complete Tournament
// End of tournament
tournamentService.completeTournament(tournamentId)
.onSuccess {
// Generate final results
val leaderboard = tournamentService.getLeaderboard(tournamentId)
.getOrThrow()
// Notify participants
leaderboard.take(3).forEach { entry ->
notificationService.sendNotification(
type = NotificationType.ACHIEVEMENT_UNLOCKED,
title = "Tournament Complete",
message = "${entry.participant.name} finished ${entry.rank}${getRankSuffix(entry.rank)}!"
)
}
// Export results
exportService.exportTournamentResults(tournamentId)
}Advanced Features
Multi-Day Tournaments
suspend fun createMultiDayTournament(
name: String,
location: String,
days: List<TournamentDay>
): Result<Tournament> {
val tournament = createTournament(
name = name,
location = location,
startDate = days.first().date,
endDate = days.last().date,
format = TournamentFormat.CUSTOM,
organizer = "Multi-Day Organizer"
).getOrThrow()
// Configure each day
days.forEach { day ->
configureTournamentDay(
tournamentId = tournament.id,
dayNumber = day.dayNumber,
format = day.format,
distances = day.distances
)
}
return Result.success(tournament)
}
data class TournamentDay(
val dayNumber: Int,
val date: Long,
val format: TournamentFormat,
val distances: List<Int>
)Team Competitions
data class Team(
val id: String,
val name: String,
val members: List<String>, // Participant IDs
val club: String
)
suspend fun addTeam(
tournamentId: String,
team: Team
): Result<Unit>
suspend fun getTeamStandings(
tournamentId: String
): Result<List<TeamStanding>>
data class TeamStanding(
val rank: Int,
val team: Team,
val totalScore: Int,
val averageScore: Double
)Elimination Rounds
enum class EliminationType {
SINGLE_ELIMINATION,
DOUBLE_ELIMINATION,
ROUND_ROBIN
}
suspend fun createEliminationRound(
tournamentId: String,
type: EliminationType,
qualifiers: List<String> // Participant IDs
): Result<EliminationBracket>
data class EliminationBracket(
val type: EliminationType,
val matches: List<Match>,
val currentRound: Int,
val totalRounds: Int
)
data class Match(
val id: String,
val roundNumber: Int,
val participant1: TournamentParticipant,
val participant2: TournamentParticipant,
val score1: Int?,
val score2: Int?,
val winner: String? // Participant ID
)Integration with Other Services
Sync Integration
class TournamentManagementService(
private val syncCoordinator: SyncCoordinator,
// ... other dependencies
) {
suspend fun submitScore(
tournamentId: String,
participantId: String,
score: TournamentScore
): Result<Unit> {
// Save locally
val result = tournamentRepository.submitScore(score)
// Sync to cloud
if (result.isSuccess) {
syncCoordinator.syncEntityType(EntityType.TOURNAMENT_SCORE)
}
return result
}
}Statistics Integration
suspend fun getTournamentStatistics(
tournamentId: String
): Result<TournamentStatistics> {
val scores = getScores(tournamentId).getOrThrow()
return Result.success(TournamentStatistics(
totalParticipants = scores.size,
averageScore = scores.map { it.total }.average(),
highScore = scores.maxOf { it.total },
lowScore = scores.minOf { it.total },
totalXs = scores.sumOf { it.xCount },
completionRate = calculateCompletionRate(scores)
))
}
data class TournamentStatistics(
val totalParticipants: Int,
val averageScore: Double,
val highScore: Int,
val lowScore: Int,
val totalXs: Int,
val completionRate: Double
)Best Practices
1. Validation
// GOOD: Validate before submission
val validation = tournamentService.validateScore(score, format)
if (validation.isValid) {
tournamentService.submitScore(tournamentId, participantId, score)
} else {
showErrors(validation.errors)
}
// BAD: Submit without validation
tournamentService.submitScore(tournamentId, participantId, score)2. Error Handling
// GOOD: Handle all failure cases
tournamentService.submitScore(tournamentId, participantId, score)
.onSuccess {
showMessage("Score submitted")
}
.onFailure { error ->
when (error) {
is NetworkException -> showMessage("Will sync when online")
is ValidationException -> showErrors(error.errors)
else -> showError("Failed to submit")
}
}3. Real-time Updates
// GOOD: Use reactive streams for live data
@Composable
fun TournamentScreen(tournamentId: String) {
val scores by service.observeScores(tournamentId)
.collectAsState(initial = emptyList())
// UI updates automatically
}
// BAD: Polling
LaunchedEffect(Unit) {
while (true) {
service.getScores(tournamentId) // Inefficient
delay(5000)
}
}Testing
Example Tests
class TournamentManagementServiceTest {
@Test
fun `create tournament with valid data succeeds`() = runTest {
val result = service.createTournament(
name = "Test Tournament",
location = "Test Location",
startDate = System.currentTimeMillis(),
endDate = System.currentTimeMillis() + 86400000,
format = TournamentFormat.WA_720,
organizer = "Test Organizer"
)
assertTrue(result.isSuccess)
val tournament = result.getOrThrow()
assertEquals("Test Tournament", tournament.name)
}
@Test
fun `submit duplicate score fails`() = runTest {
// First submission
service.submitScore(tournamentId, participantId, score1)
// Duplicate submission
val result = service.submitScore(tournamentId, participantId, score2)
assertTrue(result.isFailure)
}
@Test
fun `leaderboard sorts by score descending`() = runTest {
// Submit various scores
service.submitScore(tournamentId, "p1", score(650))
service.submitScore(tournamentId, "p2", score(680))
service.submitScore(tournamentId, "p3", score(670))
val leaderboard = service.getLeaderboard(tournamentId).getOrThrow()
assertEquals(1, leaderboard[0].rank)
assertEquals(680, leaderboard[0].score)
assertEquals(2, leaderboard[1].rank)
assertEquals(670, leaderboard[1].score)
}
}Related Documentation
Status: ✅ In production with live tournament support Complexity: High (~850 lines, multi-domain orchestration) Key Features: Real-time leaderboard, offline support, multiple formats Last Updated: 2025-11-01