Files
termdoku/internal/generator/benchmark.go

221 lines
5.4 KiB
Go

package generator
import (
"fmt"
"time"
)
// BenchmarkResult contains statistics from puzzle generation benchmarking.
type BenchmarkResult struct {
Difficulty Difficulty
TotalAttempts int
SuccessfulPuzzles int
FailedPuzzles int
AverageTime time.Duration
MinTime time.Duration
MaxTime time.Duration
AverageRating float64
Ratings []int
}
// BenchmarkGeneration tests puzzle generation performance for a given difficulty.
func BenchmarkGeneration(d Difficulty, attempts int) BenchmarkResult {
result := BenchmarkResult{
Difficulty: d,
TotalAttempts: attempts,
MinTime: time.Hour, // Start with a large value
Ratings: make([]int, 0, attempts),
}
var totalDuration time.Duration
for i := 0; i < attempts; i++ {
seed := fmt.Sprintf("benchmark-%d-%d", time.Now().UnixNano(), i)
start := time.Now()
puzzle, err := Generate(d, seed)
elapsed := time.Since(start)
if err != nil {
result.FailedPuzzles++
continue
}
result.SuccessfulPuzzles++
totalDuration += elapsed
if elapsed < result.MinTime {
result.MinTime = elapsed
}
if elapsed > result.MaxTime {
result.MaxTime = elapsed
}
rating := RatePuzzle(puzzle)
result.Ratings = append(result.Ratings, rating)
}
if result.SuccessfulPuzzles > 0 {
result.AverageTime = totalDuration / time.Duration(result.SuccessfulPuzzles)
var totalRating int
for _, r := range result.Ratings {
totalRating += r
}
result.AverageRating = float64(totalRating) / float64(len(result.Ratings))
}
return result
}
// String returns a formatted string representation of the benchmark result.
func (br BenchmarkResult) String() string {
successRate := float64(br.SuccessfulPuzzles) / float64(br.TotalAttempts) * 100
return fmt.Sprintf(`Benchmark Results for %s:
Total Attempts: %d
Successful: %d (%.1f%%)
Failed: %d
Average Time: %v
Min Time: %v
Max Time: %v
Average Rating: %.1f/100
`,
br.Difficulty.String(),
br.TotalAttempts,
br.SuccessfulPuzzles,
successRate,
br.FailedPuzzles,
br.AverageTime,
br.MinTime,
br.MaxTime,
br.AverageRating,
)
}
// CompareGenerationMethods compares standard vs symmetric generation.
func CompareGenerationMethods(d Difficulty, attempts int) (standard, symmetric BenchmarkResult) {
// Benchmark standard generation
standard = BenchmarkGeneration(d, attempts)
// Benchmark symmetric generation
symmetric = BenchmarkResult{
Difficulty: d,
TotalAttempts: attempts,
MinTime: time.Hour,
Ratings: make([]int, 0, attempts),
}
var totalDuration time.Duration
for i := 0; i < attempts; i++ {
seed := fmt.Sprintf("symmetric-benchmark-%d-%d", time.Now().UnixNano(), i)
start := time.Now()
puzzle, err := GenerateWithSymmetry(d, seed, SymmetryRotational180)
elapsed := time.Since(start)
if err != nil {
symmetric.FailedPuzzles++
continue
}
symmetric.SuccessfulPuzzles++
totalDuration += elapsed
if elapsed < symmetric.MinTime {
symmetric.MinTime = elapsed
}
if elapsed > symmetric.MaxTime {
symmetric.MaxTime = elapsed
}
rating := RatePuzzle(puzzle)
symmetric.Ratings = append(symmetric.Ratings, rating)
}
if symmetric.SuccessfulPuzzles > 0 {
symmetric.AverageTime = totalDuration / time.Duration(symmetric.SuccessfulPuzzles)
var totalRating int
for _, r := range symmetric.Ratings {
totalRating += r
}
symmetric.AverageRating = float64(totalRating) / float64(len(symmetric.Ratings))
}
return standard, symmetric
}
// PuzzleStatistics provides detailed statistics about a generated puzzle.
type PuzzleStatistics struct {
Grid Grid
Analysis PuzzleAnalysis
Rating int
EstimatedSolveTime time.Duration
ComplexityScore float64
}
// GetPuzzleStatistics performs comprehensive analysis on a puzzle.
func GetPuzzleStatistics(g Grid) PuzzleStatistics {
analysis := g.Analyze()
rating := RatePuzzle(g)
// Estimate solve time based on difficulty (rough approximation)
var estimatedTime time.Duration
switch analysis.EstimatedDifficulty {
case Easy:
estimatedTime = 3 * time.Minute
case Normal:
estimatedTime = 8 * time.Minute
case Hard:
estimatedTime = 15 * time.Minute
case Expert:
estimatedTime = 25 * time.Minute
case Lunatic:
estimatedTime = 45 * time.Minute
}
// Calculate complexity score (0-1)
complexityScore := float64(rating) / 100.0
return PuzzleStatistics{
Grid: g,
Analysis: analysis,
Rating: rating,
EstimatedSolveTime: estimatedTime,
ComplexityScore: complexityScore,
}
}
// String returns a formatted string of puzzle statistics.
func (ps PuzzleStatistics) String() string {
return fmt.Sprintf(`Puzzle Statistics:
Difficulty: %s
Rating: %d/100
Filled Cells: %d
Empty Cells: %d
Min Candidates: %d
Max Candidates: %d
Avg Candidates: %.2f
Unique Solution: %v
Symmetry: %s
Techniques: %v
Complexity Score: %.2f
Est. Solve Time: %v
`,
ps.Analysis.EstimatedDifficulty.String(),
ps.Rating,
ps.Analysis.FilledCells,
ps.Analysis.EmptyCells,
ps.Analysis.MinCandidates,
ps.Analysis.MaxCandidates,
ps.Analysis.AvgCandidates,
ps.Analysis.HasUniqueSolution,
ps.Analysis.SymmetryType.String(),
ps.Analysis.SolvingTechniques,
ps.ComplexityScore,
ps.EstimatedSolveTime,
)
}