clone

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

chore/app-model-decomp
puzzletea / games / rippleeffect / gamemode.go
gamemode.go 4.2K
1package rippleeffect23import (4 _ "embed"5 "math/rand/v2"67 "github.com/FelineStateMachine/puzzletea/difficulty"8 "github.com/FelineStateMachine/puzzletea/game"9 "github.com/FelineStateMachine/puzzletea/gameentry"10 "github.com/FelineStateMachine/puzzletea/puzzle"11)1213//go:embed help.md14var HelpContent string1516type Mode struct {17 game.BaseMode18 Size int19 MaxCage int20 GivenRatio float6421 profile generationProfile22}2324var (25 _ game.Mode = Mode{}26 _ game.Spawner = Mode{}27 _ game.SeededSpawner = Mode{}28 _ game.EloSpawner = Mode{}29)3031func NewMode(title, description string, size, maxCage int, givenRatio float64) Mode {32 return NewModeWithProfile(title, description, size, maxCage, givenRatio, defaultGenerationProfile(maxCage))33}3435func NewModeWithProfile(title, description string, size, maxCage int, givenRatio float64, profile generationProfile) Mode {36 return Mode{37 BaseMode: game.NewBaseMode(title, description),38 Size: size,39 MaxCage: maxCage,40 GivenRatio: givenRatio,41 profile: profile,42 }43}4445func (m Mode) Spawn() (game.Gamer, error) {46 puzzle, err := m.generatePuzzle()47 if err != nil {48 return nil, err49 }50 return New(m, puzzle)51}5253func (m Mode) SpawnSeeded(rng *rand.Rand) (game.Gamer, error) {54 puzzle, err := m.generatePuzzleSeeded(rng)55 if err != nil {56 return nil, err57 }58 return New(m, puzzle)59}6061var Modes = []game.Mode{62 NewModeWithProfile(63 "Mini 5x5",64 "Compact cages with extra anchors for quick local reads.",65 5,66 3,67 0.69,68 generationProfile{69 cageWeights: []int{0, 5, 6, 3},70 frontierSamples: 3,71 shapeBias: shapeBiasCompact,72 minGivensByCage: []int{0, 1, 1, 2},73 maxSingletonCages: -1,74 },75 ),76 NewModeWithProfile(77 "Easy 6x6",78 "Gentle spacing logic with compact cages and steady clues.",79 6,80 3,81 0.64,82 generationProfile{83 cageWeights: []int{0, 3, 5, 4},84 frontierSamples: 3,85 shapeBias: shapeBiasCompact,86 minGivensByCage: []int{0, 1, 1, 2},87 maxSingletonCages: -1,88 },89 ),90 NewModeWithProfile(91 "Medium 7x7",92 "Mixed cage shapes with sparser anchors and roomier deductions.",93 7,94 4,95 0.50,96 generationProfile{97 cageWeights: []int{0, 0, 3, 5, 4},98 frontierSamples: 2,99 shapeBias: shapeBiasNeutral,100 minGivensByCage: []int{0, 0, 0, 0, 0},101 maxSingletonCages: 1,102 },103 ),104 NewModeWithProfile(105 "Hard 8x8",106 "Longer cages and sparse anchors reward broader ripple reads.",107 8,108 4,109 0.46,110 generationProfile{111 cageWeights: []int{0, 0, 1, 4, 5},112 frontierSamples: 3,113 shapeBias: shapeBiasWinding,114 minGivensByCage: []int{0, 0, 0, 0, 0},115 maxSingletonCages: 1,116 },117 ),118 NewModeWithProfile(119 "Expert 9x9",120 "Winding large cages and sparse anchors push global deductions.",121 9,122 5,123 0.42,124 generationProfile{125 cageWeights: []int{0, 0, 1, 2, 4, 5},126 frontierSamples: 4,127 shapeBias: shapeBiasWinding,128 minGivensByCage: []int{0, 0, 0, 0, 0, 0},129 maxSingletonCages: 1,130 },131 ),132}133134var ModeDefinitions = rippleEffectModeDefinitions(Modes)135136func rippleEffectModeDefinitions(modes []game.Mode) []puzzle.ModeDef {137 defs := gameentry.BuildModeDefs(modes)138 presets := []difficulty.Elo{400, 900, 1500, 2200, 2800}139 for i, mode := range modes {140 if i >= len(defs) || i >= len(presets) {141 break142 }143 if _, ok := mode.(game.EloSpawner); !ok {144 continue145 }146 elo := presets[i]147 defs[i].PresetElo = &elo148 }149 return defs150}151152var Definition = puzzle.NewDefinition(puzzle.DefinitionSpec{153 Name: "Ripple Effect",154 Description: "Fill the cages with sequential numbers without violating ripple distance.",155 Aliases: []string{"ripple"},156 Modes: ModeDefinitions,157 DailyModeIDs: puzzle.SelectModeIDsByIndex(ModeDefinitions, 1, 2, 3),158})159160var Entry = gameentry.NewEntry(gameentry.EntrySpec{161 Definition: Definition,162 Help: HelpContent,163 Import: game.AdaptImport(ImportModel),164 Modes: Modes,165 Print: PDFPrintAdapter,166})167168func (m Mode) generatePuzzle() (Puzzle, error) {169 rng := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))170 return m.generatePuzzleSeeded(rng)171}172173func (m Mode) generatePuzzleSeeded(rng *rand.Rand) (Puzzle, error) {174 return generatePuzzleSeededWithProfile(m.Size, m.Size, m.MaxCage, m.GivenRatio, m.profile.withDefaults(m.MaxCage), rng)175}