clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

main
puzzletea / export / pdf / order.go
order.go 2.3K
1package pdfexport23import (4 "hash/fnv"5 "math/rand/v2"6 "sort"7 "strings"8 "time"910 "github.com/FelineStateMachine/puzzletea/puzzle"11)1213func OrderPuzzlesForPrint(puzzles []Puzzle, seed string) []Puzzle {14 ordered := append([]Puzzle(nil), puzzles...)15 if len(ordered) <= 1 {16 return ordered17 }1819 sort.SliceStable(ordered, func(i, j int) bool {20 if ordered[i].DifficultyScore != ordered[j].DifficultyScore {21 return ordered[i].DifficultyScore < ordered[j].DifficultyScore22 }23 if c := strings.Compare(puzzle.NormalizeName(ordered[i].Category), puzzle.NormalizeName(ordered[j].Category)); c != 0 {24 return c < 025 }26 if c := strings.Compare(puzzle.NormalizeName(ordered[i].ModeSelection), puzzle.NormalizeName(ordered[j].ModeSelection)); c != 0 {27 return c < 028 }29 if c := strings.Compare(ordered[i].SourceFileName, ordered[j].SourceFileName); c != 0 {30 return c < 031 }32 return ordered[i].Index < ordered[j].Index33 })3435 rng := seededRand(seed)3637 const bandSize = 638 for start := 0; start < len(ordered); start += bandSize {39 end := min(start+bandSize, len(ordered))40 shuffleBand(ordered[start:end], rng)41 }4243 // Reduce same-category runs across band edges while preserving44 // the overall difficulty trajectory.45 for i := 1; i < len(ordered); i++ {46 if !sameCategory(ordered[i-1], ordered[i]) {47 continue48 }49 for j := i + 1; j < min(i+4, len(ordered)); j++ {50 if sameCategory(ordered[i-1], ordered[j]) {51 continue52 }53 ordered[i], ordered[j] = ordered[j], ordered[i]54 break55 }56 }5758 return ordered59}6061func shuffleBand(puzzles []Puzzle, rng *rand.Rand) {62 if len(puzzles) <= 1 {63 return64 }6566 perm := rng.Perm(len(puzzles))67 shuffled := make([]Puzzle, len(puzzles))68 for i, idx := range perm {69 shuffled[i] = puzzles[idx]70 }71 copy(puzzles, shuffled)7273 for i := 1; i < len(puzzles); i++ {74 if !sameCategory(puzzles[i-1], puzzles[i]) {75 continue76 }77 for j := i + 1; j < len(puzzles); j++ {78 if sameCategory(puzzles[i-1], puzzles[j]) {79 continue80 }81 puzzles[i], puzzles[j] = puzzles[j], puzzles[i]82 break83 }84 }85}8687func seededRand(seed string) *rand.Rand {88 if strings.TrimSpace(seed) == "" {89 seed = time.Now().Format(time.RFC3339Nano)90 }91 h := fnv.New64a()92 h.Write([]byte(seed))93 s := h.Sum64()94 return rand.New(rand.NewPCG(s, ^s))95}9697func sameCategory(a, b Puzzle) bool {98 return puzzle.NormalizeName(a.Category) == puzzle.NormalizeName(b.Category)99}