diff --git a/doc/README.md b/doc/README.md
index 8e882d2..fab6de7 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -12,6 +12,7 @@ This file is my notes from exploring the database.
- 147 is spark names, 172 is spark descriptions
- 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names
- 65 is player titles, 66 is title descriptions - ties with honor_data?
+- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
# succession factor (sparks)
diff --git a/horse/game-id.kk b/horse/game-id.kk
index 5cdd902..c6b05a3 100644
--- a/horse/game-id.kk
+++ b/horse/game-id.kk
@@ -7,6 +7,9 @@ alias game-id = int
// Specific game ID types.
// I've already made mistakes with ID categories and I haven't even committed this file yet.
+pub struct scenario-id
+ game-id: game-id
+
// Game ID for characters.
// Generally numbers in the range 1000-9999.
pub struct character-id
diff --git a/horse/global/scenario.go b/horse/global/scenario.go
new file mode 100644
index 0000000..cd86309
--- /dev/null
+++ b/horse/global/scenario.go
@@ -0,0 +1,23 @@
+package global
+
+// Automatically generated with horsegen; DO NOT EDIT
+
+import . "git.sunturtle.xyz/zephyr/horse/horse"
+
+const (
+ ScenarioURAFinale ScenarioID = 1 // URA Finale
+ ScenarioUnityCup ScenarioID = 2 // Unity Cup
+)
+
+var AllScenarios = map[ScenarioID]Scenario{
+ ScenarioURAFinale: {
+ ID: 1,
+ Name: "URA Finale",
+ Title: "The Beginning: URA Finale",
+ },
+ ScenarioUnityCup: {
+ ID: 2,
+ Name: "Unity Cup",
+ Title: "Unity Cup: Shine On, Team Spirit!",
+ },
+}
diff --git a/horse/global/scenario.kk b/horse/global/scenario.kk
new file mode 100644
index 0000000..663a6b3
--- /dev/null
+++ b/horse/global/scenario.kk
@@ -0,0 +1,38 @@
+module horse/global/scenario
+
+// Automatically generated with horsegen; DO NOT EDIT
+
+import horse/game-id
+
+// Enumeration of all scenarios for type-safe programming.
+pub type scenario
+ URA-Finale
+ Unity-Cup
+
+// Get the scenario ID for a scenario.
+pub fun scenario-id(s: scenario): scenario-id
+ match s
+ URA-Finale -> Scenario-id(1)
+ Unity-Cup -> Scenario-id(2)
+
+// List of all scenarios in ID order for easy iterating.
+pub val all = [
+ URA-Finale,
+ Unity-Cup,
+]
+
+// Get the name for a scenario.
+// If no scenario matches the ID, the result contains the numeric ID.
+pub fun show(s: scenario-id): string
+ match s.game-id
+ 1 -> "URA Finale"
+ 2 -> "Unity Cup"
+ x -> "scenario " ++ x.show
+
+// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
+// If no scenario matches the ID, the result contains the numeric ID.
+pub fun title(s: scenario-id): string
+ match s.game-id
+ 1 -> "The Beginning: URA Finale"
+ 2 -> "Unity Cup: Shine On, Team Spirit!"
+ x -> "scenario " ++ x.show
diff --git a/horse/race.go b/horse/race.go
index 374b2e4..2438ed1 100644
--- a/horse/race.go
+++ b/horse/race.go
@@ -35,3 +35,12 @@ const (
SaddleTypeG2
SaddleTypeG1
)
+
+type ScenarioID int8
+
+// Scenario is metadata about a career scenario.
+type Scenario struct {
+ ID ScenarioID
+ Name string
+ Title string
+}
diff --git a/horse/spark.kk b/horse/spark.kk
index 8327d8d..f54b8ac 100644
--- a/horse/spark.kk
+++ b/horse/spark.kk
@@ -1,13 +1,14 @@
module horse/spark
-// A single spark.
-// Parameterized by the spark type: stat, aptitude, unique, race, or skill.
-pub struct spark
- kind: a
- level: level
+import horse/game-id
-pub fun spark/show(spark: spark, level-fancy: string = "*", ?kind: (a) -> string): string
- kind(spark.kind) ++ " " ++ spark.level.show ++ level-fancy
+pub type spark
+ Stat(s: stat, l: level)
+ Aptitude(a: aptitude, l: level)
+ Unique(s: skill-id, l: level)
+ Race(r: race-id, l: level)
+ Skill(s: skill-id, l: level)
+ Scenario(s: scenario-id, l: level)
pub type level
One
@@ -64,123 +65,3 @@ pub fun aptitude/show(this : aptitude): string
Pace-Chaser -> "Pace Chaser"
Late-Surger -> "Late Surger"
End-Closer -> "End Closer"
-
-// Unique (green) spark.
-// TODO: decide this representation; strings? umas? probably depends on skills generally
-pub type unique
-
-pub fun unique/show(this: unique): string
- "TODO(zeph): unique skills"
-
-// Race, skill, and scenario (white) sparks.
-pub type generic
- February-Stakes
- Takamatsunomiya-Kinen
- Osaka-Hai
- Oka-Sho
- Satsuki-Sho
- Tenno-Sho-Spring
- NHK-Mile-Cup
- Victoria-Mile
- Japanese-Oaks
- Japanese-Derby
- Yasuda-Kinen
- Takarazuka-Kinen
- Sprinters-Stakes
- Shuka-Sho
- Kikuka-Sho
- Tenno-Sho-Autumn
- Queen-Elizabeth-II-Cup
- Mile-Championship
- Japan-Cup
- Champions-Cup
- Hanshin-Juvenile-Fillies
- Asahi-Hai-Futurity-Stakes
- Arima-Kinen
- Hopeful-Stakes
- Tokyo-Daishoten
- JBC-Classic
- JBC-Sprint
- JBC-Ladies-Classic
- Japan-Dirt-Derby
- Teio-Sho
- Skill(skill: string)
- URA-Finale
- Unity-Cup
-
-// Automatically generated.
-// Equality comparison of the `generic` type.
-pub fun generic/(==)(this : generic, other : generic) : e bool
- match (this, other)
- (February-Stakes, February-Stakes) -> True
- (Takamatsunomiya-Kinen, Takamatsunomiya-Kinen) -> True
- (Osaka-Hai, Osaka-Hai) -> True
- (Oka-Sho, Oka-Sho) -> True
- (Satsuki-Sho, Satsuki-Sho) -> True
- (Tenno-Sho-Spring, Tenno-Sho-Spring) -> True
- (NHK-Mile-Cup, NHK-Mile-Cup) -> True
- (Victoria-Mile, Victoria-Mile) -> True
- (Japanese-Oaks, Japanese-Oaks) -> True
- (Japanese-Derby, Japanese-Derby) -> True
- (Yasuda-Kinen, Yasuda-Kinen) -> True
- (Takarazuka-Kinen, Takarazuka-Kinen) -> True
- (Sprinters-Stakes, Sprinters-Stakes) -> True
- (Shuka-Sho, Shuka-Sho) -> True
- (Kikuka-Sho, Kikuka-Sho) -> True
- (Tenno-Sho-Autumn, Tenno-Sho-Autumn) -> True
- (Queen-Elizabeth-II-Cup, Queen-Elizabeth-II-Cup) -> True
- (Mile-Championship, Mile-Championship) -> True
- (Japan-Cup, Japan-Cup) -> True
- (Champions-Cup, Champions-Cup) -> True
- (Hanshin-Juvenile-Fillies, Hanshin-Juvenile-Fillies) -> True
- (Asahi-Hai-Futurity-Stakes, Asahi-Hai-Futurity-Stakes) -> True
- (Arima-Kinen, Arima-Kinen) -> True
- (Hopeful-Stakes, Hopeful-Stakes) -> True
- (Tokyo-Daishoten, Tokyo-Daishoten) -> True
- (JBC-Classic, JBC-Classic) -> True
- (JBC-Sprint, JBC-Sprint) -> True
- (JBC-Ladies-Classic, JBC-Ladies-Classic) -> True
- (Japan-Dirt-Derby, Japan-Dirt-Derby) -> True
- (Teio-Sho, Teio-Sho) -> True
- (Skill(skill), Skill(skill')) -> skill == skill'
- (URA-Finale, URA-Finale) -> True
- (Unity-Cup, Unity-Cup) -> True
- (_, _) -> False
-
-// Automatically generated.
-// Shows a string representation of the `generic` type.
-pub fun generic/show(this : generic) : e string
- match this
- February-Stakes -> "February Stakes"
- Takamatsunomiya-Kinen -> "Takamatsunomiya Kinen"
- Osaka-Hai -> "Osaka Hai"
- Oka-Sho -> "Oka Sho"
- Satsuki-Sho -> "Satsuki Sho"
- Tenno-Sho-Spring -> "Tenno Sho Spring"
- NHK-Mile-Cup -> "NHK Mile Cup"
- Victoria-Mile -> "Victoria Mile"
- Japanese-Oaks -> "Japanese Oaks"
- Japanese-Derby -> "Japanese Derby"
- Yasuda-Kinen -> "Yasuda Kinen"
- Takarazuka-Kinen -> "Takarazuka Kinen"
- Sprinters-Stakes -> "Sprinters Stakes"
- Shuka-Sho -> "Shuka Sho"
- Kikuka-Sho -> "Kikuka Sho"
- Tenno-Sho-Autumn -> "Tenno Sho Autumn"
- Queen-Elizabeth-II-Cup -> "Queen Elizabeth II Cup"
- Mile-Championship -> "Mile Championship"
- Japan-Cup -> "Japan Cup"
- Champions-Cup -> "Champions Cup"
- Hanshin-Juvenile-Fillies -> "Hanshin Juvenile Fillies"
- Asahi-Hai-Futurity-Stakes -> "Asahi Hai Futurity Stakes"
- Arima-Kinen -> "Arima Kinen"
- Hopeful-Stakes -> "Hopeful Stakes"
- Tokyo-Daishoten -> "Tokyo Daishoten"
- JBC-Classic -> "JBC Classic"
- JBC-Sprint -> "JBC Sprint"
- JBC-Ladies-Classic -> "JBC Ladies Classic"
- Japan-Dirt-Derby -> "Japan Dirt Derby"
- Teio-Sho -> "Teio Sho"
- Skill(skill) -> skill.show
- URA-Finale -> "URA Finale"
- Unity-Cup -> "Unity Cup"
diff --git a/horsegen/gen.go b/horsegen/gen.go
index d0e9106..215c9c5 100644
--- a/horsegen/gen.go
+++ b/horsegen/gen.go
@@ -123,6 +123,21 @@ func ExecSaddle(t *template.Template, region string, kk, g io.Writer, saddles []
return err
}
+func ExecScenario(t *template.Template, region string, kk, g io.Writer, scen []Scenario) error {
+ data := struct {
+ Region string
+ Scenarios []Scenario
+ }{region, scen}
+ var err error
+ if kk != nil {
+ err = errors.Join(err, t.ExecuteTemplate(kk, "koka-scenario", &data))
+ }
+ if g != nil {
+ err = errors.Join(err, t.ExecuteTemplate(g, "go-scenario", &data))
+ }
+ return err
+}
+
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
var (
diff --git a/horsegen/load.go b/horsegen/load.go
index 5412d14..dfb3441 100644
--- a/horsegen/load.go
+++ b/horsegen/load.go
@@ -29,6 +29,9 @@ var raceSQL string
//go:embed saddle.sql
var saddleSQL string
+//go:embed scenario.sql
+var scenarioSQL string
+
type (
Character struct{}
SkillGroup struct{}
@@ -407,3 +410,40 @@ func Saddles(ctx context.Context, db *sqlitex.Pool) ([]Saddle, error) {
}
return r, nil
}
+
+type Scenario struct {
+ ID int
+ Name string
+ Title string
+}
+
+func Scenarios(ctx context.Context, db *sqlitex.Pool) ([]Scenario, error) {
+ conn, err := db.Take(ctx)
+ defer db.Put(conn)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't get connection for scenario: %w", err)
+ }
+ stmt, _, err := conn.PrepareTransient(scenarioSQL)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't prepare statement for scenario: %w", err)
+ }
+ defer stmt.Finalize()
+
+ var r []Scenario
+ for {
+ ok, err := stmt.Step()
+ if err != nil {
+ return nil, fmt.Errorf("error stepping scenarios: %w", err)
+ }
+ if !ok {
+ break
+ }
+ s := Scenario{
+ ID: stmt.ColumnInt(0),
+ Name: stmt.ColumnText(1),
+ Title: stmt.ColumnText(2),
+ }
+ r = append(r, s)
+ }
+ return r, nil
+}
diff --git a/horsegen/main.go b/horsegen/main.go
index 39e0cc5..178f79c 100644
--- a/horsegen/main.go
+++ b/horsegen/main.go
@@ -52,6 +52,7 @@ func main() {
skills []Skill
races []Race
saddles []Saddle
+ scens []Scenario
)
eg.Go(func() error {
slog.Info("get characters")
@@ -91,8 +92,14 @@ func main() {
})
eg.Go(func() error {
slog.Info("get saddles")
- s, err := Saddles(ctx, db)
- saddles = s
+ r, err := Saddles(ctx, db)
+ saddles = r
+ return err
+ })
+ eg.Go(func() error {
+ slog.Info("get scenarios")
+ r, err := Scenarios(ctx, db)
+ scens = r
return err
})
if err := eg.Wait(); err != nil {
@@ -154,6 +161,18 @@ func main() {
slog.Info("write saddles")
return ExecSaddle(t, region, kf, gf, saddles)
})
+ eg.Go(func() error {
+ kf, err := os.Create(filepath.Join(out, region, "scenario.kk"))
+ if err != nil {
+ return err
+ }
+ gf, err := os.Create(filepath.Join(out, region, "scenario.go"))
+ if err != nil {
+ return err
+ }
+ slog.Info("write scenarios")
+ return ExecScenario(t, region, kf, gf, scens)
+ })
if err := eg.Wait(); err != nil {
slog.Error("generate", slog.Any("err", err))
os.Exit(1)
diff --git a/horsegen/scenario.go.template b/horsegen/scenario.go.template
new file mode 100644
index 0000000..21fad59
--- /dev/null
+++ b/horsegen/scenario.go.template
@@ -0,0 +1,23 @@
+{{- define "go-scenario" -}}
+package {{ $.Region }}
+
+// Automatically generated with horsegen; DO NOT EDIT
+
+import . "git.sunturtle.xyz/zephyr/horse/horse"
+
+const (
+ {{- range $s := $.Scenarios }}
+ Scenario{{ goenum $s.Name }} ScenarioID = {{ $s.ID }} // {{ $s.Name }}
+ {{- end }}
+)
+
+var AllScenarios = map[ScenarioID]Scenario{
+ {{- range $s := $.Scenarios }}
+ Scenario{{ goenum $s.Name }}: {
+ ID: {{ $s.ID }},
+ Name: {{ printf "%q" $s.Name }},
+ Title: {{ printf "%q" $s.Title }},
+ },
+ {{- end }}
+}
+{{ end }}
\ No newline at end of file
diff --git a/horsegen/scenario.kk.template b/horsegen/scenario.kk.template
new file mode 100644
index 0000000..ef4716e
--- /dev/null
+++ b/horsegen/scenario.kk.template
@@ -0,0 +1,45 @@
+{{- define "koka-scenario" -}}
+module horse/{{ $.Region }}/scenario
+
+// Automatically generated with horsegen; DO NOT EDIT
+
+import horse/game-id
+
+// Enumeration of all scenarios for type-safe programming.
+pub type scenario
+ {{- range $s := $.Scenarios }}
+ {{ kkenum $s.Name }}
+ {{- end }}
+
+// Get the scenario ID for a scenario.
+pub fun scenario-id(s: scenario): scenario-id
+ match s
+ {{- range $s := $.Scenarios }}
+ {{ kkenum $s.Name }} -> Scenario-id({{ $s.ID }})
+ {{- end }}
+
+// List of all scenarios in ID order for easy iterating.
+pub val all = [
+ {{- range $s := $.Scenarios }}
+ {{ kkenum $s.Name }},
+ {{- end }}
+]
+
+// Get the name for a scenario.
+// If no scenario matches the ID, the result contains the numeric ID.
+pub fun show(s: scenario-id): string
+ match s.game-id
+ {{- range $s := $.Scenarios }}
+ {{ $s.ID }} -> {{ printf "%q" $s.Name }}
+ {{- end }}
+ x -> "scenario " ++ x.show
+
+// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
+// If no scenario matches the ID, the result contains the numeric ID.
+pub fun title(s: scenario-id): string
+ match s.game-id
+ {{- range $s := $.Scenarios }}
+ {{ $s.ID }} -> {{ printf "%q" $s.Title }}
+ {{- end }}
+ x -> "scenario " ++ x.show
+{{ end }}
diff --git a/horsegen/scenario.sql b/horsegen/scenario.sql
new file mode 100644
index 0000000..f802651
--- /dev/null
+++ b/horsegen/scenario.sql
@@ -0,0 +1,17 @@
+WITH scenario_name AS (
+ SELECT "index" AS id, "text" AS name
+ FROM text_data
+ WHERE category = 237
+), scenario_title AS (
+ SELECT "index" AS id, "text" AS title
+ FROM text_data
+ WHERE category = 119
+)
+SELECT
+ sc.id,
+ n.name,
+ t.title
+FROM single_mode_scenario sc
+ JOIN scenario_name n ON sc.id = n.id
+ JOIN scenario_title t ON sc.id = t.id
+ORDER BY sc.id