221 lines
5.4 KiB
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,
|
|
)
|
|
}
|