package main import ( "embed" "fmt" "io" "regexp" "strings" "text/template" "unicode" ) //go:embed character.kk.template skill.kk.template var templates embed.FS // LoadTemplates sets up templates to render game data to source code. func LoadTemplates() (*template.Template, error) { t := template.New("root") t.Funcs(template.FuncMap{ "kkenum": kkenum, }) return t.ParseFS(templates, "*") } // ExecCharacterKK renders the Koka character module to w. func ExecCharacterKK(t *template.Template, w io.Writer, c []NamedID[Character], pairs, trios []AffinityRelation) error { if len(pairs) != len(c)*len(c) { return fmt.Errorf("there are %d pairs but there must be %d for %d characters", len(pairs), len(c)*len(c), len(c)) } if len(trios) != len(c)*len(c)*len(c) { return fmt.Errorf("there are %d trios but there must be %d for %d characters", len(trios), len(c)*len(c)*len(c), len(c)) } maxid := 0 pm := make(map[int]map[int]int, len(c)) tm := make(map[int]map[int]map[int]int, len(c)) for _, u := range c { maxid = max(maxid, u.ID) pm[u.ID] = make(map[int]int, len(c)) tm[u.ID] = make(map[int]map[int]int, len(c)) for _, v := range c { tm[u.ID][v.ID] = make(map[int]int, len(c)) } } for _, p := range pairs { pm[p.IDA][p.IDB] = p.Affinity } for _, t := range trios { tm[t.IDA][t.IDB][t.IDC] = t.Affinity } data := struct { Characters []NamedID[Character] Pairs []AffinityRelation Trios []AffinityRelation PairMaps map[int]map[int]int TrioMaps map[int]map[int]map[int]int Count int MaxID int }{c, pairs, trios, pm, tm, len(c), maxid} return t.ExecuteTemplate(w, "koka-character", &data) } func ExecSkillKK(t *template.Template, w io.Writer, g []NamedID[SkillGroup], s []Skill) error { data := struct { Groups []NamedID[SkillGroup] Skills []Skill }{g, s} return t.ExecuteTemplate(w, "koka-skill", &data) } func ExecSkillGroupKK(t *template.Template, w io.Writer, g []NamedID[SkillGroup], s []Skill) error { data := struct { Groups []NamedID[SkillGroup] Skills []Skill }{g, s} return t.ExecuteTemplate(w, "koka-skill-group", &data) } const replaceDash = " ,!?/+();#○☆♡'&=♪∀゚∴" var ( kkReplace = func() *strings.Replacer { r := []string{ "Triple 7s", "Triple-Sevens", // hard to replace with the right thing automatically "1,500,000 CC", "One-Million-CC", "15,000,000 CC", "Fifteen-Million-CC", "1st", "First", ".", "", "'s", "s", "ó", "o", "∞", "Infinity", "×", "x", "◎", "Lv2", } for _, c := range replaceDash { r = append(r, string(c), "-") } return strings.NewReplacer(r...) }() kkMultidash = regexp.MustCompile(`-+`) kkDashNonletter = regexp.MustCompile(`-[^A-Za-z]`) ) func kkenum(name string) string { orig := name name = kkReplace.Replace(name) name = kkMultidash.ReplaceAllLiteralString(name, "-") name = strings.Trim(name, "-") if len(name) == 0 { panic(fmt.Errorf("%q became empty as Koka enum variant", orig)) } name = strings.ToUpper(name[:1]) + name[1:] if !unicode.IsLetter(rune(name[0])) { panic(fmt.Errorf("Koka enum variant %q (from %q) starts with a non-letter", name, orig)) } for _, c := range name { if c > 127 { // Koka does not allow non-ASCII characters in source code. // Don't proceed if we've missed one. panic(fmt.Errorf("non-ASCII character %q (%[1]U) in Koka enum variant %q (from %q)", c, name, orig)) } } if kkDashNonletter.MatchString(name) { panic(fmt.Errorf("non-letter character after a dash in Koka enum variant %q (from %q)", name, orig)) } return name }