From 748b2a1f59f02cb12d608567aadbef748fcd04e4 Mon Sep 17 00:00:00 2001 From: Lucas Kacher Date: Wed, 7 Aug 2024 23:12:17 -0700 Subject: [PATCH 1/2] feat: allow characters to choose what to gather --- cmd/engine/main.go | 31 +++++++++++++++++++++++++---- go.mod | 3 +++ internal/actions/state.go | 10 +++++++++- internal/engine/decision.go | 2 +- internal/engine/gather.go | 6 +++--- internal/math/simple.go | 8 ++++++++ internal/math/simple_test.go | 27 +++++++++++++++++++++++++ internal/models/character.go | 38 +++++++++++++++++++++++++++++++++++- 8 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 internal/math/simple.go create mode 100644 internal/math/simple_test.go diff --git a/cmd/engine/main.go b/cmd/engine/main.go index acdf484..a752180 100644 --- a/cmd/engine/main.go +++ b/cmd/engine/main.go @@ -23,9 +23,17 @@ func init() { } } +const ( + configFlag = "config" + tokenFlag = "token" + characterFlag = "character" +) + func main() { slog.Info("starting artifacts-mmo game engine") - var config = flag.String("config", "", "path to config file") + config := flag.String(configFlag, "", "path to config file") + _ = flag.String(tokenFlag, "", "API token") + _ = flag.String(characterFlag, "", "character name") flag.Parse() err := initViper(*config) @@ -33,6 +41,11 @@ func main() { log.Fatal(err) } + err = bindFlags([]string{configFlag, tokenFlag, characterFlag}) + if err != nil { + log.Fatal(err) + } + v := viper.GetViper() r, err := actions.NewDefaultRunner(v.GetString("token")) @@ -41,20 +54,30 @@ func main() { } ctx := context.Background() - character := v.GetString("character") + c := v.GetString(characterFlag) - err = blockInitialAction(ctx, r, character) + err = blockInitialAction(ctx, r, c) if err != nil { log.Fatal(err) } slog.Info("starting BuildInventory engine") - err = engine.BuildInventory(ctx, r, character) + err = engine.BuildInventory(ctx, r, c) if err != nil { log.Fatal(err) } } +func bindFlags(flags []string) error { + for _, f := range flags { + err := viper.BindPFlag(f, flag.Lookup(f)) + if err != nil { + return fmt.Errorf("failed to bind flag: %w", err) + } + } + return nil +} + func blockInitialAction(ctx context.Context, r *actions.Runner, character string) error { c, err := r.GetMyCharacterInfo(ctx, character) if err != nil { diff --git a/go.mod b/go.mod index 6ab09b4..ded332a 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,12 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 ) require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.5.0 // indirect @@ -20,6 +22,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/internal/actions/state.go b/internal/actions/state.go index cb8c28f..db73beb 100644 --- a/internal/actions/state.go +++ b/internal/actions/state.go @@ -97,7 +97,15 @@ func (r *Runner) GetMonsters(ctx context.Context, min, max int) (models.Monsters return monsters, nil } -func (r *Runner) GetResources(ctx context.Context, skill string, min, max int) (models.Resources, error) { +func (r *Runner) GetResources(ctx context.Context, skill client.ResourceSchemaSkill, min, max int) (models.Resources, error) { + if min < 0 { + min = 0 + } + + if max < 0 { + max = 0 + } + s := client.GetAllResourcesResourcesGetParamsSkill(skill) resp, err := r.Client.GetAllResourcesResourcesGetWithResponse(ctx, &client.GetAllResourcesResourcesGetParams{ diff --git a/internal/engine/decision.go b/internal/engine/decision.go index f45ef55..d638ce2 100644 --- a/internal/engine/decision.go +++ b/internal/engine/decision.go @@ -22,7 +22,7 @@ func BuildInventory(ctx context.Context, r *actions.Runner, character string) er return fmt.Errorf("get character info: %w", err) } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() currentIndex := 1 diff --git a/internal/engine/gather.go b/internal/engine/gather.go index 0482163..893912f 100644 --- a/internal/engine/gather.go +++ b/internal/engine/gather.go @@ -28,9 +28,8 @@ func Gather(ctx context.Context, r *actions.Runner, character string) error { return err } - // it's in 10s, so we don't want to go lower than the band - minLevel := c.WoodcuttingLevel - 10 - resourceInfo, err := r.GetResources(ctx, string(client.Woodcutting), minLevel, c.WoodcuttingLevel) + skill := c.ChooseWeakestSkill() + resourceInfo, err := r.GetResources(ctx, skill.Code, skill.MinLevel, skill.CurrentLevel) if err != nil { slog.Error("failed to get resources", "error", err) return err @@ -39,6 +38,7 @@ func Gather(ctx context.Context, r *actions.Runner, character string) error { loc := models.LocationsToMap(resourceLoations) res := models.ResourcesToMap(resourceInfo) // TODO there are more than one resource available, we should move to the one closet to the bank + // implement with manhattan distance res.FindResources(loc) resources := res.ToSlice() diff --git a/internal/math/simple.go b/internal/math/simple.go new file mode 100644 index 0000000..6376fea --- /dev/null +++ b/internal/math/simple.go @@ -0,0 +1,8 @@ +package math + +func Max(x, y int) int { + if x < y { + return y + } + return x +} diff --git a/internal/math/simple_test.go b/internal/math/simple_test.go new file mode 100644 index 0000000..e89e1e3 --- /dev/null +++ b/internal/math/simple_test.go @@ -0,0 +1,27 @@ +package math + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMax(t *testing.T) { + tests := []struct { + a int + b int + expected int + }{ + {0, 1, 1}, + {0, -1, 0}, + {0, 10, 10}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%d,%d", tt.a, tt.b), func(t *testing.T) { + res := Max(tt.a, tt.b) + assert.Equal(t, tt.expected, res) + }) + } +} diff --git a/internal/models/character.go b/internal/models/character.go index 45c40a3..df2626f 100644 --- a/internal/models/character.go +++ b/internal/models/character.go @@ -1,11 +1,15 @@ package models import ( + "cmp" "fmt" "log/slog" + "slices" "time" "github.com/promiseofcake/artifactsmmo-go-client/client" + + "github.com/promiseofcake/artifactsmmo-engine/internal/math" ) // Character is our representation of the Player's character @@ -24,6 +28,38 @@ func (c Character) CountInventory() int { return count } +type CharacterSkill struct { + Code client.ResourceSchemaSkill + CurrentLevel int + MinLevel int +} + +func (c Character) ChooseWeakestSkill() CharacterSkill { + skills := []CharacterSkill{ + { + Code: client.ResourceSchemaSkillWoodcutting, + CurrentLevel: c.WoodcuttingLevel, + MinLevel: math.Max(0, c.WoodcuttingLevel-10), + }, + { + Code: client.ResourceSchemaSkillMining, + CurrentLevel: c.MiningLevel, + MinLevel: math.Max(0, c.MiningLevel-10), + }, + { + Code: client.ResourceSchemaSkillFishing, + CurrentLevel: c.FishingLevel, + MinLevel: math.Max(0, c.FishingLevel-10), + }, + } + + slices.SortFunc(skills, func(a, b CharacterSkill) int { + return cmp.Compare(a.CurrentLevel, b.CurrentLevel) + }) + + return skills[0] +} + // GetCooldownDuration returns the time.Duration remaining on the character for cooldown func (c Character) GetCooldownDuration() (time.Duration, error) { t, err := c.CooldownExpiration.AsCharacterSchemaCooldownExpiration0() @@ -46,7 +82,7 @@ func (c Character) GetPosition() Coords { func (c Character) ShouldBank() bool { percentFull := float64(c.CountInventory()) / float64(c.InventoryMaxItems) result := []any{"percent_full", percentFull} - if percentFull > 0.7 { + if percentFull > 0.9 { slog.Debug("Character should bank", result...) return true } else { From 1308f5e019b839fc89bbac56baecbfdcf85648b8 Mon Sep 17 00:00:00 2001 From: Lucas Kacher Date: Wed, 7 Aug 2024 23:25:09 -0700 Subject: [PATCH 2/2] feat: add math / tests for distane --- internal/models/map.go | 8 ++++++++ internal/models/map_test.go | 27 +++++++++++++++++++++++++++ internal/models/resource.go | 8 ++++---- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 internal/models/map_test.go diff --git a/internal/models/map.go b/internal/models/map.go index 199998b..88b9458 100644 --- a/internal/models/map.go +++ b/internal/models/map.go @@ -1,7 +1,15 @@ package models +import "math" + // Coords defines X, Y map coordinates as signed integers type Coords struct { X int Y int } + +// CalculateDistance determines the number of moves (distance) to get +// from one Coords to a second Coords using the Manhattan distance forumla +func CalculateDistance(one, two Coords) int { + return int(math.Abs(float64(one.X-two.X)) + math.Abs(float64(one.Y-two.Y))) +} diff --git a/internal/models/map_test.go b/internal/models/map_test.go new file mode 100644 index 0000000..ad10216 --- /dev/null +++ b/internal/models/map_test.go @@ -0,0 +1,27 @@ +package models + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCalculateDistance(t *testing.T) { + tests := []struct { + one Coords + two Coords + expected int + }{ + {Coords{0, 0}, Coords{0, 0}, 0}, + {Coords{1, 1}, Coords{0, 0}, 2}, + {Coords{-5, 1}, Coords{0, 0}, 6}, + } + + for n, tt := range tests { + t.Run(fmt.Sprintf("%d", n), func(t *testing.T) { + res := CalculateDistance(tt.one, tt.two) + assert.Equal(t, tt.expected, res) + }) + } +} diff --git a/internal/models/resource.go b/internal/models/resource.go index dd7f822..06332c3 100644 --- a/internal/models/resource.go +++ b/internal/models/resource.go @@ -28,11 +28,11 @@ func resourcePK(resource Resource) string { } func ResourcesToMap(resources Resources) ResourceMap { - monsterMap := make(ResourceMap) - for _, m := range resources { - monsterMap[resourcePK(m)] = &m + resourceMap := make(ResourceMap) + for _, r := range resources { + resourceMap[resourcePK(r)] = &r } - return monsterMap + return resourceMap } func (r ResourceMap) FindResources(l LocationMap) {