1package puzzle23import "strings"45type GameID string67type ModeID string89type ModeDef struct {10 ID ModeID11 Title string12 Description string13 Seeded bool14}1516type ModeSpec struct {17 ID ModeID18 Title string19 Description string20 Seeded bool21}2223type Definition struct {24 ID GameID25 Name string26 Description string27 Aliases []string28 Modes []ModeDef29 DailyModeIDs []ModeID30}3132type DefinitionSpec struct {33 ID GameID34 Name string35 Description string36 Aliases []string37 Modes []ModeDef38 DailyModeIDs []ModeID39}4041func NewModeDef(spec ModeSpec) ModeDef {42 id := spec.ID43 if id == "" {44 id = CanonicalModeID(spec.Title)45 }46 return ModeDef{47 ID: id,48 Title: spec.Title,49 Description: spec.Description,50 Seeded: spec.Seeded,51 }52}5354func NewDefinition(spec DefinitionSpec) Definition {55 id := spec.ID56 if id == "" {57 id = CanonicalGameID(spec.Name)58 }59 return Definition{60 ID: id,61 Name: spec.Name,62 Description: spec.Description,63 Aliases: append([]string(nil), spec.Aliases...),64 Modes: append([]ModeDef(nil), spec.Modes...),65 DailyModeIDs: append([]ModeID(nil), spec.DailyModeIDs...),66 }67}6869func SelectModeIDsByIndex(modes []ModeDef, indexes ...int) []ModeID {70 selected := make([]ModeID, 0, len(indexes))71 for _, idx := range indexes {72 if idx < 0 || idx >= len(modes) {73 continue74 }75 selected = append(selected, modes[idx].ID)76 }77 return selected78}7980func NormalizeName(s string) string {81 s = strings.ToLower(strings.TrimSpace(s))82 s = strings.ReplaceAll(s, "-", " ")83 s = strings.ReplaceAll(s, "_", " ")84 return strings.Join(strings.Fields(s), " ")85}8687func CanonicalGameID(name string) GameID {88 return GameID(NormalizeName(name))89}9091func CanonicalModeID(title string) ModeID {92 return ModeID(NormalizeName(title))93}9495func (d Definition) HasMode(id ModeID) bool {96 for _, mode := range d.Modes {97 if mode.ID == id {98 return true99 }100 }101 return false102}