1package wordsearch23import (4 "math/rand/v2"5 "testing"6)78func skipBenchmarkInShortMode(b *testing.B) {9 b.Helper()10 if testing.Short() {11 b.Skip("skipping benchmark in short mode")12 }13}1415type wordSearchBenchCase struct {16 name string17 width int18 height int19 wordCount int20 minWordLen int21 maxWordLen int22 allowedDirs []Direction23}2425func BenchmarkGenerateWordSearchByMode(b *testing.B) {26 skipBenchmarkInShortMode(b)2728 cases := []wordSearchBenchCase{29 {30 name: "easy_10x10",31 width: 10,32 height: 10,33 wordCount: 6,34 minWordLen: 3,35 maxWordLen: 5,36 allowedDirs: []Direction{Right, Down, DownRight},37 },38 {39 name: "medium_15x15",40 width: 15,41 height: 15,42 wordCount: 10,43 minWordLen: 4,44 maxWordLen: 7,45 allowedDirs: []Direction{Right, Down, DownRight, DownLeft, Left, Up},46 },47 {48 name: "hard_20x20",49 width: 20,50 height: 20,51 wordCount: 15,52 minWordLen: 5,53 maxWordLen: 10,54 allowedDirs: []Direction{Right, Down, DownRight, DownLeft, Left, Up, UpRight, UpLeft},55 },56 }5758 for _, tt := range cases {59 b.Run(tt.name, func(b *testing.B) {60 rng := rand.New(rand.NewPCG(1, 2))61 var aggregate placementStats62 b.ReportAllocs()6364 for range b.N {65 _, _, stats := generateWordSearchSeededWithStats(66 tt.width,67 tt.height,68 tt.wordCount,69 tt.minWordLen,70 tt.maxWordLen,71 tt.allowedDirs,72 rng,73 )74 aggregate.TargetWords += stats.TargetWords75 aggregate.PlacedWords += stats.PlacedWords76 aggregate.FailedWords += stats.FailedWords77 aggregate.RandomAttempts += stats.RandomAttempts78 aggregate.FallbackAttempts += stats.FallbackAttempts79 aggregate.FallbackUsed += stats.FallbackUsed80 aggregate.FallbackPlaced += stats.FallbackPlaced81 }8283 b.ReportMetric(aggregate.SuccessRate()*100, "place_pct")84 b.ReportMetric(aggregate.AttemptsPerWord(), "attempts/word")85 if aggregate.TargetWords > 0 {86 fallbackRate := float64(aggregate.FallbackUsed) * 100 / float64(aggregate.TargetWords)87 b.ReportMetric(fallbackRate, "fallback_pct")88 }89 })90 }91}9293func BenchmarkTryPlaceWord(b *testing.B) {94 skipBenchmarkInShortMode(b)9596 cases := []struct {97 name string98 width int99 height int100 text string101 allowedDirs []Direction102 }{103 {104 name: "easy_10x10",105 width: 10,106 height: 10,107 text: "HELLO",108 allowedDirs: []Direction{Right, Down, DownRight},109 },110 {111 name: "medium_15x15",112 width: 15,113 height: 15,114 text: "PUZZLE",115 allowedDirs: []Direction{Right, Down, DownRight, DownLeft, Left, Up},116 },117 {118 name: "hard_20x20",119 width: 20,120 height: 20,121 text: "WORDSEARCH",122 allowedDirs: []Direction{Right, Down, DownRight, DownLeft, Left, Up, UpRight, UpLeft},123 },124 }125126 for _, tt := range cases {127 b.Run(tt.name, func(b *testing.B) {128 rng := rand.New(rand.NewPCG(3, 4))129 g := createEmptyGrid(tt.height, tt.width)130 b.ReportAllocs()131132 for range b.N {133 w := tryPlaceWordSeeded(g, tt.text, tt.allowedDirs, 100, rng)134 if w == nil {135 b.Fatalf("tryPlaceWordSeeded returned nil for %q", tt.text)136 }137 clearWord(g, *w)138 }139 })140 }141}142143func clearWord(g grid, w Word) {144 dx, dy := w.Direction.Vector()145 for i := range len(w.Text) {146 x := w.Start.X + dx*i147 y := w.Start.Y + dy*i148 g.Set(x, y, ' ')149 }150}