Skip to content

Commit

Permalink
refactor: use StaticCaches for frame/type mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
eseidel committed Oct 18, 2023
1 parent 5e6aad9 commit a43ee9d
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 78 deletions.
15 changes: 10 additions & 5 deletions packages/cli/bin/fleet_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ int _costOutMounts(
}

/// Returns a map of ship frame type to count in fleet.
Map<ShipType, int> _shipTypeCounts(List<Ship> ships) {
Map<ShipType, int> _shipTypeCounts(
ShipyardShipCache shipyardShips,
List<Ship> ships,
) {
final typeCounts = <ShipType, int>{};
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;
Expand All @@ -35,15 +38,17 @@ Future<void> 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) {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/bin/simulate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Future<void> 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
Expand Down Expand Up @@ -171,7 +172,7 @@ Future<void> command(FileSystem fs, ArgResults argResults) async {

final ship = exampleShip(
shipCache,
frame: shipFrameFromType(haulerType)!,
frame: shipyardShips.shipFrameFromType(haulerType)!,
overrideLocation: hqMine,
);
final trips = marketsTradingSortedByDistance(
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/lib/behavior/central_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -451,8 +452,11 @@ class CentralCommand {
/// Computes the next ship buy job.
Future<ShipBuyJob?> _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
Expand Down
12 changes: 3 additions & 9 deletions packages/cli/lib/cache/ship_cache.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -78,13 +76,9 @@ class ShipCache extends ResponseListCache<Ship> {
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.
Expand Down
76 changes: 21 additions & 55 deletions packages/cli/lib/ships.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}) {
Expand Down Expand Up @@ -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),
);
}
3 changes: 2 additions & 1 deletion packages/cli/test/behavior/central_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/cache/ship_cache_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down
22 changes: 18 additions & 4 deletions packages/cli/test/ships_test.dart
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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');
}
});
}

0 comments on commit a43ee9d

Please sign in to comment.