diff options
Diffstat (limited to 'golang/pkg/agent')
-rw-r--r-- | golang/pkg/agent/contracting.go | 54 | ||||
-rw-r--r-- | golang/pkg/agent/trading.go | 139 | ||||
-rw-r--r-- | golang/pkg/agent/utils.go | 18 | ||||
-rw-r--r-- | golang/pkg/agent/visit.go | 12 |
4 files changed, 205 insertions, 18 deletions
diff --git a/golang/pkg/agent/contracting.go b/golang/pkg/agent/contracting.go index 6d67d6d..ecd896a 100644 --- a/golang/pkg/agent/contracting.go +++ b/golang/pkg/agent/contracting.go @@ -15,7 +15,7 @@ func (a *agent) autoContracting(ship *model.Ship) { return } for _, contract := range contracts { - if contract.Fullfilled { + if contract.Fulfilled { continue } now := time.Now() @@ -27,11 +27,17 @@ func (a *agent) autoContracting(ship *model.Ship) { } } a.channel <- fmt.Errorf("negotiating new contracts is not implemented yet") - // TODO - //for { - // negotiate - // runContract - //} + for { + contract, err := a.client.NegotiateContract(ship) + if err != nil { + a.channel <- fmt.Errorf("failed to negotiate contract: %w", err) + return + } + if err := a.runContract(contract, ship); err != nil { + a.channel <- fmt.Errorf("failed to run contract %s: %w", contract.Id, err) + return + } + } } func (a *agent) runContract(contract *model.Contract, ship *model.Ship) error { @@ -51,15 +57,33 @@ func (a *agent) runContract(contract *model.Contract, ship *model.Ship) error { } func (a *agent) runProcurement(contract *model.Contract, ship *model.Ship) error { - deliveryCargo := contract.Terms.Deliver[0].TradeSymbol - deliveryWaypoint, err := a.client.GetWaypoint(contract.Terms.Deliver[0].DestinationSymbol, a.db) - if err != nil { - return fmt.Errorf("failed to get delivery waypoint: %w", err) + if contract.Fulfilled { + return nil + } + deliver := contract.Terms.Deliver[0] + // make sure we are not carrying useless stuff + if err := a.sellEverythingExcept(ship, deliver.TradeSymbol); err != nil { + return fmt.Errorf("failed to sell everything except %s for ship %s: %w", deliver.TradeSymbol, ship.Symbol, err) } - for !contract.Fullfilled { - _ = deliveryCargo - _ = deliveryWaypoint - return fmt.Errorf("not implemented") + // procure the desired goods + if ship.Cargo.Units < min(deliver.UnitsRequired-deliver.UnitsFulfilled, ship.Cargo.Capacity) { + if err := a.buyTradeGood(ship, deliver.TradeSymbol); err != nil { + return fmt.Errorf("failed to buy trade good %s with ship %s: %w", deliver.TradeSymbol, ship.Symbol, err) + } + } + // deliver the goods + if err := a.client.Navigate(ship, deliver.DestinationSymbol, a.db); err != nil { + return fmt.Errorf("failed to navigate to %s: %w", deliver.DestinationSymbol, err) + } + if err := a.client.Deliver(contract, ship, a.db); err != nil { + return fmt.Errorf("failed to deliver: %w", err) + } + deliver = contract.Terms.Deliver[0] + if deliver.UnitsRequired == deliver.UnitsFulfilled { + if err := a.client.Fulfill(contract, a.db); err != nil { + return fmt.Errorf("failed to fulfill: %w", err) + } + return nil } - return fmt.Errorf("not implemented") + return a.runProcurement(contract, ship) } diff --git a/golang/pkg/agent/trading.go b/golang/pkg/agent/trading.go new file mode 100644 index 0000000..e84cef4 --- /dev/null +++ b/golang/pkg/agent/trading.go @@ -0,0 +1,139 @@ +package agent + +import ( + "fmt" + "slices" + + "git.adyxax.org/adyxax/spacetraders/golang/pkg/model" +) + +type TradeGoodNotFoundError struct{} + +func (err *TradeGoodNotFoundError) Error() string { + return "trade good not found" +} + +func (a *agent) buyTradeGood(ship *model.Ship, tradeGoodToBuy string) error { + if ship.Cargo.Units == ship.Cargo.Capacity { + return nil + } + // list markets would sell our goods + markets, err := a.listMarketsInSystem(ship.Nav.SystemSymbol) + if err != nil { + return fmt.Errorf("failed to list markets in system %s: %w", ship.Nav.SystemSymbol, err) + } + markets = slices.DeleteFunc(markets, func(market model.Market) bool { + for _, item := range market.Exports { + if item.Symbol == tradeGoodToBuy { + return false + } + } + return true + }) + if len(markets) == 0 { + return &TradeGoodNotFoundError{} + } + // find the closest place to buy TODO + waypoint, err := a.client.GetWaypoint(ship.Nav.WaypointSymbol, a.db) + if err != nil { + return fmt.Errorf("failed to get nav waypoint %s: %w", ship.Nav.WaypointSymbol, err) + } + waypoints := make([]model.Waypoint, 0) + for i := range markets { + waypoint, err := a.client.GetWaypoint(markets[i].Symbol, a.db) + if err != nil { + return fmt.Errorf("failed to get waypoint %s: %w", markets[i].Symbol, err) + } + waypoints = append(waypoints, *waypoint) + } + sortByDistanceFrom(waypoint, waypoints) + // Go there and refresh our market data + if err := a.client.Navigate(ship, waypoints[0].Symbol, a.db); err != nil { + return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err) + } + market, err := a.client.GetMarket(waypoints[0].Symbol, a.db) + if err != nil { + return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err) + } + // Buy until full + for _, tradeGood := range market.TradeGoods { + if tradeGood.Type == "EXPORT" && tradeGood.Symbol == tradeGoodToBuy { + for ship.Cargo.Units < ship.Cargo.Capacity { + increment := min(ship.Cargo.Capacity-ship.Cargo.Units, tradeGood.TradeVolume) + if err := a.client.Purchase(ship, tradeGoodToBuy, increment, a.db); err != nil { + return fmt.Errorf("failed to purchase %d units of %s: %w", increment, tradeGoodToBuy, err) + } + } + break + } + } + return a.buyTradeGood(ship, tradeGoodToBuy) +} + +func (a *agent) sellEverythingExcept(ship *model.Ship, keep string) error { + // First lets see what we need to sell + cargo := ship.Cargo.Inventory + cargo = slices.DeleteFunc(cargo, func(inventory model.Inventory) bool { + return inventory.Symbol == keep + }) + if len(cargo) == 0 { + return nil + } + // list markets would buy our goods + markets, err := a.listMarketsInSystem(ship.Nav.SystemSymbol) + if err != nil { + return fmt.Errorf("failed to list markets in system %s: %w", ship.Nav.SystemSymbol, err) + } + markets = slices.DeleteFunc(markets, func(market model.Market) bool { + for _, item := range market.Imports { + for _, cargoItem := range cargo { + if item.Symbol == cargoItem.Symbol { + return false + } + } + } + return true + }) + if len(markets) == 0 { + return nil + } + // find the closest place to sell something TODO + waypoint, err := a.client.GetWaypoint(ship.Nav.WaypointSymbol, a.db) + if err != nil { + return fmt.Errorf("failed to get nav waypoint %s: %w", ship.Nav.WaypointSymbol, err) + } + waypoints := make([]model.Waypoint, 0) + for i := range markets { + waypoint, err := a.client.GetWaypoint(markets[i].Symbol, a.db) + if err != nil { + return fmt.Errorf("failed to get waypoint %s: %w", markets[i].Symbol, err) + } + waypoints = append(waypoints, *waypoint) + } + sortByDistanceFrom(waypoint, waypoints) + // Go there and refresh our market data + if err := a.client.Navigate(ship, waypoints[0].Symbol, a.db); err != nil { + return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err) + } + market, err := a.client.GetMarket(waypoints[0].Symbol, a.db) + if err != nil { + return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err) + } + // sell everything we can + for _, cargoItem := range cargo { + units := cargoItem.Units + for _, tradeGood := range market.TradeGoods { + if tradeGood.Type == "IMPORT" && tradeGood.Symbol == cargoItem.Symbol { + for units > 0 { + increment := min(units, tradeGood.TradeVolume) + if err := a.client.Sell(ship, cargoItem.Symbol, increment, a.db); err != nil { + return fmt.Errorf("failed to sell %d units of %s: %w", units, cargoItem.Symbol, err) + } + units = units - increment + } + break + } + } + } + return a.sellEverythingExcept(ship, keep) +} diff --git a/golang/pkg/agent/utils.go b/golang/pkg/agent/utils.go index 274e973..b274f43 100644 --- a/golang/pkg/agent/utils.go +++ b/golang/pkg/agent/utils.go @@ -40,6 +40,22 @@ func (a *agent) listWaypointsInSystemWithTrait(systemSymbol string, trait string return waypoints, nil } +func (a *agent) listMarketsInSystem(systemSymbol string) ([]model.Market, error) { + waypoints, err := a.listWaypointsInSystemWithTrait(systemSymbol, "MARKETPLACE") + if err != nil { + return nil, fmt.Errorf("failed to list waypoints in system %s with trait MARKETPLACE: %w", systemSymbol, err) + } + var markets []model.Market + for i := range waypoints { + market, err := a.client.GetMarket(waypoints[i].Symbol, a.db) + if err != nil { + return nil, fmt.Errorf("failed to get market %s: %w", waypoints[i].Symbol, err) + } + markets = append(markets, *market) + } + return markets, nil +} + func (a *agent) listShipyardsInSystem(systemSymbol string) ([]model.Shipyard, error) { waypoints, err := a.listWaypointsInSystemWithTrait(systemSymbol, "SHIPYARD") if err != nil { @@ -47,7 +63,7 @@ func (a *agent) listShipyardsInSystem(systemSymbol string) ([]model.Shipyard, er } var shipyards []model.Shipyard for i := range waypoints { - shipyard, err := a.client.GetShipyard(&waypoints[i], a.db) + shipyard, err := a.client.GetShipyard(waypoints[i].Symbol, a.db) if err != nil { return nil, fmt.Errorf("failed to get shipyard %s: %w", waypoints[i].Symbol, err) } diff --git a/golang/pkg/agent/visit.go b/golang/pkg/agent/visit.go index 4d79fef..3004553 100644 --- a/golang/pkg/agent/visit.go +++ b/golang/pkg/agent/visit.go @@ -39,9 +39,17 @@ func (a *agent) visitAllShipyards(ship *model.Ship) error { if err := a.client.Navigate(ship, waypoints[0].Symbol, a.db); err != nil { return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err) } - if _, err := a.client.GetShipyard(&waypoints[0], a.db); err != nil { + if _, err := a.client.GetShipyard(waypoints[0].Symbol, a.db); err != nil { return fmt.Errorf("failed to get shipyard %s: %w", waypoints[0].Symbol, err) } - // TODO get market data + // If this waypoint is also a marketplace, get its data + for _, trait := range waypoints[0].Traits { + if trait.Symbol == "MARKETPLACE" { + if _, err := a.client.GetMarket(waypoints[0].Symbol, a.db); err != nil { + return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err) + } + break + } + } return a.visitAllShipyards(ship) } |