summaryrefslogtreecommitdiff
path: root/golang/pkg/agent/trading.go
blob: e84cef4c461dadde71b9b0b1837cb4e57541f0cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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)
}