horsegen: generate races

This commit is contained in:
2026-01-30 23:25:44 -05:00
parent 9dd18ed972
commit 34edcf97a7
11 changed files with 4316 additions and 646 deletions

View File

@@ -11,7 +11,7 @@ import (
"unicode"
)
//go:embed character.kk.template skill.kk.template character.go.template skill.go.template
//go:embed character.kk.template skill.kk.template character.go.template skill.go.template race.kk.template race.go.template
var templates embed.FS
// LoadTemplates sets up templates to render game data to source code.
@@ -93,6 +93,21 @@ func ExecSkill(t *template.Template, region string, kk, g io.Writer, groups []Na
return err
}
func ExecRace(t *template.Template, region string, kk, g io.Writer, races []Race) error {
data := struct {
Region string
Races []Race
}{region, races}
var err error
if kk != nil {
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-race", &data))
}
if g != nil {
err = errors.Join(err, t.ExecuteTemplate(g, "go-race", &data))
}
return err
}
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
var (
@@ -105,11 +120,12 @@ var (
"114th", "Hundred-Fourteenth",
"♡ 3D Nail Art", "Nail-Art",
".", "",
"\u2019", "",
"&", "-and-",
"'s", "s",
"ó", "o",
"∞", "Infinity",
"×", "x",
"\u00d7", "x",
"◎", "Lv2",
}
for _, c := range wordSeps {
@@ -128,10 +144,11 @@ var (
"1st", "First",
"♡ 3D Nail Art", "NailArt",
".", "",
"\u2019", "",
"&", "And",
"'s", "s",
"∞", "Infinity",
"×", "X",
"\u00d7", "X",
"◎", "Lv2",
}
for _, c := range wordSeps {

View File

@@ -23,6 +23,9 @@ var skillGroupSQL string
//go:embed skill.sql
var skillSQL string
//go:embed race.sql
var raceSQL string
type (
Character struct{}
SkillGroup struct{}
@@ -315,3 +318,44 @@ func Skills(ctx context.Context, db *sqlitex.Pool) ([]Skill, error) {
}
return r, nil
}
type Race struct {
ID int
Name string
Grade int
ThumbnailID int
Primary int
}
func Races(ctx context.Context, db *sqlitex.Pool) ([]Race, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for races: %w", err)
}
stmt, _, err := conn.PrepareTransient(raceSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for races: %w", err)
}
defer stmt.Finalize()
var r []Race
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping races: %w", err)
}
if !ok {
break
}
race := Race{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Grade: stmt.ColumnInt(2),
ThumbnailID: stmt.ColumnInt(3),
Primary: stmt.ColumnInt(4),
}
r = append(r, race)
}
return r, nil
}

View File

@@ -50,6 +50,7 @@ func main() {
trios []AffinityRelation
sg []NamedID[SkillGroup]
skills []Skill
races []Race
)
eg.Go(func() error {
slog.Info("get characters")
@@ -81,6 +82,12 @@ func main() {
skills = r
return err
})
eg.Go(func() error {
slog.Info("get races")
r, err := Races(ctx, db)
races = r
return err
})
if err := eg.Wait(); err != nil {
slog.Error("load", slog.Any("err", err))
os.Exit(1)
@@ -116,8 +123,21 @@ func main() {
slog.Info("write skills")
return ExecSkill(t, region, sf, gf, sg, skills)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "race.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "race.go"))
if err != nil {
return err
}
slog.Info("write races")
return ExecRace(t, region, kf, gf, races)
})
if err := eg.Wait(); err != nil {
slog.Error("generate", slog.Any("err", err))
os.Exit(1)
} else {
slog.Info("done")
}

32
horsegen/race.go.template Normal file
View File

@@ -0,0 +1,32 @@
{{- define "go-race" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $r := $.Races }}
Race{{ goenum $r.Name }}{{ if ne $r.Primary $r.ID }}Alternate{{ end }} RaceID = {{ $r.ID }} // {{ $r.Name }}
{{- end }}
)
var AllRaces = map[RaceID]Race{
{{- range $r := $.Races }}
Race{{ goenum $r.Name }}{{ if ne $r.Primary $r.ID }}Alternate{{ end }}: {
ID: {{ $r.ID }},
Name: {{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} + " (Alternate)"{{ end }},
Thumbnail: {{ $r.ThumbnailID }},
{{- if ne $r.Primary $r.ID }}
Primary: {{ $r.Primary }},
{{- end }}
},
{{- end }}
}
var RaceNameToID = map[string]RaceID{
{{- range $r := $.Races }}
{{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} + " (Alternate)"{{ end }}: {{ $r.ID }},
{{- end }}
}
{{ end }}

80
horsegen/race.kk.template Normal file
View File

@@ -0,0 +1,80 @@
{{- define "koka-race" -}}
module horse/{{ $.Region }}/race
// Automatically generated with horsegen; DO NOT EDIT
import std/data/rb-map
import horse/game-id
pub import horse/race
// Enumeration of all races for type-safe programming.
pub type race
{{- range $r := $.Races }}
{{ kkenum $r.Name }}{{ if ne $r.Primary $r.ID }}-Alternate{{ end }}
{{- end }}
// Get the race ID for a race.
pub fun race-id(r: race): race-id
match r
{{- range $r := $.Races }}
{{ kkenum $r.Name }}{{ if ne $r.Primary $r.ID }}-Alternate{{ end }} -> Race-id({{ $r.ID }})
{{- end }}
// List of all races in ID order for easy iterating.
pub val all = [
{{- range $r := $.Races }}
{{ kkenum $r.Name }}{{ if ne $r.Primary $r.ID }}-Alternate{{ end }},
{{- end }}
]
val name2id: rbmap<string, race-id> = rb-map/empty()
{{- range $r := $.Races }}
.set({{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} ++ " (Alternate)"{{ end }}, Race-id({{ $r.ID }}))
{{- end }}
// Get the race ID that has the given exact name.
// Alternate versions of races have " (Alternate)" in their names.
// If no race matches the name, the result is an invalid ID.
pub fun from-name(name: string): race-id
name2id.lookup(name).default(Race-id(0))
// Get the name for a race.
// Alternate versions of races have " (Alternate)" in their names.
// If no race matches the ID, the result is the numeric ID.
pub fun show(r: race-id): string
match r.game-id
{{- range $r := $.Races }}
{{ $r.ID }} -> {{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} ++ " (Alternate)"{{ end }}
{{- end }}
x -> "race " ++ x.show
// Get the grade for a race.
// If no race matches the ID, the result is Pre-OP.
pub fun grade(r: race-id): grade
match r.game-id
{{- range $r := $.Races }}
{{ $r.ID }} -> {{ if eq $r.Grade 100 }}G1{{ else if eq $r.Grade 200 }}G2{{ else if eq $r.Grade 300 }}G3{{ else if eq $r.Grade 400 }}OP{{ else if eq $r.Grade 700 }}Pre-OP{{ else }}??? $r.Grade={{ $r.Grade }}{{ end }}
{{- end }}
_ -> Pre-OP
// Get the thumbnail ID for a race.
// If no race matches the ID, the result is an invalid ID.
pub fun thumbnail(r: race-id): race-thumbnail-id
match r.game-id
{{- range $r := $.Races }}
{{ $r.ID }} -> Race-thumbnail-id({{ $r.ThumbnailID }})
{{- end }}
_ -> Race-thumbnail-id(0)
// Get the primary ID for a race.
// For races which are the primary version, or if no race matches the given ID,
// the result is the input.
pub fun primary(r: race-id): race-id
match r.game-id
{{- range $r := $.Races }}
{{- if ne $r.ID $r.Primary }}
{{ $r.ID }} -> Race-id({{ $r.Primary }})
{{- end }}
{{- end }}
_ -> r
{{ end }}

13
horsegen/race.sql Normal file
View File

@@ -0,0 +1,13 @@
WITH race_names AS (
SELECT "index" AS id, "text" AS name FROM text_data WHERE category = 33
)
SELECT
race.id,
race_names.name,
race.grade,
race.thumbnail_id,
MIN(race.id) OVER (PARTITION BY race_names.name) AS "primary"
FROM race
JOIN race_names ON race.id = race_names.id
WHERE race."group" = 1
ORDER BY race.id