diff --git a/packages/cli/bin/fleet_value.dart b/packages/cli/bin/fleet_value.dart index 140686a1..8f533a9a 100644 --- a/packages/cli/bin/fleet_value.dart +++ b/packages/cli/bin/fleet_value.dart @@ -22,10 +22,13 @@ int _costOutMounts( } /// Returns a map of ship frame type to count in fleet. -Map _shipTypeCounts(List ships) { +Map _shipTypeCounts( + ShipyardShipCache shipyardShips, + List ships, +) { final typeCounts = {}; for (final ship in ships) { - final type = shipTypeFromFrame(ship.frame.symbol)!; + final type = shipyardShips.shipTypeFromFrame(ship.frame.symbol)!; typeCounts[type] = (typeCounts[type] ?? 0) + 1; } return typeCounts; @@ -35,15 +38,17 @@ Future command(FileSystem fs, ArgResults argResults) async { final shipCache = ShipCache.loadCached(fs)!; final marketPrices = MarketPrices.load(fs); final shipyardPrices = ShipyardPrices.load(fs); + final shipyardShips = ShipyardShipCache.load(fs); logger ..info('Estimating fleet value at current median prices.') ..info('Excluding intial ships.'); final purchasedShips = shipCache.ships.skip(2).toList(); - final purchaseShipTypes = - purchasedShips.map((s) => shipTypeFromFrame(s.frame.symbol)!).toList(); - final purchaseShipTypeCounts = _shipTypeCounts(purchasedShips); + final purchaseShipTypes = purchasedShips + .map((s) => shipyardShips.shipTypeFromFrame(s.frame.symbol)!) + .toList(); + final purchaseShipTypeCounts = _shipTypeCounts(shipyardShips, purchasedShips); final shipTypes = purchaseShipTypeCounts.keys.toList() ..sortBy((t) => t.value); for (final type in shipTypes) { diff --git a/packages/cli/bin/simulate.dart b/packages/cli/bin/simulate.dart index be0505e2..c2483f2f 100644 --- a/packages/cli/bin/simulate.dart +++ b/packages/cli/bin/simulate.dart @@ -126,6 +126,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final agentCache = AgentCache.loadCached(fs)!; final shipCache = ShipCache.loadCached(fs)!; final shipyardPrices = ShipyardPrices.load(fs); + final shipyardShips = ShipyardShipCache.load(fs); final hq = agentCache.headquarters(systemsCache); final hqMine = systemsCache @@ -171,7 +172,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final ship = exampleShip( shipCache, - frame: shipFrameFromType(haulerType)!, + frame: shipyardShips.shipFrameFromType(haulerType)!, overrideLocation: hqMine, ); final trips = marketsTradingSortedByDistance( diff --git a/packages/cli/lib/behavior/central_command.dart b/packages/cli/lib/behavior/central_command.dart index 9ac3606a..ad684946 100644 --- a/packages/cli/lib/behavior/central_command.dart +++ b/packages/cli/lib/behavior/central_command.dart @@ -6,6 +6,7 @@ import 'package:cli/behavior/mount_from_buy.dart'; import 'package:cli/cache/caches.dart'; import 'package:cli/logger.dart'; import 'package:cli/printing.dart'; +import 'package:cli/ships.dart'; import 'package:cli/trading.dart'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; @@ -451,8 +452,11 @@ class CentralCommand { /// Computes the next ship buy job. Future _computeNextShipBuyJob(Api api, Caches caches) async { bool shouldBuy(ShipType shipType, int count) { - return caches.shipyardPrices.havePriceFor(shipType) && - _shipCache.countOfType(shipType) < count; + // This should never be null for real code, but makes unit testing easier + // to allow a null frame here. + final frame = caches.static.shipyardShips.shipFrameFromType(shipType); + final frameCount = frame == null ? 0 : _shipCache.countOfFrame(frame); + return caches.shipyardPrices.havePriceFor(shipType) && frameCount < count; } // This should be a multiple of our squad size so we always have diff --git a/packages/cli/lib/cache/ship_cache.dart b/packages/cli/lib/cache/ship_cache.dart index df9f6352..a1fb6b3b 100644 --- a/packages/cli/lib/cache/ship_cache.dart +++ b/packages/cli/lib/cache/ship_cache.dart @@ -1,9 +1,7 @@ import 'package:cli/api.dart'; import 'package:cli/cache/json_list_store.dart'; import 'package:cli/cache/response_cache.dart'; -import 'package:cli/logger.dart'; import 'package:cli/net/queries.dart'; -import 'package:cli/ships.dart'; import 'package:file/file.dart'; import 'package:types/types.dart'; @@ -78,13 +76,9 @@ class ShipCache extends ResponseListCache { return frameCounts; } - /// Returns the number of ships of the given [shipType] in the fleet. - int countOfType(ShipType shipType) { - final frameForType = shipFrameFromType(shipType); - if (frameForType == null) { - logger.err('Unknown frame mapping for type: $shipType'); - } - return frameCounts[frameForType] ?? 0; + /// Returns the number of ships with the given [frame]. + int countOfFrame(ShipFrameSymbolEnum frame) { + return frameCounts[frame] ?? 0; } /// Returns all ship symbols. diff --git a/packages/cli/lib/ships.dart b/packages/cli/lib/ships.dart index 56998d24..b53b4d12 100644 --- a/packages/cli/lib/ships.dart +++ b/packages/cli/lib/ships.dart @@ -3,50 +3,36 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:types/api.dart'; -// TODO(eseidel): Move to using StaticCaches.shipyardShips instead. -const _typeFramePairs = [ - (ShipType.PROBE, ShipFrameSymbolEnum.PROBE), - (ShipType.MINING_DRONE, ShipFrameSymbolEnum.DRONE), - (ShipType.INTERCEPTOR, ShipFrameSymbolEnum.INTERCEPTOR), - (ShipType.LIGHT_HAULER, ShipFrameSymbolEnum.LIGHT_FREIGHTER), - (ShipType.COMMAND_FRIGATE, ShipFrameSymbolEnum.FRIGATE), - (ShipType.EXPLORER, ShipFrameSymbolEnum.EXPLORER), - (ShipType.HEAVY_FREIGHTER, ShipFrameSymbolEnum.HEAVY_FREIGHTER), - (ShipType.LIGHT_SHUTTLE, ShipFrameSymbolEnum.SHUTTLE), - (ShipType.ORE_HOUND, ShipFrameSymbolEnum.MINER), - (ShipType.REFINING_FREIGHTER, ShipFrameSymbolEnum.HEAVY_FREIGHTER), -]; - /// Map from ship type to ship frame symbol. -ShipType? shipTypeFromFrame(ShipFrameSymbolEnum frameSymbol) { - ShipType? shipType; - for (final pair in _typeFramePairs) { - if (pair.$2 != frameSymbol) { - continue; - } - if (shipType != null) { - // Multiple frames map to the same ship type. - return null; +extension ShipTypeToFrame on ShipyardShipCache { + /// Map from ship type to ship frame symbol. + ShipType? shipTypeFromFrame(ShipFrameSymbolEnum frameSymbol) { + ShipType? shipType; + for (final ship in values) { + if (ship.frame.symbol != frameSymbol) { + continue; + } + if (shipType != null) { + // Multiple frames map to the same ship type. + return null; + } + shipType = ship.type; } - shipType = pair.$1; + return shipType; } - return shipType; -} -/// Map from ship type to ship frame symbol. -ShipFrameSymbolEnum? shipFrameFromType(ShipType type) { - for (final pair in _typeFramePairs) { - if (pair.$1 == type) return pair.$2; + /// Map from ship type to ship frame symbol. + ShipFrameSymbolEnum? shipFrameFromType(ShipType type) { + return this[type]?.frame.symbol; } - return null; } /// Provides Ship data that ShipyardShip does not. /// Right now that's only Role, but it could be more in the future. @immutable -class ShipConfig { +class _ShipConfig { /// Create a new ship config. - const ShipConfig({required this.type, required this.role}); + const _ShipConfig({required this.type, required this.role}); /// ShipType this config is for. final ShipType type; @@ -56,8 +42,8 @@ class ShipConfig { } const _shipConfigs = [ - ShipConfig(type: ShipType.PROBE, role: ShipRole.SATELLITE), - ShipConfig(type: ShipType.COMMAND_FRIGATE, role: ShipRole.COMMAND), + _ShipConfig(type: ShipType.PROBE, role: ShipRole.SATELLITE), + _ShipConfig(type: ShipType.COMMAND_FRIGATE, role: ShipRole.COMMAND), ]; ShipNav _makeShipNav({required SystemWaypoint origin, required DateTime now}) { @@ -163,23 +149,3 @@ Ship? makeShipForTest({ mounts: shipyardShip.mounts, ); } - -/// Make an example ShipMount for a given mount symbol. -ShipMount makeMount(ShipMountSymbolEnum mountSymbol) { - return ShipMount( - symbol: mountSymbol, - name: mountSymbol.value, - description: mountSymbol.value, - requirements: ShipRequirements(crew: 0), - ); -} - -/// Make an example ShipModule for a given module symbol. -ShipModule makeModule(ShipModuleSymbolEnum moduleSymbol) { - return ShipModule( - symbol: moduleSymbol, - name: moduleSymbol.value, - description: moduleSymbol.value, - requirements: ShipRequirements(crew: 0), - ); -} diff --git a/packages/cli/test/behavior/central_command_test.dart b/packages/cli/test/behavior/central_command_test.dart index ed337a4a..5b442752 100644 --- a/packages/cli/test/behavior/central_command_test.dart +++ b/packages/cli/test/behavior/central_command_test.dart @@ -455,7 +455,8 @@ void main() { when(() => caches.marketPrices.prices).thenReturn([]); registerFallbackValue(TradeSymbol.ADVANCED_CIRCUITRY); when(() => caches.marketPrices.havePriceFor(any())).thenReturn(false); - when(() => caches.ships.countOfType(ShipType.ORE_HOUND)).thenReturn(0); + when(() => caches.ships.countOfFrame(ShipFrameSymbolEnum.MINER)) + .thenReturn(0); await centralCommand.advanceCentralPlanning(api, caches); expect(centralCommand.nextShipBuyJob, isNull); diff --git a/packages/cli/test/cache/ship_cache_test.dart b/packages/cli/test/cache/ship_cache_test.dart index dd08d70f..7d1a1379 100644 --- a/packages/cli/test/cache/ship_cache_test.dart +++ b/packages/cli/test/cache/ship_cache_test.dart @@ -34,7 +34,7 @@ void main() { {ShipFrameSymbolEnum.MINER: 2, ShipFrameSymbolEnum.FIGHTER: 1}, ); - expect(shipCache.countOfType(ShipType.ORE_HOUND), 2); + expect(shipCache.countOfFrame(ShipFrameSymbolEnum.MINER), 2); }); test('describeFleet', () { diff --git a/packages/cli/test/ships_test.dart b/packages/cli/test/ships_test.dart index 92646474..74d0c1fd 100644 --- a/packages/cli/test/ships_test.dart +++ b/packages/cli/test/ships_test.dart @@ -1,8 +1,12 @@ +import 'package:cli/cache/static_cache.dart'; import 'package:cli/ships.dart'; +import 'package:file/local.dart'; import 'package:test/test.dart'; import 'package:types/types.dart'; void main() { + final shipyardShips = ShipyardShipCache.load(const LocalFileSystem()); + test('shipTypeFromFrame', () { final expectedNullTypes = [ // Two ship types use heavy freighters, so we can't map back to a type. @@ -16,16 +20,26 @@ void main() { ShipFrameSymbolEnum.CARRIER, ]; for (final frame in ShipFrameSymbolEnum.values) { - final type = shipTypeFromFrame(frame); + // TODO(eseidel): Remove this once we have data on COMMAND_FRIGATE + if (frame == ShipFrameSymbolEnum.FRIGATE) { + continue; + } + + final type = shipyardShips.shipTypeFromFrame(frame); final matcher = expectedNullTypes.contains(frame) ? isNull : isNotNull; - expect(type, matcher, reason: '$frame'); + expect(type, matcher, reason: 'no shipType with frame $frame'); } }); test('shipFrameFromType', () { for (final type in ShipType.values) { - final frame = shipFrameFromType(type); - expect(frame, isNotNull, reason: '$type'); + // TODO(eseidel): Remove this once we have data on COMMAND_FRIGATE + if (type == ShipType.COMMAND_FRIGATE) { + continue; + } + + final frame = shipyardShips.shipFrameFromType(type); + expect(frame, isNotNull, reason: 'no frame for type $type'); } }); }