1package app23import (4 "strings"56 "github.com/FelineStateMachine/puzzletea/puzzle"7 "github.com/FelineStateMachine/puzzletea/registry"8 "github.com/FelineStateMachine/puzzletea/theme"9 "github.com/FelineStateMachine/puzzletea/ui"1011 "charm.land/bubbles/v2/textinput"12 tea "charm.land/bubbletea/v2"13 "charm.land/lipgloss/v2"14)1516const randomSeedModeLabel = "Random"1718func buildSeedModeOptions(definitions []puzzle.Definition) []seedModeOption {19 options := []seedModeOption{{label: randomSeedModeLabel}}2021 for _, def := range definitions {22 if !definitionHasSeededModes(def) {23 continue24 }2526 for _, mode := range def.Modes {27 if !mode.Seeded {28 continue29 }3031 options = append(options, seedModeOption{32 key: seedModeKey(def.Name, ""),33 label: def.Name,34 gameType: def.Name,35 })36 break37 }38 }3940 return options41}4243func seedModeKey(gameType, modeTitle string) string {44 if modeTitle == "" {45 return puzzle.NormalizeName(gameType)46 }47 return puzzle.NormalizeName(gameType) + "::" + puzzle.NormalizeName(modeTitle)48}4950func definitionHasSeededModes(def puzzle.Definition) bool {51 for _, mode := range def.Modes {52 if mode.Seeded {53 return true54 }55 }56 return false57}5859func findSeedModeIndex(options []seedModeOption, key string) int {60 if key == "" {61 return 062 }63 for i := range options {64 if options[i].key == key {65 return i66 }67 }68 return 069}7071func (m model) enterSeedInputView() (model, tea.Cmd) {72 ti := textinput.New()73 ti.Placeholder = "any word or phrase"74 ti.CharLimit = 6475 ti.SetWidth(min(m.width, 48))7677 options := buildSeedModeOptions(registry.Definitions())78 index := findSeedModeIndex(options, m.seed.lastModeKey)7980 m.seed.input = ti81 m.seed.modeOptions = options82 m.seed.modeIndex = index83 m.seed.focus = seedFocusText84 m.state = seedInputView85 m = m.initScreen(seedInputView)86 return m, m.seed.input.Focus()87}8889func (m model) currentSeedMode() seedModeOption {90 return currentSeedMode(m.seed)91}9293func currentSeedMode(seed seedState) seedModeOption {94 if len(seed.modeOptions) == 0 {95 return seedModeOption{label: randomSeedModeLabel}96 }97 if seed.modeIndex < 0 || seed.modeIndex >= len(seed.modeOptions) {98 return seed.modeOptions[0]99 }100 return seed.modeOptions[seed.modeIndex]101}102103func (m model) moveSeedMode(step int) model {104 if len(m.seed.modeOptions) == 0 {105 return m106 }107108 index := m.seed.modeIndex + step109 for index < 0 {110 index += len(m.seed.modeOptions)111 }112 m.seed.modeIndex = index % len(m.seed.modeOptions)113 m.seed.lastModeKey = m.currentSeedMode().key114 return m115}116117func (m model) setSeedFocus(focus seedInputFocus) (model, tea.Cmd) {118 m.seed.focus = focus119 if focus == seedFocusText {120 return m, m.seed.input.Focus()121 }122 m.seed.input.Blur()123 return m, nil124}125126func (m model) handleSeedInputUpdate(msg tea.Msg) (model, tea.Cmd) {127 keyMsg, ok := msg.(tea.KeyPressMsg)128 if ok {129 switch keyMsg.String() {130 case "up", "k":131 return m.setSeedFocus(seedFocusText)132 case "down", "j":133 return m.setSeedFocus(seedFocusMode)134 case "left", "h":135 if m.seed.focus == seedFocusMode {136 return m.moveSeedMode(-1), nil137 }138 case "right", "l":139 if m.seed.focus == seedFocusMode {140 return m.moveSeedMode(1), nil141 }142 }143 }144145 if m.seed.focus != seedFocusText {146 return m, nil147 }148149 var cmd tea.Cmd150 m.seed.input, cmd = m.seed.input.Update(msg)151 return m, cmd152}153154func seedInputBody(seed seedState) string {155 selector := renderSeedModeTitle(currentSeedMode(seed).label, seed.modeIndex)156 modePrefix := " "157 if seed.focus == seedFocusMode {158 modePrefix = ui.CursorStyle().Render("> ")159 }160161 inputView := seed.input.View()162 if seed.focus != seedFocusText {163 inputView = strings.Replace(inputView, ">", " ", 1)164 }165166 return lipgloss.JoinVertical(167 lipgloss.Left,168 inputView,169 "",170 modePrefix+selector,171 )172}173174func renderSeedModeTitle(label string, colorIndex int) string {175 colors := theme.Current().ThemeColors()176 if len(colors) == 0 {177 return ui.PanelTitle().Render("game: " + label)178 }179180 bg := colors[((colorIndex%len(colors))+len(colors))%len(colors)]181 chip := lipgloss.NewStyle().182 Bold(true).183 Foreground(theme.TextOnBG(bg)).184 Background(bg).185 Render(label)186187 var b strings.Builder188 b.WriteString(ui.PanelTitle().Render("game: "))189 b.WriteString(chip)190 return b.String()191}