1package rippleeffect23import (4 _ "embed"5 "math/rand/v2"67 "github.com/FelineStateMachine/puzzletea/game"8 "github.com/FelineStateMachine/puzzletea/gameentry"9 "github.com/FelineStateMachine/puzzletea/puzzle"10)1112//go:embed help.md13var HelpContent string1415type Mode struct {16 game.BaseMode17 Size int18 MaxCage int19 GivenRatio float6420 profile generationProfile21}2223var (24 _ game.Mode = Mode{}25 _ game.Spawner = Mode{}26 _ game.SeededSpawner = Mode{}27)2829func NewMode(title, description string, size, maxCage int, givenRatio float64) Mode {30 return NewModeWithProfile(title, description, size, maxCage, givenRatio, defaultGenerationProfile(maxCage))31}3233func NewModeWithProfile(title, description string, size, maxCage int, givenRatio float64, profile generationProfile) Mode {34 return Mode{35 BaseMode: game.NewBaseMode(title, description),36 Size: size,37 MaxCage: maxCage,38 GivenRatio: givenRatio,39 profile: profile,40 }41}4243func (m Mode) Spawn() (game.Gamer, error) {44 puzzle, err := m.generatePuzzle()45 if err != nil {46 return nil, err47 }48 return New(m, puzzle)49}5051func (m Mode) SpawnSeeded(rng *rand.Rand) (game.Gamer, error) {52 puzzle, err := m.generatePuzzleSeeded(rng)53 if err != nil {54 return nil, err55 }56 return New(m, puzzle)57}5859var Modes = []game.Mode{60 NewModeWithProfile(61 "Mini 5x5",62 "Compact cages with extra anchors for quick local reads.",63 5,64 3,65 0.69,66 generationProfile{67 cageWeights: []int{0, 5, 6, 3},68 frontierSamples: 3,69 shapeBias: shapeBiasCompact,70 minGivensByCage: []int{0, 1, 1, 2},71 },72 ),73 NewModeWithProfile(74 "Easy 6x6",75 "Gentle spacing logic with compact cages and steady clues.",76 6,77 3,78 0.64,79 generationProfile{80 cageWeights: []int{0, 3, 5, 4},81 frontierSamples: 3,82 shapeBias: shapeBiasCompact,83 minGivensByCage: []int{0, 1, 1, 2},84 },85 ),86 NewModeWithProfile(87 "Medium 7x7",88 "Mixed cage shapes with a balanced clue spread.",89 7,90 4,91 0.59,92 generationProfile{93 cageWeights: []int{0, 2, 4, 5, 3},94 frontierSamples: 2,95 shapeBias: shapeBiasNeutral,96 minGivensByCage: []int{0, 1, 1, 1, 1},97 },98 ),99 NewModeWithProfile(100 "Hard 8x8",101 "Longer cages and lighter anchors create broader ripple scans.",102 8,103 4,104 0.55,105 generationProfile{106 cageWeights: []int{0, 1, 2, 4, 5},107 frontierSamples: 3,108 shapeBias: shapeBiasWinding,109 minGivensByCage: []int{0, 1, 1, 1, 1},110 },111 ),112 NewModeWithProfile(113 "Expert 9x9",114 "Sparser anchors and winding large cages push global deductions.",115 9,116 5,117 0.51,118 generationProfile{119 cageWeights: []int{0, 1, 1, 3, 4, 5},120 frontierSamples: 4,121 shapeBias: shapeBiasWinding,122 minGivensByCage: []int{0, 1, 1, 1, 1, 1},123 },124 ),125}126127var ModeDefinitions = gameentry.BuildModeDefs(Modes)128129var Definition = puzzle.NewDefinition(puzzle.DefinitionSpec{130 Name: "Ripple Effect",131 Description: "Fill the cages with sequential numbers without violating ripple distance.",132 Aliases: []string{"ripple"},133 Modes: ModeDefinitions,134 DailyModeIDs: puzzle.SelectModeIDsByIndex(ModeDefinitions, 1, 2, 3),135})136137var Entry = gameentry.NewEntry(gameentry.EntrySpec{138 Definition: Definition,139 Help: HelpContent,140 Import: game.AdaptImport(ImportModel),141 Modes: Modes,142 Print: PDFPrintAdapter,143})144145func (m Mode) generatePuzzle() (Puzzle, error) {146 rng := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))147 return m.generatePuzzleSeeded(rng)148}149150func (m Mode) generatePuzzleSeeded(rng *rand.Rand) (Puzzle, error) {151 return generatePuzzleSeededWithProfile(m.Size, m.Size, m.MaxCage, m.GivenRatio, m.profile.withDefaults(m.MaxCage), rng)152}