Compare commits

..

30 Commits

Author SHA1 Message Date
c00d3d0186 cmd/horsebot: move to here 2026-02-10 14:03:41 -05:00
a534975601 horsegen: generate alternate races/saddles with ids 2026-02-10 13:55:47 -05:00
b55e1bc200 horse: generate with 2026-02-10 global db 2026-02-10 07:58:22 -05:00
c58dbd19b0 horsegen: generate saddles 2026-02-09 20:56:01 -05:00
2fcd608102 horse: rework for saddles 2026-02-07 09:34:19 -05:00
546f2db327 horse: generate with 2026-02-05 global db 2026-02-05 15:55:09 -05:00
856c94723f doc: some more notes on races 2026-02-04 22:53:21 -05:00
2393bf2fa5 horse: fix formatting of abilities that target styles 2026-02-01 15:33:04 -05:00
bf06de0f5e horse: rearrange career race results 2026-02-01 15:31:46 -05:00
f3f070ca2b horse: add canned functions for race grades 2026-01-31 13:44:06 -05:00
34edcf97a7 horsegen: generate races 2026-01-30 23:25:44 -05:00
9dd18ed972 doc: add diff of db changes for ny haru urara and opera added 2026-01-30 10:30:44 -05:00
332cf3f13a horse: regenerate with 2026-01-29 global db 2026-01-30 10:22:02 -05:00
c5a1cdea5f horsegen: don't discard errors 2026-01-30 10:19:22 -05:00
542d4198e7 horsegen: generate enumerations and lists 2026-01-27 21:50:57 -05:00
98afe7384a horsegen: redesign character template 2026-01-27 21:33:26 -05:00
e890108591 horse, horsegen: redesign approach for koka 2026-01-27 16:49:55 -05:00
0126101b1b meta: add shell script to generate while on linux 2026-01-24 09:37:41 -05:00
5bf2588d41 horsegen: include unique owner in skill info 2026-01-23 23:37:36 -05:00
a5f84754ea doc: add updated sql dump and notes about consistent table changes 2026-01-23 22:46:05 -05:00
4bfb06b682 horse: fix units in skill ability strings 2026-01-22 23:05:53 -05:00
72b8bc9c6c doc: add diff of db changes for tamamo cross added 2026-01-22 22:25:28 -05:00
1ae654c266 horse: generate with 2026-01-22 global db 2026-01-22 09:32:38 -05:00
74ee76c5da horse/prob: add basic distribution operations 2026-01-22 00:31:41 -05:00
36d27f1642 horse/prob: first pass on P(A xor B) 2026-01-20 01:03:51 -05:00
9469c2c7a2 horse/prob: remove redundant qualifiers 2026-01-19 22:30:19 -05:00
a8921e9cf6 horse/prob: basic probability distribution stuff 2026-01-19 21:54:25 -05:00
f9ad769d9f horse/prob: kfl addition 2026-01-19 17:21:51 -05:00
ec2efee5d5 horse/prob: begin work on kfl statistics 2026-01-19 14:58:42 -05:00
d147d71519 horse: implement all known ability value usage types 2026-01-18 15:20:43 -05:00
48 changed files with 152625 additions and 5951 deletions

1
cmd/horsebot/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
token

10
cmd/horsebot/README.md Normal file
View File

@@ -0,0 +1,10 @@
# horsebot
Discord bot serving horse game data.
Production instance is named Zenno Rob Roy, because she has read all about Umamusume and is always happy to share her knowledge and give recommendations.
## Running
The bot always uses the Gateway API.
If the `-http` argument is provided, it will also use the HTTP API, and `-key` must also be provided.

View File

@@ -0,0 +1,56 @@
package autocomplete
import (
"bytes"
"cmp"
"slices"
"sync"
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/util"
)
// Set is an autocomplete set.
type Set[V any] struct {
keys []util.Chars
vals []V
}
// Add associates a value with a key in the autocomplete set.
// The behavior is undefined if the key already has a value.
func (s *Set[V]) Add(key string, val V) {
k := util.ToChars([]byte(key))
i, _ := slices.BinarySearchFunc(s.keys, k, func(a, b util.Chars) int {
return bytes.Compare(a.Bytes(), b.Bytes())
})
s.keys = slices.Insert(s.keys, i, k)
s.vals = slices.Insert(s.vals, i, val)
}
// Find appends to r all values in the set with keys that key matches.
func (s *Set[V]) Find(r []V, key string) []V {
initFzf()
var (
p = []rune(key)
got []V
t []algo.Result
slab util.Slab
)
for i := range s.keys {
res, _ := algo.FuzzyMatchV2(false, true, true, &s.keys[i], p, false, &slab)
if res.Score <= 0 {
continue
}
j, _ := slices.BinarySearchFunc(t, res, func(a, b algo.Result) int { return -cmp.Compare(a.Score, b.Score) })
// Insert after all other matches with the same score for stability.
for j < len(t) && t[j].Score == res.Score {
j++
}
t = slices.Insert(t, j, res)
got = slices.Insert(got, j, s.vals[i])
}
return append(r, got...)
}
var initFzf = sync.OnceFunc(func() { algo.Init("default") })

View File

@@ -0,0 +1,70 @@
package autocomplete_test
import (
"slices"
"testing"
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
)
func these(s ...string) []string { return s }
func TestAutocomplete(t *testing.T) {
cases := []struct {
name string
add []string
search string
want []string
}{
{
name: "empty",
add: nil,
search: "",
want: nil,
},
{
name: "exact",
add: these("bocchi"),
search: "bocchi",
want: these("bocchi"),
},
{
name: "extra",
add: these("bocchi", "ryo", "nijika", "kita"),
search: "bocchi",
want: these("bocchi"),
},
{
name: "short",
add: these("bocchi", "ryo", "nijika", "kita"),
search: "o",
want: these("bocchi", "ryo"),
},
{
name: "unrelated",
add: these("bocchi", "ryo", "nijika", "kita"),
search: "x",
want: nil,
},
{
name: "map",
add: these("Corazón ☆ Ardiente"),
search: "corazo",
want: these("Corazón ☆ Ardiente"),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var set autocomplete.Set[string]
for _, s := range c.add {
set.Add(s, s)
}
got := set.Find(nil, c.search)
slices.Sort(c.want)
slices.Sort(got)
if !slices.Equal(c.want, got) {
t.Errorf("wrong results: want %q, got %q", c.want, got)
}
})
}
}

55
cmd/horsebot/log.go Normal file
View File

@@ -0,0 +1,55 @@
package main
import (
"log/slog"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/handler"
)
func logMiddleware(next handler.Handler) handler.Handler {
return func(e *handler.InteractionEvent) error {
var msg string
attrs := make([]slog.Attr, 0, 8)
attrs = append(attrs,
slog.Uint64("interaction", uint64(e.Interaction.ID())),
slog.Uint64("user", uint64(e.Interaction.User().ID)),
)
if guild := e.Interaction.GuildID(); guild != nil {
attrs = append(attrs, slog.String("guild", guild.String()))
}
switch i := e.Interaction.(type) {
case discord.ApplicationCommandInteraction:
msg = "command"
attrs = append(attrs,
slog.String("name", i.Data.CommandName()),
slog.Int("type", int(i.Data.Type())),
)
switch data := i.Data.(type) {
case discord.SlashCommandInteractionData:
attrs = append(attrs, slog.String("path", data.CommandPath()))
}
case discord.AutocompleteInteraction:
msg = "autocomplete"
attrs = append(attrs,
slog.String("name", i.Data.CommandName),
slog.String("path", i.Data.CommandPath()),
slog.String("focus", i.Data.Focused().Name),
)
case discord.ComponentInteraction:
msg = "component"
attrs = append(attrs,
slog.Int("type", int(i.Data.Type())),
slog.String("custom", i.Data.CustomID()),
)
default:
slog.WarnContext(e.Ctx, "unknown interaction", slog.Any("event", e))
return nil
}
slog.LogAttrs(e.Ctx, slog.LevelInfo, msg, attrs...)
return next(e)
}
}

183
cmd/horsebot/main.go Normal file
View File

@@ -0,0 +1,183 @@
package main
import (
"bytes"
"context"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"strconv"
"time"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/handler"
"github.com/disgoorg/disgo/handler/middleware"
"github.com/disgoorg/disgo/httpserver"
"github.com/disgoorg/disgo/rest"
"git.sunturtle.xyz/zephyr/horse/horse"
"git.sunturtle.xyz/zephyr/horse/horse/global"
)
func main() {
var (
tokenFile string
// http api options
addr string
route string
pubkey string
// logging options
level slog.Level
textfmt string
)
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
flag.StringVar(&addr, "http", "", "`address` to bind HTTP API server")
flag.StringVar(&route, "route", "/interactions/callback", "`path` to serve HTTP API calls")
flag.StringVar(&pubkey, "key", "", "Discord public key")
flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`")
flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json")
flag.Parse()
var lh slog.Handler
switch textfmt {
case "text":
lh = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})
case "json":
lh = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: level})
default:
fmt.Fprintf(os.Stderr, "invalid log format %q, must be text or json", textfmt)
os.Exit(1)
}
slog.SetDefault(slog.New(lh))
token, err := os.ReadFile(tokenFile)
if err != nil {
slog.Error("reading token", slog.Any("err", err))
os.Exit(1)
}
token = bytes.TrimSuffix(token, []byte{'\n'})
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
r := handler.New()
r.DefaultContext(func() context.Context { return ctx })
r.Use(middleware.Go)
r.Use(logMiddleware)
r.Route("/skill", func(r handler.Router) {
r.SlashCommand("/", skillHandler)
r.Autocomplete("/", skillAutocomplete)
r.ButtonComponent("/{id}", skillButton)
})
opts := []bot.ConfigOpt{bot.WithDefaultGateway(), bot.WithEventListeners(r)}
if addr != "" {
if pubkey == "" {
slog.Error("Discord public key must be provided when using HTTP API")
os.Exit(1)
}
opts = append(opts, bot.WithHTTPServerConfigOpts(pubkey,
httpserver.WithAddress(addr),
httpserver.WithURL(route),
))
}
slog.Info("connect", slog.String("disgo", disgo.Version))
client, err := disgo.New(string(token), opts...)
if err != nil {
slog.Error("building bot", slog.Any("err", err))
os.Exit(1)
}
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
slog.Error("syncing commands", slog.Any("err", err))
os.Exit(1)
}
if addr != "" {
slog.Info("start HTTP server", slog.String("address", addr), slog.String("route", route))
if err := client.OpenHTTPServer(); err != nil {
slog.Error("starting HTTP server", slog.Any("err", err))
stop()
}
}
slog.Info("start gateway")
if err := client.OpenGateway(ctx); err != nil {
slog.Error("starting gateway", slog.Any("err", err))
stop()
}
slog.Info("ready")
<-ctx.Done()
stop()
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
defer stop()
client.Close(ctx)
}
var commands = []discord.ApplicationCommandCreate{
discord.SlashCommandCreate{
Name: "skill",
Description: "Umamusume skill data",
Options: []discord.ApplicationCommandOption{
discord.ApplicationCommandOptionString{
Name: "query",
Description: "Skill name or ID",
Required: true,
Autocomplete: true,
},
},
},
}
func skillHandler(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
q := data.String("query")
id, err := strconv.ParseInt(q, 10, 32)
if err == nil {
// note inverted condition; this is when we have an id
id = int64(global.AllSkills[horse.SkillID(id)].ID)
}
if id == 0 {
// Either we weren't given a number or the number doesn't match any skill ID.
v := global.SkillNameToID[q]
if v == 0 {
// No such skill.
m := discord.MessageCreate{
Content: "No such skill.",
Flags: discord.MessageFlagEphemeral,
}
return e.CreateMessage(m)
}
id = int64(v)
}
// TODO(zeph): search conditions and effects, give a list
m := discord.MessageCreate{
Components: []discord.LayoutComponent{RenderSkill(horse.SkillID(id), global.AllSkills, global.SkillGroups)},
Flags: discord.MessageFlagIsComponentsV2,
}
return e.CreateMessage(m)
}
func skillAutocomplete(e *handler.AutocompleteEvent) error {
q := e.Data.String("query")
opts := skillGlobalAuto().Find(nil, q)
return e.AutocompleteResult(opts[:min(len(opts), 25)])
}
func skillButton(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
if err != nil {
m := discord.MessageCreate{
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
Flags: discord.MessageFlagEphemeral,
}
return e.CreateMessage(m)
}
m := discord.MessageUpdate{
Components: &[]discord.LayoutComponent{RenderSkill(horse.SkillID(id), global.AllSkills, global.SkillGroups)},
}
return e.UpdateMessage(m)
}

148
cmd/horsebot/skill.go Normal file
View File

@@ -0,0 +1,148 @@
package main
import (
"fmt"
"strings"
"sync"
"github.com/disgoorg/disgo/discord"
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
"git.sunturtle.xyz/zephyr/horse/horse"
"git.sunturtle.xyz/zephyr/horse/horse/global"
)
func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map[int32][4]horse.SkillID) discord.ContainerComponent {
s, ok := all[id]
if !ok {
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to RenderSkill", id))
}
thumburl := fmt.Sprintf("https://gametora.com/images/umamusume/skill_icons/utx_ico_skill_%d.png", s.IconID)
top := "## " + s.Name
if s.UniqueOwner != "" {
top += "\n-# " + s.UniqueOwner
}
r := discord.NewContainer(
discord.NewSection(
discord.NewTextDisplay(top),
discord.NewTextDisplay(s.Description),
).WithAccessory(discord.NewThumbnail(thumburl)),
)
var skilltype string
switch {
case s.Rarity == 3, s.Rarity == 4, s.Rarity == 5:
// unique of various star levels
r.AccentColor = 0xaca4d4
skilltype = "Unique Skill"
case s.UniqueOwner != "":
r.AccentColor = 0xcccccc
skilltype = "Inherited Unique"
case s.Rarity == 2:
// rare (gold)
r.AccentColor = 0xd7c25b
skilltype = "Rare Skill"
case s.GroupRate == -1:
// negative (purple) skill
r.AccentColor = 0x9151d4
skilltype = "Negative Skill"
case !s.WitCheck:
// should be passive (green)
r.AccentColor = 0x66ae1c
skilltype = "Passive Skill"
case isDebuff(s):
// debuff (red)
r.AccentColor = 0xe34747
skilltype = "Debuff Skill"
case s.Rarity == 1:
// common (white)
r.AccentColor = 0xcccccc
skilltype = "Common Skill"
}
r.Components = append(r.Components, discord.NewSmallSeparator())
text := make([]string, 0, 3)
abils := make([]string, 0, 3)
for _, act := range s.Activations {
text, abils = text[:0], abils[:0]
if act.Precondition != "" {
text = append(text, "Precondition: "+formatCondition(act.Precondition))
}
text = append(text, "Condition: "+formatCondition(act.Condition))
var t string
switch {
case act.Duration < 0:
// passive; do nothing
case act.Duration == 0:
t = "Instantaneous "
case act.Duration >= 500e4:
t = "Permanent "
default:
t = "For " + act.Duration.String() + "s, "
}
for _, a := range act.Abilities {
abils = append(abils, a.String())
}
t += strings.Join(abils, ", ")
if act.Cooldown > 0 && act.Cooldown < 500e4 {
t += " on " + act.Cooldown.String() + "s cooldown"
}
text = append(text, t)
r.Components = append(r.Components, discord.NewTextDisplay(strings.Join(text, "\n")))
}
l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, s.SPCost, s.GradeValue, s.ID)
r.Components = append(r.Components, discord.NewSmallSeparator(), l)
rel := make([]horse.Skill, 0, 4)
for _, id := range groups[s.Group] {
if id != 0 {
rel = append(rel, all[id])
}
}
if len(rel) > 1 {
buttons := make([]discord.InteractiveComponent, 0, 4)
for _, rs := range rel {
b := discord.NewSecondaryButton(rs.Name, fmt.Sprintf("/skill/%d", rs.ID))
if rs.ID == id {
b = b.AsDisabled()
}
buttons = append(buttons, b)
}
r.Components = append(r.Components, discord.NewActionRow(buttons...))
}
return r
}
func formatCondition(s string) string {
s = strings.ReplaceAll(s, "&", " & ")
if strings.ContainsRune(s, '@') {
return "```\n" + strings.ReplaceAll(s, "@", "\n@\n") + "```"
}
return "`" + s + "`"
}
func isDebuff(s horse.Skill) bool {
for _, act := range s.Activations {
for _, a := range act.Abilities {
if a.Value < 0 {
return true
}
}
}
return false
}
var skillGlobalAuto = sync.OnceValue(func() *autocomplete.Set[discord.AutocompleteChoice] {
var set autocomplete.Set[discord.AutocompleteChoice]
for _, id := range global.OrderedSkills {
s := global.AllSkills[id]
set.Add(s.Name, discord.AutocompleteChoiceString{Name: s.Name, Value: s.Name})
if s.UniqueOwner != "" {
if s.Rarity >= 3 {
set.Add(s.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + s.UniqueOwner, Value: s.Name})
} else {
set.Add(s.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + s.UniqueOwner, Value: s.Name})
}
}
}
return &set
})

1903
doc/2026-01-22-global.diff Normal file

File diff suppressed because it is too large Load Diff

123780
doc/2026-01-22-global.sql Normal file

File diff suppressed because it is too large Load Diff

1574
doc/2026-01-29-global.diff Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ This file is my notes from exploring the database.
- 47 is skill names, 48 is skill descriptions - 47 is skill names, 48 is skill descriptions
- 75 is support card names incl. variant, 76 is support card variant, 77 is support card character - 75 is support card names incl. variant, 76 is support card variant, 77 is support card character
- 147 is spark names, 172 is spark descriptions - 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 - 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? - 65 is player titles, 66 is title descriptions - ties with honor_data?
# succession factor (sparks) # succession factor (sparks)
@@ -128,8 +128,8 @@ race sparks with skills always give +1, skill sparks always give +1-5, unique sp
- single_mode_skill_need_point is base number of skill points to buy each skill - single_mode_skill_need_point is base number of skill points to buy each skill
- support card skill hints are defined in single_mode_hint_gain - support card skill hints are defined in single_mode_hint_gain
- skill_set is NOT trainee skills, seems to be npcs - skill_set includes trainee unique starting skills, among many other things
- available_skill_set has trainee starting skills - available_skill_set has trainee starting skills other than their uniques
skill categories: skill categories:
- 0 passive - 0 passive
@@ -193,6 +193,22 @@ seems to be activate_lot = 1 means wit check, 0 means guaranteed
single_mode_wins_saddle defines titles (classic triple crown, tenno sweep, &c.) using win_saddle_type = 0 single_mode_wins_saddle defines titles (classic triple crown, tenno sweep, &c.) using win_saddle_type = 0
race_instance is a combination of race, npc group, race date (month*100 + day), and time of day.
it isn't actually anything i care about.
which is to say, what i do care about is mapping races to each turn they're offered, and having a "race instance" enum like Hopeful-Stakes-Junior, Yasuda-Kinen-Classic, &c.
single_mode_program defines the race instances available for each turn, but the year is expressed a bit weirdly in the race_permission column:
- 1 = junior year
- 2 = classic year
- 3 = classic and senior year
- 4 = senior year
- 5 = ura finale
grade_rate_id appears to be consistently 800 iff maiden race and 900 iff debut race, but the values particularly for g1s are all over the place.
recommend_class_id appears to be consistently 1 iff maiden race or debut race, 2 iff pre-op, 3 iff op; but other values are confusing.
so, it doesn't seem like there's a particular flag that identifies maiden races, despite the restrictions on when they appear in the ui.
# trainee definitions # trainee definitions
- card_data has universal trainee stats: base skill set, stat growth bonuses ("talent"), default running style - card_data has universal trainee stats: base skill set, stat growth bonuses ("talent"), default running style
@@ -201,6 +217,68 @@ single_mode_wins_saddle defines titles (classic triple crown, tenno sweep, &c.)
- card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows - card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows
- single_mode_route_race is career goals (not only races) - single_mode_route_race is career goals (not only races)
# unrelated to everything # update diffs
try doober with E long, all-seeing eyes, gold recovery, and lots of stamina running in g3 diamond stakes senior year late february complete list of tables with inserts in both the 2026-01-15 update adding fine motion, manhattan cafe ssr, inari one sr and the 2026-01-22 update adding tamamo cross and main story 5:
- announce_character
- announce_data
- available_skill_set
- background_data
- banner_data
- campaign_chara_story_schedule
- campaign_data
- campaign_single_race_add_data
- campaign_single_race_add_reward
- card_data
- card_rarity_data
- card_talent_upgrade
- champions_news_chara_comment
- chara_category_motion
- character_system_lottery
- character_system_text
- chara_motion_set
- chara_story_data
- dress_data
- gacha_available
- gacha_data
- gacha_exchange
- gacha_top_bg
- home_story_trigger
- home_walk_group
- honor_data
- item_exchange
- item_place
- jukebox_chara_tag_data
- jukebox_comment
- jukebox_reaction_data
- mission_data
- nickname
- piece_data
- race
- race_bgm_cutin_extension_time
- race_instance
- race_jikkyo_base
- race_jikkyo_race
- race_jikkyo_trigger
- single_mode_chara_program
- single_mode_conclusion_set
- single_mode_event_production
- single_mode_hint_gain
- single_mode_npc
- single_mode_rival
- single_mode_route
- single_mode_route_race
- single_mode_scout_chara
- single_mode_skill_need_point
- single_mode_story_data
- single_mode_tag_card_pos
- skill_data
- skill_set
- succession_factor
- succession_factor_effect
- succession_relation_member
- support_card_data
- support_card_effect_table
- support_card_unique_effect
- text_data

8
generate.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
set -ex
go run ./horsegen "$@"
go generate ./horse/...
go fmt ./...
go test ./...

14
go.mod
View File

@@ -1,20 +1,30 @@
module git.sunturtle.xyz/zephyr/horse module git.sunturtle.xyz/zephyr/horse
go 1.24.1 go 1.25.5
require ( require (
github.com/disgoorg/disgo v0.19.0-rc.15
github.com/junegunn/fzf v0.67.0
golang.org/x/sync v0.14.0 golang.org/x/sync v0.14.0
zombiezen.com/go/sqlite v1.4.2 zombiezen.com/go/sqlite v1.4.2
) )
require ( require (
github.com/disgoorg/json/v2 v2.0.0 // indirect
github.com/disgoorg/omit v1.0.0 // indirect
github.com/disgoorg/snowflake/v2 v2.0.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.39.0 // indirect
modernc.org/libc v1.65.7 // indirect modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect

36
go.sum
View File

@@ -1,15 +1,41 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disgoorg/disgo v0.19.0-rc.15 h1:x0NsV2gcbdjwuztsg2wYXw76p1Cpc8f6ByDrkPcfQtU=
github.com/disgoorg/disgo v0.19.0-rc.15/go.mod h1:14mgXzenkJqifkDmsEgU0zI1di6jNXodwX6L8geW33A=
github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI=
github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI=
github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78=
github.com/disgoorg/omit v1.0.0/go.mod h1:RTmSARkf6PWT/UckwI0bV8XgWkWQoPppaT01rYKLcFQ=
github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzgvSI2Ro=
github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/junegunn/fzf v0.67.0 h1:naiOdIkV5/ZCfHgKQIV/f5YDWowl95G6yyOQqW8FeSo=
github.com/junegunn/fzf v0.67.0/go.mod h1:xlXX2/rmsccKQUnr9QOXPDi5DyV9cM0UjKy/huScBeE=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxEmTbaqt1hkJ/t6skqEGYiMag343ucI=
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
@@ -17,12 +43,14 @@ golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=

View File

@@ -9,29 +9,53 @@ func _() {
// Re-run the stringer command to generate them again. // Re-run the stringer command to generate them again.
var x [1]struct{} var x [1]struct{}
_ = x[ValueUsageDirect-1] _ = x[ValueUsageDirect-1]
_ = x[ValueUsageSkillCount-2]
_ = x[ValueUsageTeamSpeed-3] _ = x[ValueUsageTeamSpeed-3]
_ = x[ValueUsageTeamStamina-4] _ = x[ValueUsageTeamStamina-4]
_ = x[ValueUsageTeamPower-5] _ = x[ValueUsageTeamPower-5]
_ = x[ValueUsageTeamGuts-6] _ = x[ValueUsageTeamGuts-6]
_ = x[ValueUsageTeamWit-7] _ = x[ValueUsageTeamWit-7]
_ = x[ValueUsageRandom-8]
_ = x[ValueUsageRandom2-9]
_ = x[ValueUsageClimax-10]
_ = x[ValueUsageMaxStat-13]
_ = x[ValueUsageGreenCount-14]
_ = x[ValueUsageDistAdd-19]
_ = x[ValueUsageMidSideBlock-20]
_ = x[ValueUsageSpeed-22]
_ = x[ValueUsageSpeed2-23]
_ = x[ValueUsageArcPotential-24]
_ = x[ValueUsageMaxLead-25]
} }
const ( const (
_AbilityValueUsage_name_0 = "directly" _AbilityValueUsage_name_0 = "directlyscaling with the number of skillsscaling with team Speedscaling with team Staminascaling with team Powerscaling with team Gutsscaling with team Witwith a random 0× to 0.04× multiplierwith a random 0× to 0.04× multiplierscaling with the number of races won in training"
_AbilityValueUsage_name_1 = "scaling with team Speedscaling with team Staminascaling with team Powerscaling with team Gutsscaling with team Wit" _AbilityValueUsage_name_1 = "scaling with the highest raw statscaling with the number of Passive skills activated"
_AbilityValueUsage_name_2 = "plus extra when far from the leadscaling with mid-race phase blocked side time"
_AbilityValueUsage_name_3 = "scaling with overall speedscaling with overall speedscaling with L'Arc global potentialscaling with the longest lead obtained in the first ⅔"
) )
var ( var (
_AbilityValueUsage_index_1 = [...]uint8{0, 23, 48, 71, 93, 114} _AbilityValueUsage_index_0 = [...]uint16{0, 8, 41, 64, 89, 112, 134, 155, 193, 231, 279}
_AbilityValueUsage_index_1 = [...]uint8{0, 33, 84}
_AbilityValueUsage_index_2 = [...]uint8{0, 33, 78}
_AbilityValueUsage_index_3 = [...]uint8{0, 26, 52, 87, 142}
) )
func (i AbilityValueUsage) String() string { func (i AbilityValueUsage) String() string {
switch { switch {
case i == 1: case 1 <= i && i <= 10:
return _AbilityValueUsage_name_0 i -= 1
case 3 <= i && i <= 7: return _AbilityValueUsage_name_0[_AbilityValueUsage_index_0[i]:_AbilityValueUsage_index_0[i+1]]
i -= 3 case 13 <= i && i <= 14:
i -= 13
return _AbilityValueUsage_name_1[_AbilityValueUsage_index_1[i]:_AbilityValueUsage_index_1[i+1]] return _AbilityValueUsage_name_1[_AbilityValueUsage_index_1[i]:_AbilityValueUsage_index_1[i+1]]
case 19 <= i && i <= 20:
i -= 19
return _AbilityValueUsage_name_2[_AbilityValueUsage_index_2[i]:_AbilityValueUsage_index_2[i+1]]
case 22 <= i && i <= 25:
i -= 22
return _AbilityValueUsage_name_3[_AbilityValueUsage_index_3[i]:_AbilityValueUsage_index_3[i+1]]
default: default:
return "AbilityValueUsage(" + strconv.FormatInt(int64(i), 10) + ")" return "AbilityValueUsage(" + strconv.FormatInt(int64(i), 10) + ")"
} }

17
horse/character.kk Normal file
View File

@@ -0,0 +1,17 @@
module horse/character
import horse/game-id
pub struct character-detail
character-id: character-id
name: string
pub fun detail(
c: character-id,
?character/show: (character-id) -> string
): character-detail
Character-detail(c, c.show)
pub fun character-detail/show(d: character-detail): string
val Character-detail(Character-id(id), name) = d
name ++ " (ID " ++ id.show ++ ")"

63
horse/game-id.kk Normal file
View File

@@ -0,0 +1,63 @@
module horse/game-id
// Game ID for characters, cards, skills, races, &c.
// Values for different categories may overlap.
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.
// Game ID for characters.
// Generally numbers in the range 1000-9999.
pub struct character-id
game-id: game-id
// Game ID for trainees, i.e. costume instances of characters.
// Generally a character ID with two digits appended.
pub struct trainee-id
game-id: game-id
// Game ID for skills.
pub struct skill-id
game-id: game-id
// Game ID for skill groups.
pub struct skill-group-id
game-id: game-id
// Game ID for skill icons.
pub struct skill-icon-id
game-id: game-id
// Game ID for races,
// i.e. "Tenno Sho (Spring)" and not "Tenno Sho (Spring) at Kyoto Racecourse."
pub struct race-id
game-id: game-id
// Game ID for race thumbnails.
pub struct race-thumbnail-id
game-id: game-id
// Game ID for saddles,
// i.e. one or more race wins that appear as a title.
pub struct saddle-id
game-id: game-id
// order2 comparison between any game ID types.
pub inline fun order2(x: a, y: a, ?a/game-id: (a) -> game-id): order2<a>
match x.game-id.cmp(y.game-id)
Lt -> Lt2(x, y)
Eq -> Eq2(x)
Gt -> Gt2(x, y)
// Comparison between any game ID types.
pub inline fun cmp(x: a, y: a, ?a/game-id: (a) -> game-id): order
x.game-id.cmp(y.game-id)
// Equality between any game ID types.
pub inline fun (==)(x: a, y: a, ?a/game-id: (a) -> game-id): bool
x.game-id == y.game-id
// Check whether a game ID is valid, i.e. nonzero.
pub inline fun is-valid(x: a, ?a/game-id: (a) -> game-id): bool
x.game-id != 0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2014
horse/global/race.go Normal file

File diff suppressed because it is too large Load Diff

2052
horse/global/race.kk Normal file

File diff suppressed because it is too large Load Diff

1107
horse/global/saddle.go Normal file

File diff suppressed because it is too large Load Diff

985
horse/global/saddle.kk Normal file
View File

@@ -0,0 +1,985 @@
module horse/global/saddle
// Automatically generated with horsegen; DO NOT EDIT
import horse/game-id
pub import horse/race
pub import horse/global/race
// Enumeration of all saddles for type-safe programming.
pub type saddle
Classic-Triple-Crown
Senior-Autumn-Triple-Crown
Triple-Tiara
Senior-Spring-Triple-Crown
Tenno-Sweep
Dual-Grand-Prix
Dual-Miles
Dual-Sprints
Dual-Dirts
Arima-Kinen
Japan-C
Japanese-Derby
Tenno-Sho-Spring
Takarazuka-Kinen
Tenno-Sho-Autumn
Kikuka-Sho
Osaka-Hai
Satsuki-Sho
Japanese-Oaks
Takamatsunomiya-Kinen
Yasuda-Kinen
Sprinters-S
Mile-Ch
Oka-Sho
Victoria-Mile
Queen-Elizabeth-II-Cup
NHK-Mile-C
Shuka-Sho
Champions-C
February-S
JBC-Classic
Tokyo-Daishoten
Asahi-Hai-FS
Hopeful-S
Hanshin-JF
Teio-Sho
JBC-Sprint
JD-Derby
JBC-L-Classic
Nikkei-Shinshun-Hai
Tokai-S
American-JCC
Kyoto-Kinen
Nakayama-Kinen
Yayoi-Sho
Kinko-Sho
Fillies-Revue
Hanshin-Daishoten
Spring-S
Nikkei-Sho
Hanshin-Umamusume-S
New-Zealand-T
Yomiuri-Milers-C
Flora-S
Aoba-Sho
Kyoto-Shimbun-Hai
Keio-Hai-Spring-C
Meguro-Kinen
Sapporo-Kinen
Centaur-S
Rose-S
St-Lite-Kinen
Kobe-Shimbun-Hai
All-Comers
Mainichi-Okan
Kyoto-Daishoten
Fuchu-Umamusume-S
Swan-S
Keio-Hai-Junior-S
Copa-Republica-Argentina
Daily-Hai-Junior-S
Stayers-S
Hanshin-C
Kyoto-Kimpai
Nakayama-Kimpai
Shinzan-Kinen
Fairy-S
Aichi-Hai
Keisei-Hai
Silk-Road-S
Negishi-S
Kisaragi-Sho
Tokyo-Shimbun-Hai
Queen-C
Kyodo-News-Hai
Kyoto-Umamusume-S
Diamond-S
Kokura-Daishoten
Arlington-C
Hankyu-Hai
Tulip-Sho
Ocean-S
Nakayama-Umamusume-S
Falcon-S
Flower-C
Mainichi-Hai
March-S
Lord-Derby-CT
Antares-S
Fukushima-Umamusume-S
Niigata-Daishoten
Heian-S
Naruo-Kinen
Mermaid-S
Epsom-C
Unicorn-S
Hakodate-Sprint-S
CBC-Sho
Radio-Nikkei-Sho
Procyon-S
Tanabata-Sho
Hakodate-Kinen
Chukyo-Kinen
Hakodate-Junior-S
Ibis-Summer-D
Queen-S
Kokura-Kinen
Leopard-S
Sekiya-Kinen
Elm-S
Kitakyushu-Kinen
Niigata-Junior-S
Keeneland-C
Sapporo-Junior-S
Kokura-Junior-S
Niigata-Kinen
Shion-S
Keisei-Hai-AH
Sirius-S
Saudi-Arabia-RC
Fuji-S
Artemis-S
Fantasy-S
Miyako-S
Musashino-S
Fukushima-Kinen
Tokyo-Sports-Hai-Junior-S
Kyoto-Junior-S
Keihan-Hai
Challenge-C
Chunichi-Shimbun-Hai
Capella-S
Turquoise-S
Classic-Triple-Crown-Alt144
Senior-Spring-Triple-Crown-Alt145
Dual-Grand-Prix-Alt146
Takarazuka-Kinen-Alt147
Kikuka-Sho-Alt148
Spring-S-Alt149
Aoi-S
Senior-Spring-Triple-Crown-Alt151
Tenno-Sweep-Alt152
Tenno-Sho-Spring-Alt153
Classic-Triple-Crown-Alt154
Satsuki-Sho-Alt155
// Get the saddle ID for a saddle.
pub fun saddle-id(s: saddle): saddle-id
match s
Classic-Triple-Crown -> Saddle-id(1)
Senior-Autumn-Triple-Crown -> Saddle-id(2)
Triple-Tiara -> Saddle-id(3)
Senior-Spring-Triple-Crown -> Saddle-id(4)
Tenno-Sweep -> Saddle-id(5)
Dual-Grand-Prix -> Saddle-id(6)
Dual-Miles -> Saddle-id(7)
Dual-Sprints -> Saddle-id(8)
Dual-Dirts -> Saddle-id(9)
Arima-Kinen -> Saddle-id(10)
Japan-C -> Saddle-id(11)
Japanese-Derby -> Saddle-id(12)
Tenno-Sho-Spring -> Saddle-id(13)
Takarazuka-Kinen -> Saddle-id(14)
Tenno-Sho-Autumn -> Saddle-id(15)
Kikuka-Sho -> Saddle-id(16)
Osaka-Hai -> Saddle-id(17)
Satsuki-Sho -> Saddle-id(18)
Japanese-Oaks -> Saddle-id(19)
Takamatsunomiya-Kinen -> Saddle-id(20)
Yasuda-Kinen -> Saddle-id(21)
Sprinters-S -> Saddle-id(22)
Mile-Ch -> Saddle-id(23)
Oka-Sho -> Saddle-id(24)
Victoria-Mile -> Saddle-id(25)
Queen-Elizabeth-II-Cup -> Saddle-id(26)
NHK-Mile-C -> Saddle-id(27)
Shuka-Sho -> Saddle-id(28)
Champions-C -> Saddle-id(29)
February-S -> Saddle-id(30)
JBC-Classic -> Saddle-id(31)
Tokyo-Daishoten -> Saddle-id(32)
Asahi-Hai-FS -> Saddle-id(33)
Hopeful-S -> Saddle-id(34)
Hanshin-JF -> Saddle-id(35)
Teio-Sho -> Saddle-id(36)
JBC-Sprint -> Saddle-id(37)
JD-Derby -> Saddle-id(38)
JBC-L-Classic -> Saddle-id(39)
Nikkei-Shinshun-Hai -> Saddle-id(40)
Tokai-S -> Saddle-id(41)
American-JCC -> Saddle-id(42)
Kyoto-Kinen -> Saddle-id(43)
Nakayama-Kinen -> Saddle-id(44)
Yayoi-Sho -> Saddle-id(45)
Kinko-Sho -> Saddle-id(46)
Fillies-Revue -> Saddle-id(47)
Hanshin-Daishoten -> Saddle-id(48)
Spring-S -> Saddle-id(49)
Nikkei-Sho -> Saddle-id(50)
Hanshin-Umamusume-S -> Saddle-id(51)
New-Zealand-T -> Saddle-id(52)
Yomiuri-Milers-C -> Saddle-id(53)
Flora-S -> Saddle-id(54)
Aoba-Sho -> Saddle-id(55)
Kyoto-Shimbun-Hai -> Saddle-id(56)
Keio-Hai-Spring-C -> Saddle-id(57)
Meguro-Kinen -> Saddle-id(58)
Sapporo-Kinen -> Saddle-id(59)
Centaur-S -> Saddle-id(60)
Rose-S -> Saddle-id(61)
St-Lite-Kinen -> Saddle-id(62)
Kobe-Shimbun-Hai -> Saddle-id(63)
All-Comers -> Saddle-id(64)
Mainichi-Okan -> Saddle-id(65)
Kyoto-Daishoten -> Saddle-id(66)
Fuchu-Umamusume-S -> Saddle-id(67)
Swan-S -> Saddle-id(68)
Keio-Hai-Junior-S -> Saddle-id(69)
Copa-Republica-Argentina -> Saddle-id(70)
Daily-Hai-Junior-S -> Saddle-id(71)
Stayers-S -> Saddle-id(72)
Hanshin-C -> Saddle-id(73)
Kyoto-Kimpai -> Saddle-id(74)
Nakayama-Kimpai -> Saddle-id(75)
Shinzan-Kinen -> Saddle-id(76)
Fairy-S -> Saddle-id(77)
Aichi-Hai -> Saddle-id(78)
Keisei-Hai -> Saddle-id(79)
Silk-Road-S -> Saddle-id(80)
Negishi-S -> Saddle-id(81)
Kisaragi-Sho -> Saddle-id(82)
Tokyo-Shimbun-Hai -> Saddle-id(83)
Queen-C -> Saddle-id(84)
Kyodo-News-Hai -> Saddle-id(85)
Kyoto-Umamusume-S -> Saddle-id(86)
Diamond-S -> Saddle-id(87)
Kokura-Daishoten -> Saddle-id(88)
Arlington-C -> Saddle-id(89)
Hankyu-Hai -> Saddle-id(90)
Tulip-Sho -> Saddle-id(91)
Ocean-S -> Saddle-id(92)
Nakayama-Umamusume-S -> Saddle-id(93)
Falcon-S -> Saddle-id(94)
Flower-C -> Saddle-id(95)
Mainichi-Hai -> Saddle-id(96)
March-S -> Saddle-id(97)
Lord-Derby-CT -> Saddle-id(98)
Antares-S -> Saddle-id(99)
Fukushima-Umamusume-S -> Saddle-id(100)
Niigata-Daishoten -> Saddle-id(101)
Heian-S -> Saddle-id(102)
Naruo-Kinen -> Saddle-id(103)
Mermaid-S -> Saddle-id(104)
Epsom-C -> Saddle-id(105)
Unicorn-S -> Saddle-id(106)
Hakodate-Sprint-S -> Saddle-id(107)
CBC-Sho -> Saddle-id(108)
Radio-Nikkei-Sho -> Saddle-id(109)
Procyon-S -> Saddle-id(110)
Tanabata-Sho -> Saddle-id(111)
Hakodate-Kinen -> Saddle-id(112)
Chukyo-Kinen -> Saddle-id(113)
Hakodate-Junior-S -> Saddle-id(114)
Ibis-Summer-D -> Saddle-id(115)
Queen-S -> Saddle-id(116)
Kokura-Kinen -> Saddle-id(117)
Leopard-S -> Saddle-id(118)
Sekiya-Kinen -> Saddle-id(119)
Elm-S -> Saddle-id(120)
Kitakyushu-Kinen -> Saddle-id(121)
Niigata-Junior-S -> Saddle-id(122)
Keeneland-C -> Saddle-id(123)
Sapporo-Junior-S -> Saddle-id(124)
Kokura-Junior-S -> Saddle-id(125)
Niigata-Kinen -> Saddle-id(126)
Shion-S -> Saddle-id(127)
Keisei-Hai-AH -> Saddle-id(128)
Sirius-S -> Saddle-id(129)
Saudi-Arabia-RC -> Saddle-id(130)
Fuji-S -> Saddle-id(131)
Artemis-S -> Saddle-id(132)
Fantasy-S -> Saddle-id(133)
Miyako-S -> Saddle-id(134)
Musashino-S -> Saddle-id(135)
Fukushima-Kinen -> Saddle-id(136)
Tokyo-Sports-Hai-Junior-S -> Saddle-id(137)
Kyoto-Junior-S -> Saddle-id(138)
Keihan-Hai -> Saddle-id(139)
Challenge-C -> Saddle-id(140)
Chunichi-Shimbun-Hai -> Saddle-id(141)
Capella-S -> Saddle-id(142)
Turquoise-S -> Saddle-id(143)
Classic-Triple-Crown-Alt144 -> Saddle-id(144)
Senior-Spring-Triple-Crown-Alt145 -> Saddle-id(145)
Dual-Grand-Prix-Alt146 -> Saddle-id(146)
Takarazuka-Kinen-Alt147 -> Saddle-id(147)
Kikuka-Sho-Alt148 -> Saddle-id(148)
Spring-S-Alt149 -> Saddle-id(149)
Aoi-S -> Saddle-id(150)
Senior-Spring-Triple-Crown-Alt151 -> Saddle-id(151)
Tenno-Sweep-Alt152 -> Saddle-id(152)
Tenno-Sho-Spring-Alt153 -> Saddle-id(153)
Classic-Triple-Crown-Alt154 -> Saddle-id(154)
Satsuki-Sho-Alt155 -> Saddle-id(155)
// List of all saddles in ID order for easy iterating.
pub val all = [
Classic-Triple-Crown,
Senior-Autumn-Triple-Crown,
Triple-Tiara,
Senior-Spring-Triple-Crown,
Tenno-Sweep,
Dual-Grand-Prix,
Dual-Miles,
Dual-Sprints,
Dual-Dirts,
Arima-Kinen,
Japan-C,
Japanese-Derby,
Tenno-Sho-Spring,
Takarazuka-Kinen,
Tenno-Sho-Autumn,
Kikuka-Sho,
Osaka-Hai,
Satsuki-Sho,
Japanese-Oaks,
Takamatsunomiya-Kinen,
Yasuda-Kinen,
Sprinters-S,
Mile-Ch,
Oka-Sho,
Victoria-Mile,
Queen-Elizabeth-II-Cup,
NHK-Mile-C,
Shuka-Sho,
Champions-C,
February-S,
JBC-Classic,
Tokyo-Daishoten,
Asahi-Hai-FS,
Hopeful-S,
Hanshin-JF,
Teio-Sho,
JBC-Sprint,
JD-Derby,
JBC-L-Classic,
Nikkei-Shinshun-Hai,
Tokai-S,
American-JCC,
Kyoto-Kinen,
Nakayama-Kinen,
Yayoi-Sho,
Kinko-Sho,
Fillies-Revue,
Hanshin-Daishoten,
Spring-S,
Nikkei-Sho,
Hanshin-Umamusume-S,
New-Zealand-T,
Yomiuri-Milers-C,
Flora-S,
Aoba-Sho,
Kyoto-Shimbun-Hai,
Keio-Hai-Spring-C,
Meguro-Kinen,
Sapporo-Kinen,
Centaur-S,
Rose-S,
St-Lite-Kinen,
Kobe-Shimbun-Hai,
All-Comers,
Mainichi-Okan,
Kyoto-Daishoten,
Fuchu-Umamusume-S,
Swan-S,
Keio-Hai-Junior-S,
Copa-Republica-Argentina,
Daily-Hai-Junior-S,
Stayers-S,
Hanshin-C,
Kyoto-Kimpai,
Nakayama-Kimpai,
Shinzan-Kinen,
Fairy-S,
Aichi-Hai,
Keisei-Hai,
Silk-Road-S,
Negishi-S,
Kisaragi-Sho,
Tokyo-Shimbun-Hai,
Queen-C,
Kyodo-News-Hai,
Kyoto-Umamusume-S,
Diamond-S,
Kokura-Daishoten,
Arlington-C,
Hankyu-Hai,
Tulip-Sho,
Ocean-S,
Nakayama-Umamusume-S,
Falcon-S,
Flower-C,
Mainichi-Hai,
March-S,
Lord-Derby-CT,
Antares-S,
Fukushima-Umamusume-S,
Niigata-Daishoten,
Heian-S,
Naruo-Kinen,
Mermaid-S,
Epsom-C,
Unicorn-S,
Hakodate-Sprint-S,
CBC-Sho,
Radio-Nikkei-Sho,
Procyon-S,
Tanabata-Sho,
Hakodate-Kinen,
Chukyo-Kinen,
Hakodate-Junior-S,
Ibis-Summer-D,
Queen-S,
Kokura-Kinen,
Leopard-S,
Sekiya-Kinen,
Elm-S,
Kitakyushu-Kinen,
Niigata-Junior-S,
Keeneland-C,
Sapporo-Junior-S,
Kokura-Junior-S,
Niigata-Kinen,
Shion-S,
Keisei-Hai-AH,
Sirius-S,
Saudi-Arabia-RC,
Fuji-S,
Artemis-S,
Fantasy-S,
Miyako-S,
Musashino-S,
Fukushima-Kinen,
Tokyo-Sports-Hai-Junior-S,
Kyoto-Junior-S,
Keihan-Hai,
Challenge-C,
Chunichi-Shimbun-Hai,
Capella-S,
Turquoise-S,
Classic-Triple-Crown-Alt144,
Senior-Spring-Triple-Crown-Alt145,
Dual-Grand-Prix-Alt146,
Takarazuka-Kinen-Alt147,
Kikuka-Sho-Alt148,
Spring-S-Alt149,
Aoi-S,
Senior-Spring-Triple-Crown-Alt151,
Tenno-Sweep-Alt152,
Tenno-Sho-Spring-Alt153,
Classic-Triple-Crown-Alt154,
Satsuki-Sho-Alt155,
]
// Get the name for a saddle.
// Alternate versions of saddles have an indication of their ID in their names.
// If no saddle matches the ID, the result contains the numeric ID.
pub fun show(s: saddle-id): string
match s.game-id
1 -> "Classic Triple Crown"
2 -> "Senior Autumn Triple Crown"
3 -> "Triple Tiara"
4 -> "Senior Spring Triple Crown"
5 -> "Tenno Sweep"
6 -> "Dual Grand Prix"
7 -> "Dual Miles"
8 -> "Dual Sprints"
9 -> "Dual Dirts"
10 -> "Arima Kinen"
11 -> "Japan C."
12 -> "Japanese Derby"
13 -> "Tenno Sho (Spring)"
14 -> "Takarazuka Kinen"
15 -> "Tenno Sho (Autumn)"
16 -> "Kikuka Sho"
17 -> "Osaka Hai"
18 -> "Satsuki Sho"
19 -> "Japanese Oaks"
20 -> "Takamatsunomiya Kinen"
21 -> "Yasuda Kinen"
22 -> "Sprinters S."
23 -> "Mile Ch."
24 -> "Oka Sho"
25 -> "Victoria Mile"
26 -> "Queen Elizabeth II Cup"
27 -> "NHK Mile C."
28 -> "Shuka Sho"
29 -> "Champions C."
30 -> "February S."
31 -> "JBC Classic"
32 -> "Tokyo Daishoten"
33 -> "Asahi Hai F.S."
34 -> "Hopeful S."
35 -> "Hanshin J.F."
36 -> "Teio Sho"
37 -> "JBC Sprint"
38 -> "J.D. Derby"
39 -> "JBC L. Classic"
40 -> "Nikkei Shinshun Hai"
41 -> "Tokai S."
42 -> "American JCC"
43 -> "Kyoto Kinen"
44 -> "Nakayama Kinen"
45 -> "Yayoi Sho"
46 -> "Kinko Sho"
47 -> "Fillies' Revue"
48 -> "Hanshin Daishoten"
49 -> "Spring S."
50 -> "Nikkei Sho"
51 -> "Hanshin Umamusume S."
52 -> "New Zealand T."
53 -> "Yomiuri Milers C."
54 -> "Flora S."
55 -> "Aoba Sho"
56 -> "Kyoto Shimbun Hai"
57 -> "Keio Hai Spring C."
58 -> "Meguro Kinen"
59 -> "Sapporo Kinen"
60 -> "Centaur S."
61 -> "Rose S."
62 -> "St. Lite Kinen"
63 -> "Kobe Shimbun Hai"
64 -> "All Comers"
65 -> "Mainichi Okan"
66 -> "Kyoto Daishoten"
67 -> "Fuchu Umamusume S."
68 -> "Swan S."
69 -> "Keio Hai Junior S."
70 -> "Copa Republica Argentina"
71 -> "Daily Hai Junior S."
72 -> "Stayers S."
73 -> "Hanshin C."
74 -> "Kyoto Kimpai"
75 -> "Nakayama Kimpai"
76 -> "Shinzan Kinen"
77 -> "Fairy S."
78 -> "Aichi Hai"
79 -> "Keisei Hai"
80 -> "Silk Road S."
81 -> "Negishi S."
82 -> "Kisaragi Sho"
83 -> "Tokyo Shimbun Hai"
84 -> "Queen C."
85 -> "Kyodo News Hai"
86 -> "Kyoto Umamusume S."
87 -> "Diamond S."
88 -> "Kokura Daishoten"
89 -> "Arlington C."
90 -> "Hankyu Hai"
91 -> "Tulip Sho"
92 -> "Ocean S."
93 -> "Nakayama Umamusume S."
94 -> "Falcon S."
95 -> "Flower C."
96 -> "Mainichi Hai"
97 -> "March S."
98 -> "Lord Derby C.T."
99 -> "Antares S."
100 -> "Fukushima Umamusume S."
101 -> "Niigata Daishoten"
102 -> "Heian S."
103 -> "Naruo Kinen"
104 -> "Mermaid S."
105 -> "Epsom C."
106 -> "Unicorn S."
107 -> "Hakodate Sprint S."
108 -> "CBC Sho"
109 -> "Radio Nikkei Sho"
110 -> "Procyon S."
111 -> "Tanabata Sho"
112 -> "Hakodate Kinen"
113 -> "Chukyo Kinen"
114 -> "Hakodate Junior S."
115 -> "Ibis Summer D."
116 -> "Queen S."
117 -> "Kokura Kinen"
118 -> "Leopard S."
119 -> "Sekiya Kinen"
120 -> "Elm S."
121 -> "Kitakyushu Kinen"
122 -> "Niigata Junior S."
123 -> "Keeneland C."
124 -> "Sapporo Junior S."
125 -> "Kokura Junior S."
126 -> "Niigata Kinen"
127 -> "Shion S."
128 -> "Keisei Hai A.H."
129 -> "Sirius S."
130 -> "Saudi Arabia R.C."
131 -> "Fuji S."
132 -> "Artemis S."
133 -> "Fantasy S."
134 -> "Miyako S."
135 -> "Musashino S."
136 -> "Fukushima Kinen"
137 -> "Tokyo Sports Hai Junior S."
138 -> "Kyoto Junior S."
139 -> "Keihan Hai"
140 -> "Challenge C."
141 -> "Chunichi Shimbun Hai"
142 -> "Capella S."
143 -> "Turquoise S."
144 -> "Classic Triple Crown" ++ " (Alternate 144)"
145 -> "Senior Spring Triple Crown" ++ " (Alternate 145)"
146 -> "Dual Grand Prix" ++ " (Alternate 146)"
147 -> "Takarazuka Kinen" ++ " (Alternate 147)"
148 -> "Kikuka Sho" ++ " (Alternate 148)"
149 -> "Spring S." ++ " (Alternate 149)"
150 -> "Aoi S."
151 -> "Senior Spring Triple Crown" ++ " (Alternate 151)"
152 -> "Tenno Sweep" ++ " (Alternate 152)"
153 -> "Tenno Sho (Spring)" ++ " (Alternate 153)"
154 -> "Classic Triple Crown" ++ " (Alternate 154)"
155 -> "Satsuki Sho" ++ " (Alternate 155)"
x -> "saddle " ++ x.show
// Get the list of races that entitle a horse to a saddle.
// If no saddle matches the ID, the result is the empty list.
pub fun races(s: saddle-id): list<race-id>
match s.game-id
1 -> [Race-id(100501), Race-id(101001), Race-id(101501), ]
2 -> [Race-id(101601), Race-id(101901), Race-id(102301), ]
3 -> [Race-id(100401), Race-id(100901), Race-id(101401), ]
4 -> [Race-id(100301), Race-id(100601), Race-id(101201), ]
5 -> [Race-id(100601), Race-id(101601), ]
6 -> [Race-id(101201), Race-id(102301), ]
7 -> [Race-id(101101), Race-id(101801), ]
8 -> [Race-id(101301), Race-id(100201), ]
9 -> [Race-id(100101), Race-id(102001), ]
10 -> [Race-id(102301), ]
11 -> [Race-id(101901), ]
12 -> [Race-id(101001), ]
13 -> [Race-id(100601), ]
14 -> [Race-id(101201), ]
15 -> [Race-id(101601), ]
16 -> [Race-id(101501), ]
17 -> [Race-id(100301), ]
18 -> [Race-id(100501), ]
19 -> [Race-id(100901), ]
20 -> [Race-id(100201), ]
21 -> [Race-id(101101), ]
22 -> [Race-id(101301), ]
23 -> [Race-id(101801), ]
24 -> [Race-id(100401), ]
25 -> [Race-id(100801), ]
26 -> [Race-id(101701), ]
27 -> [Race-id(100701), ]
28 -> [Race-id(101401), ]
29 -> [Race-id(102001), ]
30 -> [Race-id(100101), ]
31 -> [Race-id(110501), ]
32 -> [Race-id(110601), ]
33 -> [Race-id(102201), ]
34 -> [Race-id(102401), ]
35 -> [Race-id(102101), ]
36 -> [Race-id(110101), ]
37 -> [Race-id(110401), ]
38 -> [Race-id(110201), ]
39 -> [Race-id(110301), ]
40 -> [Race-id(200101), ]
41 -> [Race-id(200201), ]
42 -> [Race-id(200301), ]
43 -> [Race-id(200401), ]
44 -> [Race-id(200501), ]
45 -> [Race-id(200601), ]
46 -> [Race-id(200701), ]
47 -> [Race-id(200801), ]
48 -> [Race-id(200901), ]
49 -> [Race-id(201001), ]
50 -> [Race-id(201101), ]
51 -> [Race-id(201201), ]
52 -> [Race-id(201301), ]
53 -> [Race-id(201401), ]
54 -> [Race-id(201501), ]
55 -> [Race-id(201601), ]
56 -> [Race-id(201701), ]
57 -> [Race-id(201801), ]
58 -> [Race-id(201901), ]
59 -> [Race-id(202001), ]
60 -> [Race-id(202101), ]
61 -> [Race-id(202201), ]
62 -> [Race-id(202301), ]
63 -> [Race-id(202401), ]
64 -> [Race-id(202501), ]
65 -> [Race-id(202601), ]
66 -> [Race-id(202701), ]
67 -> [Race-id(202801), ]
68 -> [Race-id(202901), ]
69 -> [Race-id(203001), ]
70 -> [Race-id(203101), ]
71 -> [Race-id(203201), ]
72 -> [Race-id(203301), ]
73 -> [Race-id(203401), ]
74 -> [Race-id(300101), ]
75 -> [Race-id(300201), ]
76 -> [Race-id(300301), ]
77 -> [Race-id(300401), ]
78 -> [Race-id(300501), ]
79 -> [Race-id(300601), ]
80 -> [Race-id(300701), ]
81 -> [Race-id(300801), ]
82 -> [Race-id(300901), ]
83 -> [Race-id(301001), ]
84 -> [Race-id(301101), ]
85 -> [Race-id(301201), ]
86 -> [Race-id(301301), ]
87 -> [Race-id(301401), ]
88 -> [Race-id(301501), ]
89 -> [Race-id(301601), ]
90 -> [Race-id(301701), ]
91 -> [Race-id(301801), ]
92 -> [Race-id(301901), ]
93 -> [Race-id(302001), ]
94 -> [Race-id(302101), ]
95 -> [Race-id(302201), ]
96 -> [Race-id(302301), ]
97 -> [Race-id(302401), ]
98 -> [Race-id(302501), ]
99 -> [Race-id(302601), ]
100 -> [Race-id(302701), ]
101 -> [Race-id(302801), ]
102 -> [Race-id(302901), ]
103 -> [Race-id(303001), ]
104 -> [Race-id(303101), ]
105 -> [Race-id(303201), ]
106 -> [Race-id(303301), ]
107 -> [Race-id(303401), ]
108 -> [Race-id(303501), ]
109 -> [Race-id(303601), ]
110 -> [Race-id(303701), ]
111 -> [Race-id(303801), ]
112 -> [Race-id(303901), ]
113 -> [Race-id(304001), ]
114 -> [Race-id(304101), ]
115 -> [Race-id(304201), ]
116 -> [Race-id(304301), ]
117 -> [Race-id(304401), ]
118 -> [Race-id(304501), ]
119 -> [Race-id(304601), ]
120 -> [Race-id(304701), ]
121 -> [Race-id(304801), ]
122 -> [Race-id(304901), ]
123 -> [Race-id(305001), ]
124 -> [Race-id(305101), ]
125 -> [Race-id(305201), ]
126 -> [Race-id(305301), ]
127 -> [Race-id(305401), ]
128 -> [Race-id(305501), ]
129 -> [Race-id(305601), ]
130 -> [Race-id(305701), ]
131 -> [Race-id(305801), ]
132 -> [Race-id(305901), ]
133 -> [Race-id(306001), ]
134 -> [Race-id(306101), ]
135 -> [Race-id(306201), ]
136 -> [Race-id(306301), ]
137 -> [Race-id(306401), ]
138 -> [Race-id(306501), ]
139 -> [Race-id(306601), ]
140 -> [Race-id(306701), ]
141 -> [Race-id(306801), ]
142 -> [Race-id(306901), ]
143 -> [Race-id(307001), ]
144 -> [Race-id(100501), Race-id(101001), Race-id(102601), ]
145 -> [Race-id(100301), Race-id(100601), Race-id(102501), ]
146 -> [Race-id(102501), Race-id(102301), ]
147 -> [Race-id(102501), ]
148 -> [Race-id(102601), ]
149 -> [Race-id(203501), ]
150 -> [Race-id(405001), ]
151 -> [Race-id(100301), Race-id(102701), Race-id(101201), ]
152 -> [Race-id(102701), Race-id(101601), ]
153 -> [Race-id(102701), ]
154 -> [Race-id(102801), Race-id(101001), Race-id(101501), ]
155 -> [Race-id(102801), ]
_ -> []
// Get a saddle's type.
// If no saddle matches the ID, the result is Honor.
pub fun saddle-type(s: saddle-id): saddle-type
match s.game-id
1 -> Honor
2 -> Honor
3 -> Honor
4 -> Honor
5 -> Honor
6 -> Honor
7 -> Honor
8 -> Honor
9 -> Honor
10 -> G1-Win
11 -> G1-Win
12 -> G1-Win
13 -> G1-Win
14 -> G1-Win
15 -> G1-Win
16 -> G1-Win
17 -> G1-Win
18 -> G1-Win
19 -> G1-Win
20 -> G1-Win
21 -> G1-Win
22 -> G1-Win
23 -> G1-Win
24 -> G1-Win
25 -> G1-Win
26 -> G1-Win
27 -> G1-Win
28 -> G1-Win
29 -> G1-Win
30 -> G1-Win
31 -> G1-Win
32 -> G1-Win
33 -> G1-Win
34 -> G1-Win
35 -> G1-Win
36 -> G1-Win
37 -> G1-Win
38 -> G1-Win
39 -> G1-Win
40 -> G2-Win
41 -> G2-Win
42 -> G2-Win
43 -> G2-Win
44 -> G2-Win
45 -> G2-Win
46 -> G2-Win
47 -> G2-Win
48 -> G2-Win
49 -> G2-Win
50 -> G2-Win
51 -> G2-Win
52 -> G2-Win
53 -> G2-Win
54 -> G2-Win
55 -> G2-Win
56 -> G2-Win
57 -> G2-Win
58 -> G2-Win
59 -> G2-Win
60 -> G2-Win
61 -> G2-Win
62 -> G2-Win
63 -> G2-Win
64 -> G2-Win
65 -> G2-Win
66 -> G2-Win
67 -> G2-Win
68 -> G2-Win
69 -> G2-Win
70 -> G2-Win
71 -> G2-Win
72 -> G2-Win
73 -> G2-Win
74 -> G3-Win
75 -> G3-Win
76 -> G3-Win
77 -> G3-Win
78 -> G3-Win
79 -> G3-Win
80 -> G3-Win
81 -> G3-Win
82 -> G3-Win
83 -> G3-Win
84 -> G3-Win
85 -> G3-Win
86 -> G3-Win
87 -> G3-Win
88 -> G3-Win
89 -> G3-Win
90 -> G3-Win
91 -> G2-Win
92 -> G3-Win
93 -> G3-Win
94 -> G3-Win
95 -> G3-Win
96 -> G3-Win
97 -> G3-Win
98 -> G3-Win
99 -> G3-Win
100 -> G3-Win
101 -> G3-Win
102 -> G3-Win
103 -> G3-Win
104 -> G3-Win
105 -> G3-Win
106 -> G3-Win
107 -> G3-Win
108 -> G3-Win
109 -> G3-Win
110 -> G3-Win
111 -> G3-Win
112 -> G3-Win
113 -> G3-Win
114 -> G3-Win
115 -> G3-Win
116 -> G3-Win
117 -> G3-Win
118 -> G3-Win
119 -> G3-Win
120 -> G3-Win
121 -> G3-Win
122 -> G3-Win
123 -> G3-Win
124 -> G3-Win
125 -> G3-Win
126 -> G3-Win
127 -> G3-Win
128 -> G3-Win
129 -> G3-Win
130 -> G3-Win
131 -> G2-Win
132 -> G3-Win
133 -> G3-Win
134 -> G3-Win
135 -> G3-Win
136 -> G3-Win
137 -> G3-Win
138 -> G3-Win
139 -> G3-Win
140 -> G3-Win
141 -> G3-Win
142 -> G3-Win
143 -> G3-Win
144 -> Honor
145 -> Honor
146 -> Honor
147 -> G1-Win
148 -> G1-Win
149 -> G2-Win
150 -> G3-Win
151 -> Honor
152 -> Honor
153 -> G1-Win
154 -> Honor
155 -> G1-Win
_ -> Honor
// Get the primary ID for a saddle.
// For saddles which are the primary version, or if no saddle matches the given ID,
// the result is the input.
pub fun primary(s: saddle-id): saddle-id
match s.game-id
144 -> Saddle-id(1)
145 -> Saddle-id(4)
146 -> Saddle-id(6)
147 -> Saddle-id(14)
148 -> Saddle-id(16)
149 -> Saddle-id(49)
151 -> Saddle-id(4)
152 -> Saddle-id(5)
153 -> Saddle-id(13)
154 -> Saddle-id(1)
155 -> Saddle-id(18)
_ -> s

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
module horse/legacy module horse/legacy
import horse/character import horse/character
import horse/game-id
import horse/race import horse/race
import horse/spark import horse/spark
@@ -9,9 +10,9 @@ pub struct legacy
parents: (veteran, veteran) parents: (veteran, veteran)
pub struct veteran pub struct veteran
character: character character: character-id
stat: spark<stat> stat: spark<stat>
aptitude: spark<aptitude> aptitude: spark<aptitude>
unique: maybe<spark<unique>> unique: maybe<spark<unique>>
generic: list<spark<generic>> generic: list<spark<generic>>
results: list<race-result> saddles: list<saddle-id>

104
horse/movement.kk Normal file
View File

@@ -0,0 +1,104 @@
module horse/movement
// Running styles.
pub type style
Front-Runner
Pace-Chaser
Late-Surger
End-Closer
// Automatically generated.
// Equality comparison of the `style` type.
pub fun style/(==)(this : style, other : style) : e bool
match (this, other)
(Front-Runner, Front-Runner) -> True
(Pace-Chaser, Pace-Chaser) -> True
(Late-Surger, Late-Surger) -> True
(End-Closer, End-Closer) -> True
(_, _) -> False
// Shows a string representation of the `style` type.
pub fun style/show(this : style) : e string
match this
Front-Runner -> "Front Runner"
Pace-Chaser -> "Pace Chaser"
Late-Surger -> "Late Surger"
End-Closer -> "End Closer"
// Starting aptitude levels.
pub type level
G
F
E
D
C
B
A
S
// Automatically generated.
// Comparison of the `level` type.
pub fun level/cmp(this : level, other : level) : e order
match (this, other)
(G, G) -> Eq
(G, _) -> Lt
(_, G) -> Gt
(F, F) -> Eq
(F, _) -> Lt
(_, F) -> Gt
(E, E) -> Eq
(E, _) -> Lt
(_, E) -> Gt
(D, D) -> Eq
(D, _) -> Lt
(_, D) -> Gt
(C, C) -> Eq
(C, _) -> Lt
(_, C) -> Gt
(B, B) -> Eq
(B, _) -> Lt
(_, B) -> Gt
(A, A) -> Eq
(A, _) -> Lt
(_, A) -> Gt
(S, S) -> Eq
// Automatically generated.
// Fip comparison of the `level` type.
pub fun level/order2(this : level, other : level) : order2<level>
match (this, other)
(G, G) -> Eq2(G)
(G, other') -> Lt2(G, other')
(this', G) -> Gt2(G, this')
(F, F) -> Eq2(F)
(F, other') -> Lt2(F, other')
(this', F) -> Gt2(F, this')
(E, E) -> Eq2(E)
(E, other') -> Lt2(E, other')
(this', E) -> Gt2(E, this')
(D, D) -> Eq2(D)
(D, other') -> Lt2(D, other')
(this', D) -> Gt2(D, this')
(C, C) -> Eq2(C)
(C, other') -> Lt2(C, other')
(this', C) -> Gt2(C, this')
(B, B) -> Eq2(B)
(B, other') -> Lt2(B, other')
(this', B) -> Gt2(B, this')
(A, A) -> Eq2(A)
(A, other') -> Lt2(A, other')
(this', A) -> Gt2(A, this')
(S, S) -> Eq2(S)
// Automatically generated.
// Shows a string representation of the `level` type.
pub fun level/show(this : level) : string
match this
G -> "G"
F -> "F"
E -> "E"
D -> "D"
C -> "C"
B -> "B"
A -> "A"
S -> "S"

158
horse/prob/kfl.kk Normal file
View File

@@ -0,0 +1,158 @@
module horse/prob/kfl
// kfl is a semiring of probabilities formed by vibes.
pub type kfl
// Effectively if not literally impossible events.
Impossible
// Not worth aiming for, but can technically still happen.
Probably-Not
// You expect it not to happen most of the time, but it might still be worth
// trying for it if you're being forced to play to your outs.
Doubtful
// More likely that it won't happen, but a success isn't surprising.
Unlikely
// Either it does or it doesn't.
Mayhapsibly
// Decent chance it doesn't happen, but you still expect it to.
Probably
// You expect it to happen most of the time, but accept that there will be failures.
Most-Likely
// Very close to guaranteed, but technically with a small chance to fail.
Cry-If-Not
// Absolutely guaranteed events.
Guaranteed
// Automatically generated.
// Comparison of the `kfl` type.
pub fun cmp(this : kfl, other : kfl) : e order
match (this, other)
(Impossible, Impossible) -> Eq
(Impossible, _) -> Lt
(_, Impossible) -> Gt
(Probably-Not, Probably-Not) -> Eq
(Probably-Not, _) -> Lt
(_, Probably-Not) -> Gt
(Doubtful, Doubtful) -> Eq
(Doubtful, _) -> Lt
(_, Doubtful) -> Gt
(Unlikely, Unlikely) -> Eq
(Unlikely, _) -> Lt
(_, Unlikely) -> Gt
(Mayhapsibly, Mayhapsibly) -> Eq
(Mayhapsibly, _) -> Lt
(_, Mayhapsibly) -> Gt
(Probably, Probably) -> Eq
(Probably, _) -> Lt
(_, Probably) -> Gt
(Most-Likely, Most-Likely) -> Eq
(Most-Likely, _) -> Lt
(_, Most-Likely) -> Gt
(Cry-If-Not, Cry-If-Not) -> Eq
(Cry-If-Not, _) -> Lt
(_, Cry-If-Not) -> Gt
(Guaranteed, Guaranteed) -> Eq
// Shows a string representation of the `kfl` type.
pub fun show(this : kfl) : e string
match this
Impossible -> "impossible"
Probably-Not -> "probably not"
Doubtful -> "doubtful"
Unlikely -> "unlikely"
Mayhapsibly -> "mayhapsibly"
Probably -> "probably"
Most-Likely -> "most likely"
Cry-If-Not -> "cry if not"
Guaranteed -> "guaranteed"
// KFL multiplication, or the probability of cooccurrence of two independent events.
pub fun (*)(a: kfl, b: kfl): e kfl
val (l, h) = match a.cmp(b) // this operation is commutative
Gt -> (b, a)
_ -> (a, b)
match (l, h)
(r, Guaranteed) -> r // factor out Guaranteed cases
(Impossible, _) -> Impossible
(Probably-Not, _) -> Impossible
(r, Cry-If-Not) -> r // factor out further Cry-If-Not cases
(Doubtful, Most-Likely) -> Probably-Not
(Doubtful, _) -> Impossible
(Unlikely, Most-Likely) -> Doubtful
(Unlikely, Probably) -> Doubtful
(Unlikely, Mayhapsibly) -> Probably-Not
(Unlikely, _) -> Probably-Not // (Unlikely, Unlikely) because commutative
(Mayhapsibly, Most-Likely) -> Unlikely
(Mayhapsibly, Probably) -> Unlikely
(Mayhapsibly, _) -> Unlikely
(Probably, Most-Likely) -> Mayhapsibly
(Probably, _) -> Unlikely
(Most-Likely, _) -> Probably
// These two are only needed because the type system doesn't understand commutativity.
(Cry-If-Not, _) -> Cry-If-Not
(Guaranteed, _) -> Guaranteed
// KFL addition, or the probability of occurrence of at least one of two independent events.
pub fun (+)(a: kfl, b: kfl): e kfl
val (l, h) = match a.cmp(b) // this operation is commutative
Gt -> (b, a)
_ -> (a, b)
match (l, h)
// Cases with _ on the right are (a, a) due to commutativity.
// Cases with _ on the left simplify later cases that all absorb to the right.
(Guaranteed, _) -> Guaranteed
(_, Guaranteed) -> Guaranteed
(Cry-If-Not, _) -> Guaranteed
(Most-Likely, Cry-If-Not) -> Cry-If-Not
(Most-Likely, _) -> Cry-If-Not
(_, Cry-If-Not) -> Cry-If-Not
(Probably, Most-Likely) -> Cry-If-Not
(Probably, _) -> Most-Likely
(_, Most-Likely) -> Most-Likely
(Mayhapsibly, Probably) -> Most-Likely
(Mayhapsibly, _) -> Probably
(Unlikely, Probably) -> Most-Likely
(Unlikely, Mayhapsibly) -> Probably
(Unlikely, _) -> Mayhapsibly
(_, Probably) -> Probably
(Doubtful, Mayhapsibly) -> Probably
(Doubtful, Unlikely) -> Mayhapsibly
(Doubtful, _) -> Unlikely
(_, Mayhapsibly) -> Mayhapsibly
(_, Unlikely) -> Unlikely
(Probably-Not, Doubtful) -> Unlikely
(Probably-Not, _) -> Probably-Not
(_, Doubtful) -> Doubtful
(_, Probably-Not) -> Probably-Not
(_, Impossible) -> Impossible
// KFL union, or the probability of occurrence of exactly one of two independent events.
pub fun either(a: kfl, b: kfl): e kfl
val (l, h) = match a.cmp(b) // this operation is commutative
Gt -> (b, a)
_ -> (a, b)
match (l, h)
(Impossible, r) -> r
(Probably-Not, Guaranteed) -> Cry-If-Not
(Probably-Not, r) -> r
(Doubtful, Guaranteed) -> Most-Likely
(Doubtful, Cry-If-Not) -> Most-Likely
(Doubtful, Most-Likely) -> Probably
(Doubtful, Probably) -> Mayhapsibly
(Doubtful, Mayhapsibly) -> Mayhapsibly
(Doubtful, Unlikely) -> Mayhapsibly
(Doubtful, _) -> Unlikely
(Unlikely, Guaranteed) -> Probably
(Unlikely, Cry-If-Not) -> Mayhapsibly
(Unlikely, Most-Likely) -> Mayhapsibly
(Unlikely, _) -> Probably
(Mayhapsibly, Guaranteed) -> Mayhapsibly
(Mayhapsibly, Cry-If-Not) -> Mayhapsibly
(Mayhapsibly, Most-Likely) -> Mayhapsibly
(Mayhapsibly, _) -> Probably
(Probably, Guaranteed) -> Unlikely
(Probably, Cry-If-Not) -> Unlikely
(Probably, Most-Likely) -> Unlikely
(Probably, _) -> Mayhapsibly
(Most-Likely, _) -> Doubtful
(Cry-If-Not, _) -> Probably-Not
(Guaranteed, _) -> Impossible

58
horse/prob/pmf.kk Normal file
View File

@@ -0,0 +1,58 @@
module horse/prob/pmf
import std/core/list
// Discrete-support probability distribution implemented as a list with the invariant
// that support is always given in increasing order.
pub type pmf<s, v>
Event(s: s, v: v, next: pmf<s, v>)
End
// Add an independent event to the distribution.
pub fun add(p: pmf<s, v>, s: s, v: v, ?s/cmp: (a: s, b: s) -> order, ?v/(+): (new: v, old: v) -> e v): e pmf<s, v>
match p
End -> Event(s, v, End)
Event(s', v', next) -> match s.cmp(s')
Lt -> Event(s, v, Event(s', v', next))
Eq -> Event(s, v + v', next)
Gt -> Event(s', v', add(next, s, v))
// Replace an event in the distribution.
pub inline fun set(p: pmf<s, v>, s: s, v: v, ?s/cmp: (a: s, b: s) -> order): e pmf<s, v>
p.add(s, v, cmp, fn(new, old) new)
// Construct a pmf from a list of (support, value) entries.
pub fun list/pmf(l: list<(s, v)>, ?s/cmp: (a: s, b: s) -> order, ?v/(+): (new: v, old: v) -> e v): e pmf<s, v>
l.foldl(End) fn(p, (s, v)) p.add(s, v)
// Fold over the entries of the distribution.
pub tail fun foldl(p: pmf<s, v>, init: a, f: (a, s, v) -> e a): e a
match p
End -> init
Event(s, v, next) -> foldl(next, f(init, s, v), f)
// Convert the distribution to a list of entries.
pub fun pmf/list(p: pmf<s, v>): list<(s, v)>
p.foldl(Nil) fn(l, s, v) Cons((s, v), l)
// Distribution of cooccurrence of two events described by their distributions.
pub fun (*)(a: pmf<s, v>, b: pmf<s, v>, ?s/cmp: (a: s, b: s) -> order, ?v/(*): (a: v, b: v) -> e v): e pmf<s, v>
match a
End -> End
Event(sa, va, nexta) -> match b
End -> End
Event(sb, vb, nextb) -> match sa.cmp(sb)
Lt -> nexta * b
Eq -> Event(sa, va * vb, nexta * nextb)
Gt -> a * nextb
// Distribution of occurrence of at least one of two events described by their distributions.
pub fun (+)(a: pmf<s, v>, b: pmf<s, v>, ?s/cmp: (a: s, b: s) -> order, ?v/(+): (a: v, b: v) -> e v): e pmf<s, v>
match a
End -> b
Event(sa, va, nexta) -> match b
End -> a
Event(sb, vb, nextb) -> match sa.cmp(sb)
Lt -> Event(sa, va, nexta + b)
Eq -> Event(sa, va + vb, nexta + nextb)
Gt -> Event(sb, vb, a + nextb)

37
horse/race.go Normal file
View File

@@ -0,0 +1,37 @@
package horse
type RaceID int32
// Race is the internal data about a race.
type Race struct {
ID RaceID
Name string
Thumbnail int
// Some careers contain unusual versions of races, e.g. Tenno Sho (Spring)
// in Hanshin instead of Kyoto for Narita Taishin and Biwa Hayahide.
// For such races, this field holds the normal race ID.
Primary RaceID
}
type SaddleID int32
// Saddle is the internal data about a race win saddle.
type Saddle struct {
ID SaddleID
Name string
Races []RaceID
Type SaddleType
// Saddles that involve alternate races are themselves alternate.
// For such saddles, this field holds the normal saddle ID.
Primary SaddleID
}
type SaddleType int8
const (
// Saddle for multiple race wins, e.g. Classic Triple Crown, Dual Grand Prix, &c.
SaddleTypeHonor SaddleType = iota
SaddleTypeG3
SaddleTypeG2
SaddleTypeG1
)

View File

@@ -1,426 +1,30 @@
module horse/race module horse/race
import std/data/linearset import std/data/linearset
import horse/game-id
// Exhaustive enumeration of graded races that can be run in career. pub struct race-detail
// Races that can be run in multiple years are listed only once. race-id: race-id
pub type career-race name: string
February-Stakes grade: grade
Takamatsunomiya-Kinen thumbnail-id: race-thumbnail-id
Osaka-Hai // Some careers contain unusual versions of races, e.g. Tenno Sho (Spring)
Oka-Sho // in Hanshin instead of Kyoto for Narita Taishin and Biwa Hayahide.
Satsuki-Sho // For such races, this field holds the normal race ID.
Tenno-Sho-Spring primary: race-id
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
Nikkei-Shinshun-Hai
Tokai-Stakes
American-Jockey-Club-Cup
Kyoto-Kinen
Nakayama-Kinen
Tulip-Sho
Yayoi-Sho
Kinko-Sho
Fillies-Revue
Hanshin-Daishoten
Spring-Stakes
Nikkei-Sho
Hanshin-Umamusume-Stakes
New-Zealand-Trophy
Yomiuri-Milers-Cup
Flora-Stakes
Aoba-Sho
Kyoto-Shimbun-Hai
Keio-Hai-Spring-Cup
Meguro-Kinen
Sapporo-Kinen
Centaur-Stakes
Rose-Stakes
St-Lite-Kinen
Kobe-Shimbun-Hai
All-Comers
Mainichi-Okan
Kyoto-Daishoten
Fuchu-Umamusume-Stakes
Fuji-Stakes
Swan-Stakes
Keio-Hai-Junior-Stakes
Copa-Republica-Argentina
Daily-Hai-Junior-Stakes
Stayers-Stakes
Hanshin-Cup
Kyoto-Kimpai
Nakayama-Kimpai
Shinzan-Kinen
Fairy-Stakes
Aichi-Hai
Keisei-Hai
Silk-Road-Stakes
Negishi-Stakes
Kisaragi-Sho
Tokyo-Shimbun-Hai
Queen-Cup
Kyodo-News-Hai
Kyoto-Umamusume-Stakes
Diamond-Stakes
Kokura-Daishoten
Arlington-Cup
Hankyu-Hai
Ocean-Stakes
Nakayama-Umamusume-Stakes
Falcon-Stakes
Flower-Cup
Mainichi-Hai
March-Stakes
Lord-Derby-Challenge-Trophy
Antares-Stakes
Fukushima-Umamusume-Stakes
Niigata-Daishoten
Heian-Stakes
Aoi-Stakes
Naruo-Kinen
Mermaid-Stakes
Epsom-Cup
Unicorn-Stakes
Hakodate-Sprint-Stakes
CBC-Sho
Radio-Nikkei-Sho
Procyon-Stakes
Tanabata-Sho
Hakodate-Kinen
Chukyo-Kinen
Hakodate-Junior-Stakes
Ibis-Summer-Dash
Queen-Stakes
Kokura-Kinen
Leopard-Stakes
Sekiya-Kinen
Elm-Stakes
Kitakyushu-Kinen
Niigata-Junior-Stakes
Keeneland-Cup
Sapporo-Junior-Stakes
Kokura-Junior-Stakes
Niigata-Kinen
Shion-Stakes
Keisei-Hai-Autumn-Handicap
Sirius-Stakes
Saudi-Arabia-Royal-Cup
Artemis-Stakes
Fantasy-Stakes
Miyako-Stakes
Musashino-Stakes
Fukushima-Kinen
Tokyo-Sports-Hai-Junior-Stakes
Kyoto-Junior-Stakes
Keihan-Hai
Challenge-Cup
Chunichi-Shimbun-Hai
Capella-Stakes
Turquoise-Stakes
// Automatically generated. pub fun detail(
// Shows a string representation of the `career-race` type. r: race-id,
pub fun career-race/show(this : career-race) : e string ?race/show: (race-id) -> string,
match this ?race/grade: (race-id) -> grade,
February-Stakes -> "February Stakes" ?race/thumbnail: (race-id) -> race-thumbnail-id,
Takamatsunomiya-Kinen -> "Takamatsunomiya Kinen" ?race/primary: (race-id) -> race-id
Osaka-Hai -> "Osaka Hai" ): race-detail
Oka-Sho -> "Oka Sho" Race-detail(r, r.show, r.grade, r.thumbnail, r.primary)
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"
Nikkei-Shinshun-Hai -> "Nikkei Shinshun Hai"
Tokai-Stakes -> "Tokai Stakes"
American-Jockey-Club-Cup -> "American Jockey Club Cup"
Kyoto-Kinen -> "Kyoto Kinen"
Nakayama-Kinen -> "Nakayama Kinen"
Tulip-Sho -> "Tulip Sho"
Yayoi-Sho -> "Yayoi Sho"
Kinko-Sho -> "Kinko Sho"
Fillies-Revue -> "Fillies Revue"
Hanshin-Daishoten -> "Hanshin Daishoten"
Spring-Stakes -> "Spring Stakes"
Nikkei-Sho -> "Nikkei Sho"
Hanshin-Umamusume-Stakes -> "Hanshin Umamusume Stakes"
New-Zealand-Trophy -> "New Zealand Trophy"
Yomiuri-Milers-Cup -> "Yomiuri Milers Cup"
Flora-Stakes -> "Flora Stakes"
Aoba-Sho -> "Aoba Sho"
Kyoto-Shimbun-Hai -> "Kyoto Shimbun Hai"
Keio-Hai-Spring-Cup -> "Keio Hai Spring Cup"
Meguro-Kinen -> "Meguro Kinen"
Sapporo-Kinen -> "Sapporo Kinen"
Centaur-Stakes -> "Centaur Stakes"
Rose-Stakes -> "Rose Stakes"
St-Lite-Kinen -> "St Lite Kinen"
Kobe-Shimbun-Hai -> "Kobe Shimbun Hai"
All-Comers -> "All Comers"
Mainichi-Okan -> "Mainichi Okan"
Kyoto-Daishoten -> "Kyoto Daishoten"
Fuchu-Umamusume-Stakes -> "Fuchu Umamusume Stakes"
Fuji-Stakes -> "Fuji Stakes"
Swan-Stakes -> "Swan Stakes"
Keio-Hai-Junior-Stakes -> "Keio Hai Junior Stakes"
Copa-Republica-Argentina -> "Copa Republica Argentina"
Daily-Hai-Junior-Stakes -> "Daily Hai Junior Stakes"
Stayers-Stakes -> "Stayers Stakes"
Hanshin-Cup -> "Hanshin Cup"
Kyoto-Kimpai -> "Kyoto Kimpai"
Nakayama-Kimpai -> "Nakayama Kimpai"
Shinzan-Kinen -> "Shinzan Kinen"
Fairy-Stakes -> "Fairy Stakes"
Aichi-Hai -> "Aichi Hai"
Keisei-Hai -> "Keisei Hai"
Silk-Road-Stakes -> "Silk Road Stakes"
Negishi-Stakes -> "Negishi Stakes"
Kisaragi-Sho -> "Kisaragi Sho"
Tokyo-Shimbun-Hai -> "Tokyo Shimbun Hai"
Queen-Cup -> "Queen Cup"
Kyodo-News-Hai -> "Kyodo News Hai"
Kyoto-Umamusume-Stakes -> "Kyoto Umamusume Stakes"
Diamond-Stakes -> "Diamond Stakes"
Kokura-Daishoten -> "Kokura Daishoten"
Arlington-Cup -> "Arlington Cup"
Hankyu-Hai -> "Hankyu Hai"
Ocean-Stakes -> "Ocean Stakes"
Nakayama-Umamusume-Stakes -> "Nakayama Umamusume Stakes"
Falcon-Stakes -> "Falcon Stakes"
Flower-Cup -> "Flower Cup"
Mainichi-Hai -> "Mainichi Hai"
March-Stakes -> "March Stakes"
Lord-Derby-Challenge-Trophy -> "Lord Derby Challenge Trophy"
Antares-Stakes -> "Antares Stakes"
Fukushima-Umamusume-Stakes -> "Fukushima Umamusume Stakes"
Niigata-Daishoten -> "Niigata Daishoten"
Heian-Stakes -> "Heian Stakes"
Aoi-Stakes -> "Aoi Stakes"
Naruo-Kinen -> "Naruo Kinen"
Mermaid-Stakes -> "Mermaid Stakes"
Epsom-Cup -> "Epsom Cup"
Unicorn-Stakes -> "Unicorn Stakes"
Hakodate-Sprint-Stakes -> "Hakodate Sprint Stakes"
CBC-Sho -> "CBC Sho"
Radio-Nikkei-Sho -> "Radio Nikkei Sho"
Procyon-Stakes -> "Procyon Stakes"
Tanabata-Sho -> "Tanabata Sho"
Hakodate-Kinen -> "Hakodate Kinen"
Chukyo-Kinen -> "Chukyo Kinen"
Hakodate-Junior-Stakes -> "Hakodate Junior Stakes"
Ibis-Summer-Dash -> "Ibis Summer Dash"
Queen-Stakes -> "Queen Stakes"
Kokura-Kinen -> "Kokura Kinen"
Leopard-Stakes -> "Leopard Stakes"
Sekiya-Kinen -> "Sekiya Kinen"
Elm-Stakes -> "Elm Stakes"
Kitakyushu-Kinen -> "Kitakyushu Kinen"
Niigata-Junior-Stakes -> "Niigata Junior Stakes"
Keeneland-Cup -> "Keeneland Cup"
Sapporo-Junior-Stakes -> "Sapporo Junior Stakes"
Kokura-Junior-Stakes -> "Kokura Junior Stakes"
Niigata-Kinen -> "Niigata Kinen"
Shion-Stakes -> "Shion Stakes"
Keisei-Hai-Autumn-Handicap -> "Keisei Hai Autumn Handicap"
Sirius-Stakes -> "Sirius Stakes"
Saudi-Arabia-Royal-Cup -> "Saudi Arabia Royal Cup"
Artemis-Stakes -> "Artemis Stakes"
Fantasy-Stakes -> "Fantasy Stakes"
Miyako-Stakes -> "Miyako Stakes"
Musashino-Stakes -> "Musashino Stakes"
Fukushima-Kinen -> "Fukushima Kinen"
Tokyo-Sports-Hai-Junior-Stakes -> "Tokyo Sports Hai Junior Stakes"
Kyoto-Junior-Stakes -> "Kyoto Junior Stakes"
Keihan-Hai -> "Keihan Hai"
Challenge-Cup -> "Challenge Cup"
Chunichi-Shimbun-Hai -> "Chunichi Shimbun Hai"
Capella-Stakes -> "Capella Stakes"
Turquoise-Stakes -> "Turquoise Stakes"
// Automatically generated. pub fun race-detail/show(r: race-detail): string
// Equality comparison of the `career-race` type. val Race-detail(Race-id(id), name) = r
pub fun career-race/(==)(this : career-race, other : career-race) : e bool name ++ " (ID " ++ id.show ++ ")"
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
(Nikkei-Shinshun-Hai, Nikkei-Shinshun-Hai) -> True
(Tokai-Stakes, Tokai-Stakes) -> True
(American-Jockey-Club-Cup, American-Jockey-Club-Cup) -> True
(Kyoto-Kinen, Kyoto-Kinen) -> True
(Nakayama-Kinen, Nakayama-Kinen) -> True
(Tulip-Sho, Tulip-Sho) -> True
(Yayoi-Sho, Yayoi-Sho) -> True
(Kinko-Sho, Kinko-Sho) -> True
(Fillies-Revue, Fillies-Revue) -> True
(Hanshin-Daishoten, Hanshin-Daishoten) -> True
(Spring-Stakes, Spring-Stakes) -> True
(Nikkei-Sho, Nikkei-Sho) -> True
(Hanshin-Umamusume-Stakes, Hanshin-Umamusume-Stakes) -> True
(New-Zealand-Trophy, New-Zealand-Trophy) -> True
(Yomiuri-Milers-Cup, Yomiuri-Milers-Cup) -> True
(Flora-Stakes, Flora-Stakes) -> True
(Aoba-Sho, Aoba-Sho) -> True
(Kyoto-Shimbun-Hai, Kyoto-Shimbun-Hai) -> True
(Keio-Hai-Spring-Cup, Keio-Hai-Spring-Cup) -> True
(Meguro-Kinen, Meguro-Kinen) -> True
(Sapporo-Kinen, Sapporo-Kinen) -> True
(Centaur-Stakes, Centaur-Stakes) -> True
(Rose-Stakes, Rose-Stakes) -> True
(St-Lite-Kinen, St-Lite-Kinen) -> True
(Kobe-Shimbun-Hai, Kobe-Shimbun-Hai) -> True
(All-Comers, All-Comers) -> True
(Mainichi-Okan, Mainichi-Okan) -> True
(Kyoto-Daishoten, Kyoto-Daishoten) -> True
(Fuchu-Umamusume-Stakes, Fuchu-Umamusume-Stakes) -> True
(Fuji-Stakes, Fuji-Stakes) -> True
(Swan-Stakes, Swan-Stakes) -> True
(Keio-Hai-Junior-Stakes, Keio-Hai-Junior-Stakes) -> True
(Copa-Republica-Argentina, Copa-Republica-Argentina) -> True
(Daily-Hai-Junior-Stakes, Daily-Hai-Junior-Stakes) -> True
(Stayers-Stakes, Stayers-Stakes) -> True
(Hanshin-Cup, Hanshin-Cup) -> True
(Kyoto-Kimpai, Kyoto-Kimpai) -> True
(Nakayama-Kimpai, Nakayama-Kimpai) -> True
(Shinzan-Kinen, Shinzan-Kinen) -> True
(Fairy-Stakes, Fairy-Stakes) -> True
(Aichi-Hai, Aichi-Hai) -> True
(Keisei-Hai, Keisei-Hai) -> True
(Silk-Road-Stakes, Silk-Road-Stakes) -> True
(Negishi-Stakes, Negishi-Stakes) -> True
(Kisaragi-Sho, Kisaragi-Sho) -> True
(Tokyo-Shimbun-Hai, Tokyo-Shimbun-Hai) -> True
(Queen-Cup, Queen-Cup) -> True
(Kyodo-News-Hai, Kyodo-News-Hai) -> True
(Kyoto-Umamusume-Stakes, Kyoto-Umamusume-Stakes) -> True
(Diamond-Stakes, Diamond-Stakes) -> True
(Kokura-Daishoten, Kokura-Daishoten) -> True
(Arlington-Cup, Arlington-Cup) -> True
(Hankyu-Hai, Hankyu-Hai) -> True
(Ocean-Stakes, Ocean-Stakes) -> True
(Nakayama-Umamusume-Stakes, Nakayama-Umamusume-Stakes) -> True
(Falcon-Stakes, Falcon-Stakes) -> True
(Flower-Cup, Flower-Cup) -> True
(Mainichi-Hai, Mainichi-Hai) -> True
(March-Stakes, March-Stakes) -> True
(Lord-Derby-Challenge-Trophy, Lord-Derby-Challenge-Trophy) -> True
(Antares-Stakes, Antares-Stakes) -> True
(Fukushima-Umamusume-Stakes, Fukushima-Umamusume-Stakes) -> True
(Niigata-Daishoten, Niigata-Daishoten) -> True
(Heian-Stakes, Heian-Stakes) -> True
(Aoi-Stakes, Aoi-Stakes) -> True
(Naruo-Kinen, Naruo-Kinen) -> True
(Mermaid-Stakes, Mermaid-Stakes) -> True
(Epsom-Cup, Epsom-Cup) -> True
(Unicorn-Stakes, Unicorn-Stakes) -> True
(Hakodate-Sprint-Stakes, Hakodate-Sprint-Stakes) -> True
(CBC-Sho, CBC-Sho) -> True
(Radio-Nikkei-Sho, Radio-Nikkei-Sho) -> True
(Procyon-Stakes, Procyon-Stakes) -> True
(Tanabata-Sho, Tanabata-Sho) -> True
(Hakodate-Kinen, Hakodate-Kinen) -> True
(Chukyo-Kinen, Chukyo-Kinen) -> True
(Hakodate-Junior-Stakes, Hakodate-Junior-Stakes) -> True
(Ibis-Summer-Dash, Ibis-Summer-Dash) -> True
(Queen-Stakes, Queen-Stakes) -> True
(Kokura-Kinen, Kokura-Kinen) -> True
(Leopard-Stakes, Leopard-Stakes) -> True
(Sekiya-Kinen, Sekiya-Kinen) -> True
(Elm-Stakes, Elm-Stakes) -> True
(Kitakyushu-Kinen, Kitakyushu-Kinen) -> True
(Niigata-Junior-Stakes, Niigata-Junior-Stakes) -> True
(Keeneland-Cup, Keeneland-Cup) -> True
(Sapporo-Junior-Stakes, Sapporo-Junior-Stakes) -> True
(Kokura-Junior-Stakes, Kokura-Junior-Stakes) -> True
(Niigata-Kinen, Niigata-Kinen) -> True
(Shion-Stakes, Shion-Stakes) -> True
(Keisei-Hai-Autumn-Handicap, Keisei-Hai-Autumn-Handicap) -> True
(Sirius-Stakes, Sirius-Stakes) -> True
(Saudi-Arabia-Royal-Cup, Saudi-Arabia-Royal-Cup) -> True
(Artemis-Stakes, Artemis-Stakes) -> True
(Fantasy-Stakes, Fantasy-Stakes) -> True
(Miyako-Stakes, Miyako-Stakes) -> True
(Musashino-Stakes, Musashino-Stakes) -> True
(Fukushima-Kinen, Fukushima-Kinen) -> True
(Tokyo-Sports-Hai-Junior-Stakes, Tokyo-Sports-Hai-Junior-Stakes) -> True
(Kyoto-Junior-Stakes, Kyoto-Junior-Stakes) -> True
(Keihan-Hai, Keihan-Hai) -> True
(Challenge-Cup, Challenge-Cup) -> True
(Chunichi-Shimbun-Hai, Chunichi-Shimbun-Hai) -> True
(Capella-Stakes, Capella-Stakes) -> True
(Turquoise-Stakes, Turquoise-Stakes) -> True
(_, _) -> False
// Race grades. // Race grades.
pub type grade pub type grade
@@ -431,250 +35,86 @@ pub type grade
G1 G1
EX EX
pub fun career-race/grade(r: career-race): grade // Automatically generated.
match r // Comparison of the `grade` type.
February-Stakes -> G1 pub fun grade/cmp(this : grade, other : grade) : e order
Takamatsunomiya-Kinen -> G1 match (this, other)
Osaka-Hai -> G1 (Pre-OP, Pre-OP) -> Eq
Oka-Sho -> G1 (Pre-OP, _) -> Lt
Satsuki-Sho -> G1 (_, Pre-OP) -> Gt
Tenno-Sho-Spring -> G1 (OP, OP) -> Eq
NHK-Mile-Cup -> G1 (OP, _) -> Lt
Victoria-Mile -> G1 (_, OP) -> Gt
Japanese-Oaks -> G1 (G3, G3) -> Eq
Japanese-Derby -> G1 (G3, _) -> Lt
Yasuda-Kinen -> G1 (_, G3) -> Gt
Takarazuka-Kinen -> G1 (G2, G2) -> Eq
Sprinters-Stakes -> G1 (G2, _) -> Lt
Shuka-Sho -> G1 (_, G2) -> Gt
Kikuka-Sho -> G1 (G1, G1) -> Eq
Tenno-Sho-Autumn -> G1 (G1, _) -> Lt
Queen-Elizabeth-II-Cup -> G1 (_, G1) -> Gt
Mile-Championship -> G1 (EX, EX) -> Eq
Japan-Cup -> G1
Champions-Cup -> G1
Hanshin-Juvenile-Fillies -> G1
Asahi-Hai-Futurity-Stakes -> G1
Arima-Kinen -> G1
Hopeful-Stakes -> G1
Tokyo-Daishoten -> G1
JBC-Classic -> G1
JBC-Sprint -> G1
JBC-Ladies-Classic -> G1
Japan-Dirt-Derby -> G1
Teio-Sho -> G1
Nikkei-Shinshun-Hai -> G2
Tokai-Stakes -> G2
American-Jockey-Club-Cup -> G2
Kyoto-Kinen -> G2
Nakayama-Kinen -> G2
Tulip-Sho -> G2
Yayoi-Sho -> G2
Kinko-Sho -> G2
Fillies-Revue -> G2
Hanshin-Daishoten -> G2
Spring-Stakes -> G2
Nikkei-Sho -> G2
Hanshin-Umamusume-Stakes -> G2
New-Zealand-Trophy -> G2
Yomiuri-Milers-Cup -> G2
Flora-Stakes -> G2
Aoba-Sho -> G2
Kyoto-Shimbun-Hai -> G2
Keio-Hai-Spring-Cup -> G2
Meguro-Kinen -> G2
Sapporo-Kinen -> G2
Centaur-Stakes -> G2
Rose-Stakes -> G2
St-Lite-Kinen -> G2
Kobe-Shimbun-Hai -> G2
All-Comers -> G2
Mainichi-Okan -> G2
Kyoto-Daishoten -> G2
Fuchu-Umamusume-Stakes -> G2
Fuji-Stakes -> G2
Swan-Stakes -> G2
Keio-Hai-Junior-Stakes -> G2
Copa-Republica-Argentina -> G2
Daily-Hai-Junior-Stakes -> G2
Stayers-Stakes -> G2
Hanshin-Cup -> G2
Kyoto-Kimpai -> G3
Nakayama-Kimpai -> G3
Shinzan-Kinen -> G3
Fairy-Stakes -> G3
Aichi-Hai -> G3
Keisei-Hai -> G3
Silk-Road-Stakes -> G3
Negishi-Stakes -> G3
Kisaragi-Sho -> G3
Tokyo-Shimbun-Hai -> G3
Queen-Cup -> G3
Kyodo-News-Hai -> G3
Kyoto-Umamusume-Stakes -> G3
Diamond-Stakes -> G3
Kokura-Daishoten -> G3
Arlington-Cup -> G3
Hankyu-Hai -> G3
Ocean-Stakes -> G3
Nakayama-Umamusume-Stakes -> G3
Falcon-Stakes -> G3
Flower-Cup -> G3
Mainichi-Hai -> G3
March-Stakes -> G3
Lord-Derby-Challenge-Trophy -> G3
Antares-Stakes -> G3
Fukushima-Umamusume-Stakes -> G3
Niigata-Daishoten -> G3
Heian-Stakes -> G3
Aoi-Stakes -> G3
Naruo-Kinen -> G3
Mermaid-Stakes -> G3
Epsom-Cup -> G3
Unicorn-Stakes -> G3
Hakodate-Sprint-Stakes -> G3
CBC-Sho -> G3
Radio-Nikkei-Sho -> G3
Procyon-Stakes -> G3
Tanabata-Sho -> G3
Hakodate-Kinen -> G3
Chukyo-Kinen -> G3
Hakodate-Junior-Stakes -> G3
Ibis-Summer-Dash -> G3
Queen-Stakes -> G3
Kokura-Kinen -> G3
Leopard-Stakes -> G3
Sekiya-Kinen -> G3
Elm-Stakes -> G3
Kitakyushu-Kinen -> G3
Niigata-Junior-Stakes -> G3
Keeneland-Cup -> G3
Sapporo-Junior-Stakes -> G3
Kokura-Junior-Stakes -> G3
Niigata-Kinen -> G3
Shion-Stakes -> G3
Keisei-Hai-Autumn-Handicap -> G3
Sirius-Stakes -> G3
Saudi-Arabia-Royal-Cup -> G3
Artemis-Stakes -> G3
Fantasy-Stakes -> G3
Miyako-Stakes -> G3
Musashino-Stakes -> G3
Fukushima-Kinen -> G3
Tokyo-Sports-Hai-Junior-Stakes -> G3
Kyoto-Junior-Stakes -> G3
Keihan-Hai -> G3
Challenge-Cup -> G3
Chunichi-Shimbun-Hai -> G3
Capella-Stakes -> G3
Turquoise-Stakes -> G3
pub type title
Classic-Triple-Crown
Triple-Tiara
Senior-Spring-Triple-Crown
Senior-Autumn-Triple-Crown
Tenno-Sweep
Dual-Grand-Prix
Dual-Miles
Dual-Sprints
Dual-Dirts
// Get the titles that a race contributes to.
inline fun career-race/titles(r: career-race): list<title>
match r
Satsuki-Sho -> [Classic-Triple-Crown]
Japanese-Derby -> [Classic-Triple-Crown]
Kikuka-Sho -> [Classic-Triple-Crown]
Oka-Sho -> [Triple-Tiara]
Japanese-Oaks -> [Triple-Tiara]
Shuka-Sho -> [Triple-Tiara]
Osaka-Hai -> [Senior-Spring-Triple-Crown]
Tenno-Sho-Spring -> [Senior-Spring-Triple-Crown, Tenno-Sweep]
Takarazuka-Kinen -> [Senior-Spring-Triple-Crown, Dual-Grand-Prix]
Tenno-Sho-Autumn -> [Senior-Autumn-Triple-Crown, Tenno-Sweep]
Japan-Cup -> [Senior-Autumn-Triple-Crown]
Arima-Kinen -> [Senior-Autumn-Triple-Crown, Dual-Grand-Prix]
Yasuda-Kinen -> [Dual-Miles]
Mile-Championship -> [Dual-Miles]
Takamatsunomiya-Kinen -> [Dual-Sprints]
Sprinters-Stakes -> [Dual-Sprints]
February-Stakes -> [Dual-Dirts]
Champions-Cup -> [Dual-Dirts]
_ -> []
// Get the races that a title requires.
inline fun title/races(t: title): list<career-race>
match t
Classic-Triple-Crown -> [Satsuki-Sho, Japanese-Derby, Kikuka-Sho]
Triple-Tiara -> [Oka-Sho, Japanese-Oaks, Shuka-Sho]
Senior-Spring-Triple-Crown -> [Osaka-Hai, Tenno-Sho-Spring, Takarazuka-Kinen]
Senior-Autumn-Triple-Crown -> [Tenno-Sho-Autumn, Japan-Cup, Arima-Kinen]
Tenno-Sweep -> [Tenno-Sho-Spring, Tenno-Sho-Autumn]
Dual-Grand-Prix -> [Takarazuka-Kinen, Arima-Kinen]
Dual-Miles -> [Yasuda-Kinen, Mile-Championship]
Dual-Sprints -> [Takamatsunomiya-Kinen, Sprinters-Stakes]
Dual-Dirts -> [February-Stakes, Champions-Cup]
// Get all titles earned by an uma.
pub fun career/titles(results: list<race-result>): list<title>
val wins = results.flatmap-maybe() fn(r) (if r.place == 1 then Just(r.race) else Nothing)
val title-wins = wins.filter(_.titles.is-cons).linear-set
val titles = title-wins.list.flatmap(_.titles).linear-set.list
titles.filter(_.races.linear-set.is-subset-of(title-wins))
// Automatically generated. // Automatically generated.
// Equality comparison of the `title` type. // Shows a string representation of the `grade` type.
pub fun title/(==)(this : title, other : title) : e bool pub fun grade/show(this : grade) : e string
match this
Pre-OP -> "Pre-OP"
OP -> "OP"
G3 -> "G3"
G2 -> "G2"
G1 -> "G1"
EX -> "EX"
pub struct saddle-detail
saddle-id: saddle-id
name: string
races: list<race-id>
saddle-type: saddle-type
// For careers with unusual races, granted saddles also differ.
// This field holds the normal saddle's ID for such cases.
primary: saddle-id
pub fun saddle/detail(
id: saddle-id,
?saddle/show: (saddle-id) -> string,
?saddle/races: (saddle-id) -> list<race-id>,
?saddle/saddle-type: (saddle-id) -> saddle-type,
?saddle/primary: (saddle-id) -> saddle-id
): saddle-detail
Saddle-detail(id, id.show, id.races, id.saddle-type, id.primary)
pub fun saddle-detail/show(s: saddle-detail): string
val Saddle-detail(Saddle-id(id), name, _, _, Saddle-id(primary)) = s
if id == primary then name else name ++ " (Alternate " ++ id.show ++ ")"
// Types of saddles.
pub type saddle-type
Honor // multiple race wins: classic triple crown, dual grand prix, &c.
G3-Win
G2-Win
G1-Win
// Automatically generated.
// Shows a string representation of the `saddle-type` type.
pub fun saddle-type/show(this : saddle-type) : e string
match this
Honor -> "Honor"
G3-Win -> "G3"
G2-Win -> "G2"
G1-Win -> "G1"
// Automatically generated.
// Equality comparison of the `saddle-type` type.
pub fun saddle-type/(==)(this : saddle-type, other : saddle-type) : e bool
match (this, other) match (this, other)
(Classic-Triple-Crown, Classic-Triple-Crown) -> True (Honor, Honor) -> True
(Triple-Tiara, Triple-Tiara) -> True (G3-Win, G3-Win) -> True
(Senior-Spring-Triple-Crown, Senior-Spring-Triple-Crown) -> True (G2-Win, G2-Win) -> True
(Senior-Autumn-Triple-Crown, Senior-Autumn-Triple-Crown) -> True (G1-Win, G1-Win) -> True
(Tenno-Sweep, Tenno-Sweep) -> True
(Dual-Grand-Prix, Dual-Grand-Prix) -> True
(Dual-Miles, Dual-Miles) -> True
(Dual-Sprints, Dual-Sprints) -> True
(Dual-Dirts, Dual-Dirts) -> True
(_, _) -> False (_, _) -> False
// Automatically generated.
// Shows a string representation of the `title` type.
pub fun title/show(this : title) : e string
match this
Classic-Triple-Crown -> "Classic Triple Crown"
Triple-Tiara -> "Triple Tiara"
Senior-Spring-Triple-Crown -> "Senior Spring Triple Crown"
Senior-Autumn-Triple-Crown -> "Senior Autumn Triple Crown"
Tenno-Sweep -> "Tenno Sweep"
Dual-Grand-Prix -> "Dual Grand Prix"
Dual-Miles -> "Dual Miles"
Dual-Sprints -> "Dual Sprints"
Dual-Dirts -> "Dual Dirts"
// Graded race that a veteran ran.
pub struct race-result
race: career-race
place: int
turn: turn
// Automatically generated.
// Equality comparison of the `race-result` type.
pub fun race-result/(==)(this : race-result, other : race-result) : e bool
match (this, other)
(Race-result(race, place, turn), Race-result(race', place', turn')) -> race == race' && place == place' && turn == turn'
// Automatically generated.
// Shows a string representation of the `race-result` type.
pub fun race-result/show(this : race-result) : e string
match this
Race-result(race, place, turn) -> turn.show ++ " " ++ race.show ++ ": " ++ place.show
// Determine whether two race results are for the same race.
// This differs from (==) which also requires the race to be on the same turn.
pub fun race-result/same(a: race-result, b: race-result): bool
a.race == b.race
// Turn that a race occurred. // Turn that a race occurred.
pub struct turn pub struct turn
year: turn-year year: turn-year

View File

@@ -36,6 +36,7 @@ type Skill struct {
GradeValue int32 GradeValue int32
WitCheck bool WitCheck bool
Activations []Activation Activations []Activation
UniqueOwner string
SPCost int SPCost int
IconID int IconID int
} }
@@ -72,7 +73,7 @@ func (a Ability) String() string {
case AbilityVision: case AbilityVision:
r = append(r, a.Value.String()...) r = append(r, a.Value.String()...)
r = append(r, 'm') r = append(r, 'm')
case AbilityHP, AbilityCurrentSpeed, AbilityTargetSpeed, AbilityLaneSpeed, AbilityAccel: case AbilityHP:
r = append(r, (a.Value * 100).String()...) r = append(r, (a.Value * 100).String()...)
r = append(r, '%') r = append(r, '%')
case AbilityGateDelay: case AbilityGateDelay:
@@ -81,12 +82,36 @@ func (a Ability) String() string {
case AbilityFrenzy: case AbilityFrenzy:
r = append(r, a.Value.String()...) r = append(r, a.Value.String()...)
r = append(r, 's') r = append(r, 's')
case AbilityCurrentSpeed, AbilityTargetSpeed, AbilityLaneSpeed:
r = append(r, a.Value.String()...)
r = append(r, " m/s"...)
case AbilityAccel:
r = append(r, a.Value.String()...)
r = append(r, " m/s²"...)
case AbilityLaneChange: case AbilityLaneChange:
r = append(r, (a.Value * 100).String()...) r = append(r, a.Value.String()...)
r = append(r, "% of track width"...) r = append(r, " track widths"...)
} }
} }
if a.Target != TargetSelf { switch a.Target {
case TargetSelf:
// do nothing
case TargetStyle, TargetRushingStyle:
// TargetValue is the style to target, not the number of targets.
r = append(r, " to "...)
r = append(r, a.Target.String()...)
switch a.TargetValue {
case 1:
r = append(r, " Front Runner"...)
case 2:
r = append(r, " Pace Chaser"...)
case 3:
r = append(r, " Late Surger"...)
case 4:
r = append(r, " End Closer"...)
}
default:
// For other targeting types, TargetValue is either irrelevant or limit.
r = append(r, " to "...) r = append(r, " to "...)
if a.TargetValue > 1 && a.TargetValue < 18 { if a.TargetValue > 1 && a.TargetValue < 18 {
r = strconv.AppendInt(r, int64(a.TargetValue), 10) r = strconv.AppendInt(r, int64(a.TargetValue), 10)
@@ -127,11 +152,23 @@ type AbilityValueUsage int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AbilityValueUsage -trimprefix ValueUsage -linecomment //go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AbilityValueUsage -trimprefix ValueUsage -linecomment
const ( const (
ValueUsageDirect AbilityValueUsage = 1 // directly ValueUsageDirect AbilityValueUsage = 1 // directly
ValueUsageSkillCount AbilityValueUsage = 2 // scaling with the number of skills
ValueUsageTeamSpeed AbilityValueUsage = 3 // scaling with team Speed ValueUsageTeamSpeed AbilityValueUsage = 3 // scaling with team Speed
ValueUsageTeamStamina AbilityValueUsage = 4 // scaling with team Stamina ValueUsageTeamStamina AbilityValueUsage = 4 // scaling with team Stamina
ValueUsageTeamPower AbilityValueUsage = 5 // scaling with team Power ValueUsageTeamPower AbilityValueUsage = 5 // scaling with team Power
ValueUsageTeamGuts AbilityValueUsage = 6 // scaling with team Guts ValueUsageTeamGuts AbilityValueUsage = 6 // scaling with team Guts
ValueUsageTeamWit AbilityValueUsage = 7 // scaling with team Wit ValueUsageTeamWit AbilityValueUsage = 7 // scaling with team Wit
ValueUsageRandom AbilityValueUsage = 8 // with a random 0× to 0.04× multiplier
ValueUsageRandom2 AbilityValueUsage = 9 // with a random 0× to 0.04× multiplier
ValueUsageClimax AbilityValueUsage = 10 // scaling with the number of races won in training
ValueUsageMaxStat AbilityValueUsage = 13 // scaling with the highest raw stat
ValueUsageGreenCount AbilityValueUsage = 14 // scaling with the number of Passive skills activated
ValueUsageDistAdd AbilityValueUsage = 19 // plus extra when far from the lead
ValueUsageMidSideBlock AbilityValueUsage = 20 // scaling with mid-race phase blocked side time
ValueUsageSpeed AbilityValueUsage = 22 // scaling with overall speed
ValueUsageSpeed2 AbilityValueUsage = 23 // scaling with overall speed
ValueUsageArcPotential AbilityValueUsage = 24 // scaling with L'Arc global potential
ValueUsageMaxLead AbilityValueUsage = 25 // scaling with the longest lead obtained in the first ⅔
) )
type AbilityTarget int8 type AbilityTarget int8

226
horse/skill.kk Normal file
View File

@@ -0,0 +1,226 @@
module horse/skill
// This module contains skill-related definitions
// common to all versions of the game.
import std/num/decimal
import horse/game-id
import horse/movement
// Full details about a skill.
pub struct skill-detail
skill-id: skill-id
name: string
description: string
group-id: skill-group-id
rarity: rarity
group-rate: int
grade-value: int
wit-check: bool
activations: list<activation>
owner: maybe<trainee-id>
sp-cost: int
icon-id: skill-icon-id
pub fun detail(
s: skill-id,
?skill/show: (skill-id) -> string,
?skill/description: (skill-id) -> string,
?skill/group: (skill-id) -> skill-group-id,
?skill/rarity: (skill-id) -> rarity,
?skill/group-rate: (skill-id) -> int,
?skill/grade-value: (skill-id) -> int,
?skill/wit-check: (skill-id) -> bool,
?skill/activations: (skill-id) -> list<activation>,
?skill/unique-owner: (skill-id) -> maybe<trainee-id>,
?skill/sp-cost: (skill-id) -> int,
?skill/icon-id: (skill-id) -> skill-icon-id
): skill-detail
Skill-detail(
s,
s.show,
s.description,
s.group,
s.rarity,
s.group-rate,
s.grade-value,
s.wit-check,
s.activations,
s.unique-owner,
s.sp-cost,
s.icon-id
)
pub fun skill-detail/show(d: skill-detail, ?character/show: (character-id) -> string, ?trainee/show: (trainee-id) -> string): string
val Skill-detail(Skill-id(id), name, desc, _, rarity, _, grade-value, wit-check, activations, owner, sp-cost, _) = d
val r = name ++ " (ID " ++ id.show ++ "): " ++ desc ++ " " ++ activations.map(activation/show).join(". ") ++ (if wit-check then ". Wit check. " else ". No wit check. ") ++ rarity.show ++ " costing " ++ sp-cost.show ++ " SP, worth " ++ grade-value.show ++ " grade value."
match owner
Nothing -> r
Just(owner-id) -> match owner-id.show
"" -> r ++ " Unique skill of trainee with ID " ++ owner-id.show ++ "."
owner-name -> r ++ " Unique skill of " ++ owner-name ++ "."
// Skill rarity levels.
pub type rarity
Common // white
Rare // gold
Unique-Low // 1*/2* unique
Unique-Upgraded // 3*+ unique on a trainee upgraded from 1*/2*
Unique // base 3* unique
pub fun rarity/show(r: rarity): string
match r
Common -> "Common"
Rare -> "Rare"
Unique-Low -> "Unique (1\u2606/2\u2606)"
Unique-Upgraded -> "Unique (3\u2606+ from 1\u2606/2\u2606 upgraded)"
Unique -> "Unique (3\u2606+)"
// Condition and precondition logic.
pub alias condition = string
// Activation conditions and effects.
// A skill has one or two activations.
pub struct activation
precondition: condition
condition: condition
duration: decimal // seconds
cooldown: decimal // seconds
abilities: list<ability> // one to three elements
pub fun activation/show(a: activation, ?character/show: (character-id) -> string): string
match a
Activation("", condition, duration, _, abilities) | !duration.is-pos -> condition ++ " -> " ++ abilities.show
Activation("", condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> condition ++ " -> for " ++ duration.show ++ "s, " ++ abilities.show
Activation("", condition, duration, cooldown, abilities) -> condition ++ " -> for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
Activation(precondition, condition, duration, _, abilities) | !duration.is-pos -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show
Activation(precondition, condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s, " ++ abilities.show
Activation(precondition, condition, duration, cooldown, abilities) -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
// Effects of activating a skill.
pub struct ability
ability-type: ability-type
value-usage: value-usage
target: target
pub fun ability/show(a: ability, ?character/show: (character-id) -> string): string
match a
Ability(t, Direct, Self) -> t.show
Ability(t, Direct, target) -> t.show ++ " " ++ target.show
Ability(t, v, Self) -> t.show ++ " " ++ v.show
Ability(t, v, target) -> t.show ++ " " ++ target.show ++ " " ++ v.show
// Skill ability effects.
pub type ability-type
Passive-Speed(bonus: decimal)
Passive-Stamina(bonus: decimal)
Passive-Power(bonus: decimal)
Passive-Guts(bonus: decimal)
Passive-Wit(bonus: decimal)
Great-Escape
Vision(bonus: decimal)
HP(rate: decimal)
Gate-Delay(rate: decimal)
Frenzy(add: decimal)
Current-Speed(add: decimal)
Target-Speed(add: decimal)
Lane-Speed(add: decimal)
Accel(add: decimal)
Lane-Change(add: decimal)
pub fun ability-type/show(a: ability-type): string
match a
Passive-Speed(bonus) -> bonus.show ++ " Speed"
Passive-Stamina(bonus) -> bonus.show ++ " Stamina"
Passive-Power(bonus) -> bonus.show ++ " Power"
Passive-Guts(bonus) -> bonus.show ++ " Guts"
Passive-Wit(bonus) -> bonus.show ++ " Wit"
Great-Escape -> "enable Great Escape style"
Vision(bonus) -> bonus.show ++ " vision"
HP(rate) | rate.is-pos -> show(rate * 100.decimal) ++ "% HP recovery"
HP(rate) -> show(rate * 100.decimal) ++ "% HP loss"
Gate-Delay(rate) -> rate.show ++ "× gate delay"
Frenzy(add) -> add.show ++ "s longer Rushed"
Current-Speed(rate) -> rate.show ++ "m/s current speed"
Target-Speed(rate) -> rate.show ++ "m/s target speed"
Lane-Speed(rate) -> rate.show ++ "m/s lane change speed"
Accel(rate) -> rate.show ++ "m/s² acceleration"
Lane-Change(rate) -> rate.show ++ " course width movement"
// Special scaling types for skill abilities.
pub type value-usage
Direct
Team-Speed
Team-Stamina
Team-Power
Team-Guts
Team-Wit
Multiply-Random
Multiply-Random2
Climax
Max-Stat
Passive-Count
Front-Distance-Add
Midrace-Side-Block-Time
Speed-Scaling
Speed-Scaling2
Arc-Global-Potential
Max-Lead-Distance
pub fun value-usage/show(v: value-usage): string
match v
Direct -> "with no scaling"
Team-Speed -> "scaling with team Speed"
Team-Stamina -> "scaling with team Stamina"
Team-Power -> "scaling with team Power"
Team-Guts -> "scaling with team Guts"
Team-Wit -> "scaling with team Wit"
Multiply-Random -> "scaling with a random multiplier (0×, 0.02×, or 0.04×)"
Multiply-Random2 -> "scaling with a random multiplier (0×, 0.02×, or 0.04×)"
Climax -> "scaling with the number of races won during training"
Max-Stat -> "scaling with the value of the user's highest stat"
Passive-Count -> "scaling with the number of Passive skills activated"
Front-Distance-Add -> "scaling with distance from the leader"
Midrace-Side-Block-Time -> "scaling with mid-race phase blocked side time"
Speed-Scaling -> "scaling with overall speed"
Speed-Scaling2 -> "scaling with overall speed"
Arc-Global-Potential -> "scaling with L'Arc global potential"
Max-Lead-Distance -> "scaling with the distance of the longest lead obtained in the first two thirds of the race"
// Who a skill ability targets.
pub type target
Self
Sympathizers
In-View
Frontmost(limit: int)
Ahead(limit: int)
Behind(limit: int)
All-Teammates
Style(style: style)
Rushing-Ahead(limit: int)
Rushing-Behind(limit: int)
Rushing-Style(style: style)
Specific-Character(who: character-id)
Triggering
pub fun target/show(t: target, ?character/show: (character-id) -> string): string
match t
Self -> "self"
Sympathizers -> "others with Sympathy"
In-View -> "others in field of view"
Frontmost(limit) -> "frontmost " ++ limit.show
Ahead(limit) | limit >= 18 -> "others ahead"
Ahead(limit) -> "next " ++ limit.show ++ " others ahead"
Behind(limit) | limit >= 18 -> "others behind"
Behind(limit) -> "next " ++ limit.show ++ " others behind"
All-Teammates -> "all teammates"
Style(s) -> "other " ++ s.show ++ "s"
Rushing-Ahead(limit) | limit >= 18 -> "others rushing ahead"
Rushing-Ahead(limit) -> "next " ++ limit.show ++ " others rushing ahead"
Rushing-Behind(limit) | limit >= 18 -> "others rushing behind"
Rushing-Behind(limit) -> "next " ++ limit.show ++ " others rushing behind"
Rushing-Style(s) -> "rushing " ++ s.show ++ "s"
Specific-Character(who) -> match who.show
"" -> "character with ID " ++ who.show
name -> name
Triggering -> "whosoever triggered this skill"

View File

@@ -1,70 +1,18 @@
module horse/trainee module horse/trainee
import std/data/rb-map import horse/game-id
import horse/movement
import horse/race
// Aptitudes of an umamusume being trained. // Details of a trainee.
pub struct uma pub struct trainee-detail
turf: aptitudes turf: level
dirt: aptitudes dirt: level
sprint: aptitudes sprint: level
mile: aptitudes mile: level
medium: aptitudes medium: level
long: aptitudes long: level
front-runner: aptitudes front-runner: level
pace-chaser: aptitudes pace-chaser: level
late-surger: aptitudes late-surger: level
end-closer: aptitudes end-closer: level
// Aptitude level distribution.
pub alias aptitudes = rbmap<level, float64>
// Starting aptitude levels.
pub type level
G
F
E
D
C
B
A
S
// Automatically generated.
// Fip comparison of the `level` type.
pub fun level/order2(this : level, other : level) : order2<level>
match (this, other)
(G, G) -> Eq2(G)
(G, other') -> Lt2(G, other')
(this', G) -> Gt2(G, this')
(F, F) -> Eq2(F)
(F, other') -> Lt2(F, other')
(this', F) -> Gt2(F, this')
(E, E) -> Eq2(E)
(E, other') -> Lt2(E, other')
(this', E) -> Gt2(E, this')
(D, D) -> Eq2(D)
(D, other') -> Lt2(D, other')
(this', D) -> Gt2(D, this')
(C, C) -> Eq2(C)
(C, other') -> Lt2(C, other')
(this', C) -> Gt2(C, this')
(B, B) -> Eq2(B)
(B, other') -> Lt2(B, other')
(this', B) -> Gt2(B, this')
(A, A) -> Eq2(A)
(A, other') -> Lt2(A, other')
(this', A) -> Gt2(A, this')
(S, S) -> Eq2(S)
// Automatically generated.
// Shows a string representation of the `level` type.
pub fun level/show(this : level) : string
match this
G -> "G"
F -> "F"
E -> "E"
D -> "D"
C -> "C"
B -> "B"
A -> "A"
S -> "S"

View File

@@ -5,67 +5,55 @@ module horse/{{ $.Region }}/character
import std/core/vector import std/core/vector
import std/core-extras import std/core-extras
import std/data/rb-map
import horse/game-id
pub import horse/character
// Character identity. // Enumeration of all characters for type-safe programming.
pub type character pub type character
{{- range $uma := $.Characters }} {{- range $uma := $.Characters }}
{{ kkenum $uma.Name }} {{ kkenum $uma.Name }}
{{- end }} {{- end }}
// The list of all characters in order by ID, for easy iterating. // Get the character ID for a character.
pub val character/all = [ pub fun character-id(c: character): character-id
match c
{{- range $uma := $.Characters }}
{{ kkenum $uma.Name }} -> Character-id({{ $uma.ID }})
{{- end }}
// List of all characters in ID order for easy iterating.
pub val all = [
{{- range $uma := $.Characters }} {{- range $uma := $.Characters }}
{{ kkenum $uma.Name }}, {{ kkenum $uma.Name }},
{{- end }} {{- end }}
] ]
// Get the character for a character ID. val name2id: rbmap<string, character-id> = rb-map/empty()
// Generally, these are four digit numbers in the range 1000-1999.
pub fun character/from-id(id: int): maybe<character>
match id
{{- range $uma := $.Characters }} {{- range $uma := $.Characters }}
{{ $uma.ID }} -> Just( {{- kkenum $uma.Name -}} ) .set({{ printf "%q" $uma.Name }}, Character-id({{ $uma.ID}}))
{{- end }} {{- end }}
_ -> Nothing
// Get the ID for a character. // Get the character ID that has the given exact name.
pub fun character/character-id(c: character): int // If no character matches the name, the result is an invalid ID.
match c pub fun from-name(name: string): character-id
name2id.lookup(name).default(Character-id(0))
// Get the name for a character.
// If no character matches the ID, the result is the numeric ID.
pub fun show(c: character-id): string
match c.game-id
{{- range $uma := $.Characters }} {{- range $uma := $.Characters }}
{{ kkenum $uma.Name }} -> {{ $uma.ID }} {{ $uma.ID }} -> {{ printf "%q" $uma.Name }}
{{- end }} {{- end }}
x -> "character " ++ x.show
// Get the name of a character. fun character/index(c: character-id): int
pub fun character/show(c: character): string match c.game-id
match c
{{- range $uma := $.Characters }} {{- range $uma := $.Characters }}
{{ kkenum $uma.Name }} -> {{ printf "%q" $uma.Name }} {{ $uma.ID }} -> {{ $uma.Index }}
{{- end }}
// Compare two characters.
pub fip fun character/order2(a: character, b: character): order2<character>
match (a, b)
{{- range $uma := $.Characters }}{{ $e := kkenum $uma.Name }}
( {{- $e }}, {{ $e -}} ) -> Eq2( {{- $e -}} )
{{- if ne $uma.ID $.MaxID }}
( {{- $e }}, b') -> Lt2( {{- $e }}, b')
(a', {{ $e -}} ) -> Gt2( {{- $e }}, a')
{{- end }}
{{- end }}
// Character equality.
pub fun character/(==)(a: character, b: character): bool
match (a, b)
{{- range $uma := $.Characters }}{{ $e := kkenum $uma.Name }}
( {{- $e }}, {{ $e -}} ) -> True
{{- end }}
_ -> False
fip fun character/index(^c: character): int
match c
{{- range $uma := $.Characters }}
{{ kkenum $uma.Name }} -> {{ $uma.Index }}
{{- end }} {{- end }}
_ -> -99999999
// Create the table of all pair affinities. // Create the table of all pair affinities.
// The affinity is the value at a.index*count + b.index. // The affinity is the value at a.index*count + b.index.
@@ -87,7 +75,7 @@ extern global/create-pair-table(): vector<int>
val global/pair-table = global/create-pair-table() val global/pair-table = global/create-pair-table()
// Base affinity between a pair using the global ruleset. // Base affinity between a pair using the global ruleset.
pub fun global/pair-affinity(a: character, b: character): int pub fun global/pair-affinity(a: character-id, b: character-id): int
global/pair-table.at(a.index * {{ $.Count }} + b.index).default(0) global/pair-table.at(a.index * {{ $.Count }} + b.index).default(0)
// Create the table of all trio affinities. // Create the table of all trio affinities.
@@ -114,7 +102,7 @@ extern global/create-trio-table(): vector<int>
val global/trio-table = global/create-trio-table() val global/trio-table = global/create-trio-table()
// Base affinity for a trio using the global ruleset. // Base affinity for a trio using the global ruleset.
pub fun global/trio-affinity(a: character, b: character, c: character): int pub fun global/trio-affinity(a: character-id, b: character-id, c: character-id): int
global/trio-table.at(a.index * {{ $.Count }} * {{ $.Count }} + b.index * {{ $.Count }} + c.index).default(0) global/trio-table.at(a.index * {{ $.Count }} * {{ $.Count }} + b.index * {{ $.Count }} + c.index).default(0)
{{- end }} {{- end }}

View File

@@ -11,7 +11,7 @@ import (
"unicode" "unicode"
) )
//go:embed character.kk.template skill.kk.template character.go.template skill.go.template //go:embed *.template
var templates embed.FS var templates embed.FS
// LoadTemplates sets up templates to render game data to source code. // LoadTemplates sets up templates to render game data to source code.
@@ -64,10 +64,10 @@ func ExecCharacter(t *template.Template, region string, kk, g io.Writer, c []Nam
}{region, c, pairs, trios, pm, tm, len(c), maxid} }{region, c, pairs, trios, pm, tm, len(c), maxid}
var err error var err error
if kk != nil { if kk != nil {
err = errors.Join(t.ExecuteTemplate(kk, "koka-character", &data)) err = errors.Join(err, t.ExecuteTemplate(kk, "koka-character", &data))
} }
if g != nil { if g != nil {
err = errors.Join(t.ExecuteTemplate(g, "go-character", &data)) err = errors.Join(err, t.ExecuteTemplate(g, "go-character", &data))
} }
return err return err
} }
@@ -85,21 +85,42 @@ func ExecSkill(t *template.Template, region string, kk, g io.Writer, groups []Na
}{region, groups, skills, m} }{region, groups, skills, m}
var err error var err error
if kk != nil { if kk != nil {
err = errors.Join(t.ExecuteTemplate(kk, "koka-skill", &data)) err = errors.Join(err, t.ExecuteTemplate(kk, "koka-skill", &data))
} }
if g != nil { if g != nil {
err = errors.Join(t.ExecuteTemplate(g, "go-skill-data", &data)) err = errors.Join(err, t.ExecuteTemplate(g, "go-skill-data", &data))
} }
return err return err
} }
func ExecSkillGroupKK(t *template.Template, region string, w io.Writer, g []NamedID[SkillGroup], s []Skill) error { func ExecRace(t *template.Template, region string, kk, g io.Writer, races []Race) error {
data := struct { data := struct {
Region string Region string
Groups []NamedID[SkillGroup] Races []Race
Skills []Skill }{region, races}
}{region, g, s} var err error
return t.ExecuteTemplate(w, "koka-skill-group", &data) 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
}
func ExecSaddle(t *template.Template, region string, kk, g io.Writer, saddles []Saddle) error {
data := struct {
Region string
Saddles []Saddle
}{region, saddles}
var err error
if kk != nil {
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-saddle", &data))
}
if g != nil {
err = errors.Join(err, t.ExecuteTemplate(g, "go-saddle", &data))
}
return err
} }
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴" const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
@@ -111,13 +132,15 @@ var (
"1,500,000 CC", "One-Million-CC", "1,500,000 CC", "One-Million-CC",
"15,000,000 CC", "Fifteen-Million-CC", "15,000,000 CC", "Fifteen-Million-CC",
"1st", "First", "1st", "First",
"114th", "Hundred-Fourteenth",
"♡ 3D Nail Art", "Nail-Art", "♡ 3D Nail Art", "Nail-Art",
".", "", ".", "",
"\u2019", "",
"&", "-and-", "&", "-and-",
"'s", "s", "'s", "s",
"ó", "o", "ó", "o",
"∞", "Infinity", "∞", "Infinity",
"×", "x", "\u00d7", "x",
"◎", "Lv2", "◎", "Lv2",
} }
for _, c := range wordSeps { for _, c := range wordSeps {
@@ -136,10 +159,11 @@ var (
"1st", "First", "1st", "First",
"♡ 3D Nail Art", "NailArt", "♡ 3D Nail Art", "NailArt",
".", "", ".", "",
"\u2019", "",
"&", "And", "&", "And",
"'s", "s", "'s", "s",
"∞", "Infinity", "∞", "Infinity",
"×", "X", "\u00d7", "X",
"◎", "Lv2", "◎", "Lv2",
} }
for _, c := range wordSeps { for _, c := range wordSeps {
@@ -159,6 +183,7 @@ func kkenum(name string) string {
} }
name = strings.ToUpper(name[:1]) + name[1:] name = strings.ToUpper(name[:1]) + name[1:]
if !unicode.IsLetter(rune(name[0])) { if !unicode.IsLetter(rune(name[0])) {
//lint:ignore ST1005 proper name
panic(fmt.Errorf("Koka enum variant %q (from %q) starts with a non-letter", name, orig)) panic(fmt.Errorf("Koka enum variant %q (from %q) starts with a non-letter", name, orig))
} }
for _, c := range name { for _, c := range name {

View File

@@ -23,6 +23,12 @@ var skillGroupSQL string
//go:embed skill.sql //go:embed skill.sql
var skillSQL string var skillSQL string
//go:embed race.sql
var raceSQL string
//go:embed saddle.sql
var saddleSQL string
type ( type (
Character struct{} Character struct{}
SkillGroup struct{} SkillGroup struct{}
@@ -191,6 +197,8 @@ type Skill struct {
Activations [2]SkillActivation Activations [2]SkillActivation
SPCost int SPCost int
InheritID int InheritID int
UniqueOwnerID int
UniqueOwner string
IconID int IconID int
Index int Index int
} }
@@ -304,8 +312,96 @@ func Skills(ctx context.Context, db *sqlitex.Pool) ([]Skill, error) {
}, },
SPCost: stmt.ColumnInt(47), SPCost: stmt.ColumnInt(47),
InheritID: stmt.ColumnInt(48), InheritID: stmt.ColumnInt(48),
IconID: stmt.ColumnInt(49), UniqueOwnerID: stmt.ColumnInt(49),
Index: stmt.ColumnInt(50), UniqueOwner: stmt.ColumnText(50),
IconID: stmt.ColumnInt(51),
Index: stmt.ColumnInt(52),
}
r = append(r, s)
}
return r, nil
}
type Race struct {
ID int
Name string
Grade int
ThumbnailID int
Primary int
Alternate 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),
Alternate: stmt.ColumnInt(5),
}
r = append(r, race)
}
return r, nil
}
type Saddle struct {
ID int
Name string
Races [3]int
Type int
Primary int
Alternate int
}
func Saddles(ctx context.Context, db *sqlitex.Pool) ([]Saddle, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for saddles: %w", err)
}
stmt, _, err := conn.PrepareTransient(saddleSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for saddles: %w", err)
}
defer stmt.Finalize()
var r []Saddle
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping saddles: %w", err)
}
if !ok {
break
}
s := Saddle{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Races: [3]int{stmt.ColumnInt(2), stmt.ColumnInt(3), stmt.ColumnInt(4)},
Type: stmt.ColumnInt(5),
Primary: stmt.ColumnInt(6),
Alternate: stmt.ColumnInt(7),
} }
r = append(r, s) r = append(r, s)
} }

View File

@@ -20,7 +20,7 @@ func main() {
region string region string
) )
flag.StringVar(&mdb, "mdb", os.ExpandEnv(`$USERPROFILE\AppData\LocalLow\Cygames\Umamusume\master\master.mdb`), "`path` to Umamusume master.mdb") flag.StringVar(&mdb, "mdb", os.ExpandEnv(`$USERPROFILE\AppData\LocalLow\Cygames\Umamusume\master\master.mdb`), "`path` to Umamusume master.mdb")
flag.StringVar(&out, "o", `.\horse`, "`dir`ectory for output files") flag.StringVar(&out, "o", `horse`, "`dir`ectory for output files")
flag.StringVar(&region, "region", "global", "region the database is for (global, jp)") flag.StringVar(&region, "region", "global", "region the database is for (global, jp)")
flag.Parse() flag.Parse()
@@ -50,6 +50,8 @@ func main() {
trios []AffinityRelation trios []AffinityRelation
sg []NamedID[SkillGroup] sg []NamedID[SkillGroup]
skills []Skill skills []Skill
races []Race
saddles []Saddle
) )
eg.Go(func() error { eg.Go(func() error {
slog.Info("get characters") slog.Info("get characters")
@@ -81,6 +83,18 @@ func main() {
skills = r skills = r
return err return err
}) })
eg.Go(func() error {
slog.Info("get races")
r, err := Races(ctx, db)
races = r
return err
})
eg.Go(func() error {
slog.Info("get saddles")
s, err := Saddles(ctx, db)
saddles = s
return err
})
if err := eg.Wait(); err != nil { if err := eg.Wait(); err != nil {
slog.Error("load", slog.Any("err", err)) slog.Error("load", slog.Any("err", err))
os.Exit(1) os.Exit(1)
@@ -110,19 +124,39 @@ func main() {
return err return err
} }
gf, err := os.Create(filepath.Join(out, region, "skill.go")) gf, err := os.Create(filepath.Join(out, region, "skill.go"))
if err != nil {
return err
}
slog.Info("write skills") slog.Info("write skills")
return ExecSkill(t, region, sf, gf, sg, skills) return ExecSkill(t, region, sf, gf, sg, skills)
}) })
eg.Go(func() error { eg.Go(func() error {
sf, err := os.Create(filepath.Join(out, region, "skill-group.kk")) kf, err := os.Create(filepath.Join(out, region, "race.kk"))
if err != nil { if err != nil {
return err return err
} }
slog.Info("write skill groups") gf, err := os.Create(filepath.Join(out, region, "race.go"))
return ExecSkillGroupKK(t, region, sf, sg, skills) if err != nil {
return err
}
slog.Info("write races")
return ExecRace(t, region, kf, gf, races)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "saddle.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "saddle.go"))
if err != nil {
return err
}
slog.Info("write saddles")
return ExecSaddle(t, region, kf, gf, saddles)
}) })
if err := eg.Wait(); err != nil { if err := eg.Wait(); err != nil {
slog.Error("generate", slog.Any("err", err)) slog.Error("generate", slog.Any("err", err))
os.Exit(1)
} else { } else {
slog.Info("done") 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 $r.Alternate }}Alt{{ $r.ID }}{{ end }} RaceID = {{ $r.ID }} // {{ $r.Name }}
{{- end }}
)
var AllRaces = map[RaceID]Race{
{{- range $r := $.Races }}
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }}: {
ID: {{ $r.ID }},
Name: {{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ 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 $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ 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 $r.Alternate }}-Alt{{ $r.ID }}{{ 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 $r.Alternate }}-Alt{{ $r.ID }}{{ 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 $r.Alternate }}-Alt{{ $r.ID }}{{ end }},
{{- end }}
]
val name2id: rbmap<string, race-id> = rb-map/empty()
{{- range $r := $.Races }}
.set({{ printf "%q" $r.Name }}{{ if $r.Alternate }} ++ " (Alternate {{ $r.ID }})"{{ end }}, Race-id({{ $r.ID }}))
{{- end }}
// Get the race ID that has the given exact name.
// Alternate versions of races have an indication of their ID 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 an indication of their ID 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 $r.Alternate }} ++ " (Alternate {{ $r.ID }})"{{ 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 $r.Alternate }}
{{ $r.ID }} -> Race-id({{ $r.Primary }})
{{- end }}
{{- end }}
_ -> r
{{ end }}

14
horsegen/race.sql Normal file
View File

@@ -0,0 +1,14 @@
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",
ROW_NUMBER() OVER (PARTITION BY race_names.name ORDER BY race.id) - 1 AS "alternate"
FROM race
JOIN race_names ON race.id = race_names.id
WHERE race."group" = 1
ORDER BY race.id

View File

@@ -0,0 +1,27 @@
{{- define "go-saddle" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $s := $.Saddles }}
Saddle{{ goenum $s.Name }}{{ if $s.Alternate }}Alt{{ $s.Alternate }}{{ end }} SaddleID = {{ $s.ID }} // {{ $s.Name }}
{{- end }}
)
var AllSaddles = map[SaddleID]Saddle{
{{- range $s := $.Saddles }}
Saddle{{ goenum $s.Name }}{{ if $s.Alternate }}Alt{{ $s.Alternate }}{{ end }}: {
ID: {{ $s.ID }},
Name: {{ printf "%q" $s.Name }}{{ if $s.Alternate }} + " (Alternate {{ $s.Alternate }})"{{ end }},
Races: []RaceID{ {{- range $id := $s.Races }}{{ if $id }}{{ $id }}, {{ end }}{{ end -}} },
Type: SaddleType{{ if eq $s.Type 0 }}Honor{{ else if eq $s.Type 1 }}G3{{ else if eq $s.Type 2 }}G2{{ else if eq $s.Type 3 }}G1{{ else }}??? $s.Type={{ $s.Type }}{{ end }},
{{- if $s.Alternate }}
Primary: {{ $s.Primary }},
{{- end }}
},
{{- end }}
}
{{ end }}

View File

@@ -0,0 +1,69 @@
{{- define "koka-saddle" -}}
module horse/{{ $.Region }}/saddle
// Automatically generated with horsegen; DO NOT EDIT
import horse/game-id
pub import horse/race
pub import horse/{{ $.Region }}/race
// Enumeration of all saddles for type-safe programming.
pub type saddle
{{- range $s := $.Saddles }}
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.ID }}{{ end }}
{{- end }}
// Get the saddle ID for a saddle.
pub fun saddle-id(s: saddle): saddle-id
match s
{{- range $s := $.Saddles }}
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.ID }}{{ end }} -> Saddle-id({{ $s.ID }})
{{- end }}
// List of all saddles in ID order for easy iterating.
pub val all = [
{{- range $s := $.Saddles }}
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.ID }}{{ end }},
{{- end }}
]
// Get the name for a saddle.
// Alternate versions of saddles have an indication of their ID in their names.
// If no saddle matches the ID, the result contains the numeric ID.
pub fun show(s: saddle-id): string
match s.game-id
{{- range $s := $.Saddles }}
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.Alternate }} ++ " (Alternate {{ $s.ID }})"{{ end }}
{{- end }}
x -> "saddle " ++ x.show
// Get the list of races that entitle a horse to a saddle.
// If no saddle matches the ID, the result is the empty list.
pub fun races(s: saddle-id): list<race-id>
match s.game-id
{{- range $s := $.Saddles }}
{{ $s.ID }} -> [{{ range $id := $s.Races }}{{ if $id }}Race-id({{ $id }}), {{ end }}{{ end }}]
{{- end }}
_ -> []
// Get a saddle's type.
// If no saddle matches the ID, the result is Honor.
pub fun saddle-type(s: saddle-id): saddle-type
match s.game-id
{{- range $s := $.Saddles }}
{{ $s.ID }} -> {{ if eq $s.Type 0 }}Honor{{ else if eq $s.Type 1 }}G3-Win{{ else if eq $s.Type 2 }}G2-Win{{ else if eq $s.Type 3 }}G1-Win{{ else }}??? $s.Type={{ $s.Type }}{{ end }}
{{- end }}
_ -> Honor
// Get the primary ID for a saddle.
// For saddles which are the primary version, or if no saddle matches the given ID,
// the result is the input.
pub fun primary(s: saddle-id): saddle-id
match s.game-id
{{- range $s := $.Saddles }}
{{- if $s.Alternate }}
{{ $s.ID }} -> Saddle-id({{ $s.Primary }})
{{- end }}
{{- end }}
_ -> s
{{ end }}

20
horsegen/saddle.sql Normal file
View File

@@ -0,0 +1,20 @@
WITH saddle_names AS (
SELECT "index" AS id, "text" AS name
FROM text_data
WHERE category = 111
)
SELECT
s.id,
n.name,
ri1.id AS race1,
IFNULL(ri2.id, 0) AS race2,
IFNULL(ri3.id, 0) AS race3,
s.win_saddle_type,
MIN(s.id) OVER (PARTITION BY n.name) AS "primary",
ROW_NUMBER() OVER (PARTITION BY n.name ORDER BY s.id) - 1 AS "alternate"
FROM single_mode_wins_saddle s
JOIN race_instance ri1 ON s.race_instance_id_1 = ri1.id
LEFT JOIN race_instance ri2 ON s.race_instance_id_2 = ri2.id
LEFT JOIN race_instance ri3 ON s.race_instance_id_3 = ri3.id
LEFT JOIN saddle_names n ON s.id = n.id
ORDER BY s.id

View File

@@ -53,6 +53,9 @@ var AllSkills = map[SkillID]Skill{
{{- end }} {{- end }}
{{- end }} {{- end }}
}, },
{{- if $s.UniqueOwner }}
UniqueOwner: {{ printf "%q" $s.UniqueOwner }},
{{- end }}
{{- if $s.SPCost }} {{- if $s.SPCost }}
SPCost: {{ $s.SPCost }}, SPCost: {{ $s.SPCost }},
{{- end }} {{- end }}

View File

@@ -1,347 +1,233 @@
{{- define "koka-skill-group" -}}
module horse/{{ $.Region }}/skill-group
// Automatically generated with horsegen; DO NOT EDIT
// Skill groups.
// A skill group may contain white, circle, double-circle, gold, and purple skills
// for the same effect.
// Sparks that grant skills refer to a skill group.
pub type skill-group
{{- range $g := $.Groups }}
{{ kkenum $g.Name }}
{{- end }}
// Map a skill group to its ID.
pub fip fun skill-group/group-id(^sg: skill-group): int
match sg
{{- range $g := $.Groups }}
{{ kkenum $g.Name }} -> {{ $g.ID }}
{{- end }}
// Get the skill group for an ID.
pub fip(1) fun skill-group/from-id(^id: int): maybe<skill-group>
match id
{{- range $g := $.Groups }}
{{ $g.ID }} -> Just( {{- kkenum $g.Name -}} )
{{- end }}
_ -> Nothing
// Get the name for a skill group.
// Skill group names are the name of the base skill in the group.
pub fun skill-group/show(sg: skill-group): string
match sg
{{- range $g := $.Groups }}
{{ kkenum $g.Name }} -> {{ printf "%q" $g.Name }}
{{- end }}
// Compare two skill groups by ID order.
pub fip fun skill-group/order2(a: skill-group, b: skill-group): order2<skill-group>
match cmp(a.group-id, b.group-id)
Lt -> Lt2(a, b)
Eq -> Eq2(a)
Gt -> Gt2(a, b)
pub fun skill-group/(==)(a: skill-group, b: skill-group): bool
a.group-id == b.group-id
{{- end -}}
{{- define "koka-skill" -}} {{- define "koka-skill" -}}
module horse/{{ $.Region }}/skill module horse/{{ $.Region }}/skill
// Automatically generated with horsegen; DO NOT EDIT // Automatically generated with horsegen; DO NOT EDIT
import std/data/rb-map
import std/num/decimal import std/num/decimal
pub import horse/{{ $.Region }}/skill-group import horse/game-id
import horse/movement
pub import horse/skill
// Skill instances. // Enumeration of all skills for type-safe programming.
pub type skill pub type skill
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end -}} {{ kkenum $s.Name }}{{ if $s.InheritID }}-Inherit{{ end }}
{{- end }} {{- end }}
// Map a skill to its ID. // Get the skill ID for a skill.
pub fip fun skill/skill-id(^s: skill): int pub fun skill-id(s: skill): skill-id
match s match s
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }} -> {{ $s.ID }} {{ kkenum $s.Name }}{{ if $s.InheritID }}-Inherit{{ end }} -> Skill-id({{ $s.ID }})
{{- end }} {{- end }}
// Get the skill for an ID. // List of all skills in ID order for easy iterating.
pub fip(1) fun skill/from-id(^id: int): maybe<skill> pub val all = [
match id
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ $s.ID }} -> Just( {{- kkenum $s.Name -}}{{ if ne $s.InheritID 0 }}-Inherit{{ end -}} ) {{ kkenum $s.Name }}{{ if $s.InheritID }}-Inherit{{ end }},
{{- end }}
]
val name2id: rbmap<string, skill-id> = rb-map/empty()
{{- range $s := $.Skills }}
.set({{ printf "%q" $s.Name }}{{ if $s.InheritID }} ++ " (Inherited)"{{ end }}, Skill-id({{ $s.ID }}))
{{- end }}
// Get the skill ID that has the given exact name.
// Inherited skills have `" (Inherited)"` appended to their names.
// If no skill matches the name, the result is an invalid ID.
pub fun from-name(name: string): skill-id
name2id.lookup(name).default(Skill-id(0))
// Get the name for a skill.
// Inherited skills have `" (Inherited)"` appended to their names.
// If no skill matches the ID, the result is the numeric ID.
pub fun show(s: skill-id): string
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.InheritID }} ++ " (Inherited)"{{ end }}
{{- end }}
x -> "skill " ++ x.show
// Get the description for a skill.
// If no skill matches the ID, the result is the empty string.
pub fun description(s: skill-id): string
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ printf "%q" $s.Description }}
{{- end }}
_ -> ""
// Get the skill group ID for a skill.
// If no skill matches the ID, the result is an invalid ID.
pub fun group(s: skill-id): skill-group-id
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> Skill-group-id( {{- $s.GroupID -}} )
{{- end }}
_ -> Skill-group-id(0)
// Get the rarity of a skill.
// If no skill matches the ID, the result is Common.
pub fun rarity(s: skill-id): rarity
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ if eq $s.Rarity 1 }}Common{{ else if eq $s.Rarity 2 }}Rare{{ else if eq $s.Rarity 3 }}Unique-Low{{ else if eq $s.Rarity 4 }}Unique-Upgraded{{ else if eq $s.Rarity 5 }}Unique{{ else }}??? $s.Rarity={{ $s.Rarity }}{{ end }}
{{- end }}
_ -> Common
// Get the group rate of a skill.
// If no skill matches the ID, the result is 0.
pub fun group-rate(s: skill-id): int
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ $s.GroupRate }}
{{- end }}
_ -> 0
// Get the grade value of a skill.
// If no skill matches the ID, the result is 0.
pub fun grade-value(s: skill-id): int
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ $s.GradeValue }}
{{- end }}
_ -> 0
// Get whether a skill is a wit check.
// If no skill matches the ID, the result is False.
pub fun wit-check(s: skill-id): bool
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ if $s.WitCheck }}True{{ else }}False{{ end }}
{{- end }}
_ -> False
// Get the activations of a skill.
// If no skill matches the ID, the result is an empty list.
pub fun activations(s: skill-id): list<activation>
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> [
{{- range $a := $s.Activations }}
{{- if $a.Condition }}
Activation(
precondition = {{ printf "%q" $a.Precondition }},
condition = {{ printf "%q" $a.Condition }},
duration = {{ $a.Duration }}.decimal{{ if gt $a.Duration 0 }}(-4){{ end }},
cooldown = {{ $a.Cooldown }}.decimal{{ if gt $a.Cooldown 0 }}(-4){{ end }},
abilities = [
{{- range $abil := $a.Abilities }}
{{- if $abil.Type }}
Ability(
ability-type = {{ if eq $abil.Type 1 }}Passive-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 2 }}Passive-Stamina({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 3 }}Passive-Power({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 4 }}Passive-Guts({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 5 }}Passive-Wit({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 6 }}Great-Escape
{{- else if eq $abil.Type 8 }}Vision({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 9 }}HP({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 10 }}Gate-Delay({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 13 }}Frenzy({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 21 }}Current-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 27 }}Target-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 28 }}Lane-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 31 }}Accel({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 35 }}Lane-Change({{ $abil.Value }}.decimal(-4))
{{- else }}??? $abil.Type={{$abil.Type}}
{{- end }},
value-usage = {{ if eq $abil.ValueUsage 1 }}Direct
{{- else if eq $abil.ValueUsage 3 }}Team-Speed
{{- else if eq $abil.ValueUsage 4 }}Team-Stamina
{{- else if eq $abil.ValueUsage 5 }}Team-Power
{{- else if eq $abil.ValueUsage 6 }}Team-Guts
{{- else if eq $abil.ValueUsage 7 }}Team-Wit
{{- else if eq $abil.ValueUsage 8 }}Multiply-Random
{{- else if eq $abil.ValueUsage 9 }}Multiply-Random2
{{- else if eq $abil.ValueUsage 10 }}Climax
{{- else if eq $abil.ValueUsage 13 }}Max-Stat
{{- else if eq $abil.ValueUsage 14 }}Passive-Count
{{- else if eq $abil.ValueUsage 19 }}Front-Distance-Add
{{- else if eq $abil.ValueUsage 20 }}Midrace-Side-Block-Time
{{- else if eq $abil.ValueUsage 22 }}Speed-Scaling
{{- else if eq $abil.ValueUsage 23 }}Speed-Scaling2
{{- else if eq $abil.ValueUsage 24 }}Arc-Global-Potential
{{- else if eq $abil.ValueUsage 25 }}Max-Lead-Distance
{{- else }}??? $abil.ValueUsage={{ $abil.ValueUsage }}
{{- end }},
target = {{ if eq $abil.Target 1}}Self
{{- else if eq $abil.Target 4 }}Sympathizers
{{- else if eq $abil.Target 4 }}In-View
{{- else if eq $abil.Target 4 }}Frontmost
{{- else if eq $abil.Target 9 }}Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 10 }}Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 4 }}All-Teammates
{{- else if eq $abil.Target 18 }}Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- else if eq $abil.Target 19 }}Rushing-Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 20 }}Rushing-Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 21 }}Rushing-Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- else if eq $abil.Target 22 }}Specific-Character(Character-id({{ $abil.TargetValue }}))
{{- else if eq $abil.Target 23 }}Triggering
{{- end }}
),
{{- end }}
{{- end }}
]
),
{{- end }}
{{- end }}
]
{{- end }}
_ -> Nil
// Get the owner of a unique skill.
// If the skill is not unique, or if there is no skill with the given ID,
// the result is Nothing.
pub fun unique-owner(s: skill-id): maybe<trainee-id>
match s.game-id
{{- range $s := $.Skills }}
{{- if $s.UniqueOwnerID }}
{{ $s.ID }} -> Just(Trainee-id({{ $s.UniqueOwnerID }}))
{{- end }}
{{- end }} {{- end }}
_ -> Nothing _ -> Nothing
// Get the name of a skill. // Get the SP cost of a skill.
// Inherited skills have the same names as their original counterparts. // If there is no skill with the given ID, the result is 0.
pub fun skill/show(s: skill): string pub fun sp-cost(s: skill-id): int
match s match s.game-id
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }} -> {{ printf "%q" $s.Name }} {{ $s.ID }} -> {{ $s.SPCost }}
{{- end }} {{- end }}
_ -> 0
// Compare two skills by ID order. // Get the icon ID of a skill.
pub fip fun skill/order2(a: skill, b: skill): order2<skill> // If there is no skill with the given ID, the result is an invalid ID.
match cmp(a.skill-id, b.skill-id) pub fun icon-id(s: skill-id): skill-icon-id
Lt -> Lt2(a, b) match s.game-id
Eq -> Eq2(a) {{- range $s := $.Skills }}
Gt -> Gt2(a, b) {{ $s.ID }} -> Skill-icon-id({{ $s.IconID }})
{{- end }}
_ -> Skill-icon-id(0)
pub fun skill/(==)(a: skill, b: skill): bool // Get the name for a skill group.
a.skill-id == b.skill-id // Skill group names are the name of the base skill in the group.
// If there is no skill group with the given ID, the result is the numeric ID.
// Get the skills in a skill group. pub fun skill-group/show(sg: skill-group-id): string
pub fun skill-group/skills(g: skill-group): list<skill> match sg.game-id
match g
{{- range $g := $.Groups }} {{- range $g := $.Groups }}
{{ kkenum $g.Name }} -> [ {{- range $s := index $.Related $g.ID }}{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }}, {{ end }}] {{ $g.ID }} -> {{- printf "%q" $g.Name -}}
{{- end }} {{- end }}
x -> "skill group " ++ x.show
// Get complete skill info. // Get the list of skills in a skill group.
pub fun skill/detail(^s: skill): skill-detail pub fun skill-group/skills(sg: skill-group-id): list<skill-id>
match s match sg.game-id
{{- range $s := $.Skills }} {{- range $g := $.Groups }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }} -> {{ template "kk-render-skill-detail" $s }} {{ $g.ID }} -> [ {{- range $s := index $.Related $g.ID }}Skill-id({{ $s.ID }}), {{ end -}} ]
{{- end }} {{- end }}
_ -> Nil
// Details about a skill. {{- end }}
pub struct skill-detail
skill-id: int
name: string
description: string
group: maybe<skill-group>
rarity: rarity
group-rate: int
grade-value: int
wit-check: bool
activations: list<activation>
sp-cost: int
icon-id: int
// Automatically generated.
// Shows a string representation of the `skill-detail` type.
pub fun skill-detail/show(this : skill-detail) : e string
match this
Skill-detail(skill-id, name, description, group, rarity, group-rate, grade-value, wit-check, activations, sp-cost, icon-id) -> "Skill-detail(skill-id: " ++ skill-id.show ++ ", name: " ++ name.show ++ ", description: " ++ description.show ++ ", group: " ++ group.show ++ ", rarity: " ++ rarity.show ++ ", group-rate: " ++ group-rate.show ++ ", grade-value: " ++ grade-value.show ++ ", wit-check: " ++ wit-check.show ++ ", activations: " ++ activations.show ++ ", sp-cost: " ++ sp-cost.show ++ ", icon-id: " ++ icon-id.show ++ ")"
// Skill rarity.
pub type rarity
Common // white
Rare // gold
Unique-Low // 1*/2* unique
Unique-Upgraded // 3*+ unique on a trainee upgraded from 1*/2*
Unique // base 3* unique
pub fun rarity/show(r: rarity): string
match r
Common -> "Common"
Rare -> "Rare"
Unique-Low -> "Unique (1\u2606/2\u2606)"
Unique-Upgraded -> "Unique (3\u2606+ from 1\u2606/2\u2606 upgraded)"
Unique -> "Unique (3\u2606+)"
// Condition and precondition logic.
pub alias condition = string
// Activation conditions and effects.
// A skill has one or two activations.
pub struct activation
precondition: condition
condition: condition
duration: decimal // seconds
cooldown: decimal // seconds
abilities: list<ability> // one to three elements
pub fun activation/show(a: activation): string
match a
Activation("", condition, duration, _, abilities) | duration <= 0.decimal -> condition ++ " -> " ++ abilities.show
Activation("", condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s"
Activation("", condition, duration, cooldown, abilities) -> condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown"
Activation(precondition, condition, duration, _, abilities) | duration <= 0.decimal -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show
Activation(precondition, condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s"
Activation(precondition, condition, duration, cooldown, abilities) -> precondition ++ "-> " ++ condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown"
// Effects of activating a skill.
pub struct ability
ability-type: ability-type
value-usage: value-usage
target: target
pub fun ability/show(a: ability): string
match a
Ability(t, Direct, Self) -> t.show
Ability(t, Direct, target) -> t.show ++ " " ++ target.show
Ability(t, v, Self) -> t.show ++ " scaling by " ++ v.show
Ability(t, v, target) -> t.show ++ " " ++ target.show ++ " scaling by " ++ v.show
// Target of a skill activation effect.
pub type ability-type
Passive-Speed(bonus: decimal)
Passive-Stamina(bonus: decimal)
Passive-Power(bonus: decimal)
Passive-Guts(bonus: decimal)
Passive-Wit(bonus: decimal)
Great-Escape
Vision(bonus: decimal)
HP(rate: decimal)
Gate-Delay(rate: decimal)
Frenzy(add: decimal)
Current-Speed(rate: decimal)
Target-Speed(rate: decimal)
Lane-Speed(rate: decimal)
Accel(rate: decimal)
Lane-Change(rate: decimal)
pub fun ability-type/show(a: ability-type): string
match a
Passive-Speed(bonus) -> "passive " ++ bonus.show ++ " Speed"
Passive-Stamina(bonus) -> "passive " ++ bonus.show ++ " Stamina"
Passive-Power(bonus) -> "passive " ++ bonus.show ++ " Power"
Passive-Guts(bonus) -> "passive " ++ bonus.show ++ " Guts"
Passive-Wit(bonus) -> "passive " ++ bonus.show ++ " Wit"
Great-Escape -> "enable Great Escape style"
Vision(bonus) -> bonus.show ++ " vision"
HP(rate) | rate >= 0.decimal -> show(rate * 100.decimal) ++ "% HP recovery"
HP(rate) -> show(rate * 100.decimal) ++ "% HP loss"
Gate-Delay(rate) -> rate.show ++ "× gate delay"
Frenzy(add) -> add.show ++ "s longer Rushed"
Current-Speed(rate) -> show(rate * 100.decimal) ++ "% current speed"
Target-Speed(rate) -> show(rate * 100.decimal) ++ "% target speed"
Lane-Speed(rate) -> show(rate * 100.decimal) ++ "% lane speed"
Accel(rate) -> show(rate * 100.decimal) ++ "% acceleration"
Lane-Change(rate) -> rate.show ++ " course width movement"
// Special scaling for skill activation effects.
pub type value-usage
Direct
Team-Speed
Team-Stamina
Team-Power
Team-Guts
Team-Wit
Multiply-Random
pub fun value-usage/show(v: value-usage): string
match v
Direct -> "no scaling"
Team-Speed -> "team's Speed"
Team-Stamina -> "team's Stamina"
Team-Power -> "team's Power"
Team-Guts -> "team's Guts"
Team-Wit -> "team's Wit"
Multiply-Random -> "random multiplier (0×, 0.02×, or 0.04×)"
// Who a skill activation targets.
pub type target
Self
In-View
Ahead(limit: int)
Behind(limit: int)
Style(style: style)
Rushing-Ahead(limit: int)
Rushing-Behind(limit: int)
Rushing-Style(style: style)
pub fun target/show(t: target): string
match t
Self -> "self"
In-View -> "others in field of view"
Ahead(limit) | limit >= 18 -> "others ahead"
Ahead(limit) -> "next " ++ limit.show ++ " others ahead"
Behind(limit) | limit >= 18 -> "others behind"
Behind(limit) -> "next " ++ limit.show ++ " others behind"
Style(Front-Runner) -> "other Front Runners"
Style(Pace-Chaser) -> "other Pace Chasers"
Style(Late-Surger) -> "other Late Surgers"
Style(End-Closer) -> "other End Closers"
Rushing-Ahead(limit) | limit >= 18 -> "others rushing ahead"
Rushing-Ahead(limit) -> "next " ++ limit.show ++ " others rushing ahead"
Rushing-Behind(limit) | limit >= 18 -> "others rushing behind"
Rushing-Behind(limit) -> "next " ++ limit.show ++ " others rushing behind"
Rushing-Style(Front-Runner) -> "rushing Front Runners"
Rushing-Style(Pace-Chaser) -> "rushing Pace Chasers"
Rushing-Style(Late-Surger) -> "rushing Late Surgers"
Rushing-Style(End-Closer) -> "rushing End Closers"
// Running style for skill targets.
{{- /* TODO(zeph): there is definitely a better place for this to live */}}
pub type style
Front-Runner
Pace-Chaser
Late-Surger
End-Closer
{{- end -}}
{{ define "kk-render-skill-detail" }}
{{- /* Call with Skill structure as argument. */ -}}
Skill-detail(skill-id = {{ $.ID -}}
, name = {{ printf "%q" $.Name -}}
, description = {{ printf "%q" $.Description -}}
, group = {{ if ne $.GroupName "" }}Just({{ kkenum $.GroupName }}){{ else }}Nothing{{ end -}}
, rarity = {{ if eq $.Rarity 1 }}Common{{ else if eq $.Rarity 2 }}Rare{{ else if eq $.Rarity 3 }}Unique-Low{{ else if eq $.Rarity 4 }}Unique-Upgraded{{ else if eq $.Rarity 5 }}Unique{{ else }}??? $.Rarity={{ $.Rarity }}{{ end -}}
, group-rate = {{ $.GroupRate -}}
, grade-value = {{ $.GradeValue -}}
, wit-check = {{ if $.WitCheck }}True{{ else }}False{{ end -}}
, activations = [
{{- range $a := $.Activations -}}
{{- if ne $a.Condition "" -}}
Activation(precondition = {{ printf "%q" $a.Precondition -}}
, condition = {{ printf "%q" $a.Condition -}}
, duration = {{ $a.Duration -}}{{ if gt $a.Duration 0 }}.decimal(-4){{ else }}.decimal{{ end -}}
, cooldown = {{ $a.Cooldown -}}{{ if gt $a.Cooldown 0 }}.decimal(-4){{ else }}.decimal{{ end -}}
, abilities = [
{{- range $abil := $a.Abilities -}}
{{- if ne $abil.Type 0 -}}
Ability(ability-type =
{{- if eq $abil.Type 1 -}}Passive-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 2 -}}Passive-Stamina({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 3 -}}Passive-Power({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 4 -}}Passive-Guts({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 5 -}}Passive-Wit({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 6 -}}Great-Escape
{{- else if eq $abil.Type 8 -}}Vision({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 9 -}}HP({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 10 -}}Gate-Delay({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 13 -}}Frenzy({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 21 -}}Current-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 27 -}}Target-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 28 -}}Lane-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 31 -}}Accel({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 35 -}}Lane-Change({{ $abil.Value }}.decimal(-4))
{{- else -}}??? $abil.Type={{$abil.Type}}
{{- end -}}
, value-usage =
{{- if eq $abil.ValueUsage 1 -}}Direct
{{- else if eq $abil.ValueUsage 3 -}}Team-Speed
{{- else if eq $abil.ValueUsage 4 -}}Team-Stamina
{{- else if eq $abil.ValueUsage 5 -}}Team-Power
{{- else if eq $abil.ValueUsage 6 -}}Team-Guts
{{- else if eq $abil.ValueUsage 7 -}}Team-Wit
{{- else if eq $abil.ValueUsage 8 -}}Multiply-Random
{{- else -}}??? $abil.ValueUsage={{ $abil.ValueUsage }}
{{- end -}}
, target =
{{- if eq $abil.Target 1 -}}Self
{{- else if eq $abil.Target 4 -}}In-View
{{- else if eq $abil.Target 9 -}}Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 10 -}}Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 18 -}}Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- else if eq $abil.Target 19 -}}Rushing-Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 20 -}}Rushing-Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 21 -}}Rushing-Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- end -}}
),
{{- end -}}
{{- end -}}
]),
{{- end -}}
{{- end -}}
], sp-cost = {{ $.SPCost -}}
, icon-id = {{ $.IconID -}}
)
{{- end -}}

View File

@@ -12,6 +12,21 @@ WITH skill_names AS (
FROM skill_data d FROM skill_data d
JOIN skill_names n ON d.id = n.id JOIN skill_names n ON d.id = n.id
WHERE group_rate = 1 WHERE group_rate = 1
), card_name AS (
SELECT
"index" AS "id",
"text" AS "name"
FROM text_data n
WHERE category = 4
), card_unique AS (
SELECT DISTINCT
ss.skill_id1 AS unique_id,
card_name.id AS owner_id,
card_name.name
FROM card_data card
JOIN card_name ON card.id = card_name.id
JOIN card_rarity_data rd ON card.id = rd.card_id
JOIN skill_set ss ON rd.skill_set = ss.id
) )
SELECT SELECT
d.id, d.id,
@@ -67,6 +82,8 @@ SELECT
d.target_value_2_3, d.target_value_2_3,
IFNULL(p.need_skill_point, 0) AS sp_cost, IFNULL(p.need_skill_point, 0) AS sp_cost,
d.unique_skill_id_1, d.unique_skill_id_1,
COALESCE(u.owner_id, iu.owner_id, 0) AS unique_owner_id,
COALESCE(u.name, iu.name, '') AS unique_owner,
d.icon_id, d.icon_id,
ROW_NUMBER() OVER (ORDER BY d.id) - 1 AS "index" ROW_NUMBER() OVER (ORDER BY d.id) - 1 AS "index"
FROM skill_data d FROM skill_data d
@@ -74,4 +91,6 @@ FROM skill_data d
LEFT JOIN skill_data ud ON d.unique_skill_id_1 = ud.id LEFT JOIN skill_data ud ON d.unique_skill_id_1 = ud.id
LEFT JOIN skill_groups g ON d.group_id = g.group_id LEFT JOIN skill_groups g ON d.group_id = g.group_id
LEFT JOIN single_mode_skill_need_point p ON d.id = p.id LEFT JOIN single_mode_skill_need_point p ON d.id = p.id
LEFT JOIN card_unique u ON d.id = u.unique_id
LEFT JOIN card_unique iu ON d.unique_skill_id_1 = iu.unique_id
ORDER BY d.id ORDER BY d.id