Skip to content

Commit

Permalink
refactor: move game phase into db
Browse files Browse the repository at this point in the history
  • Loading branch information
eseidel committed Aug 11, 2024
1 parent 988af58 commit c1ecf9b
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 57 deletions.
34 changes: 23 additions & 11 deletions packages/cli/bin/cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,33 @@ Future<void> cliMain(List<String> args) async {

logger.info('Welcome to Space Traders! 🚀');

if (results['selloff'] as bool) {
logger.err('Selling all ships!');
config = config = Config(GamePhase.selloff);
}

// Use package:file to make things mockable.
const fs = LocalFileSystem();
final db = await defaultDatabase();
final agentName = Platform.environment['ST_AGENT'];
final email = Platform.environment['ST_EMAIL'];
final token =
await loadAuthTokenOrRegister(db, agentName: agentName, email: email);

// Api client should move to per-ship with a per-ship priority function.
final api = apiFromAuthToken(token, db);
final agentSymbol =
await db.getAgentSymbol() ?? Platform.environment['ST_AGENT'];
if (agentSymbol == null) {
throw StateError('No agent symbol found in database or environment.');
}
final api;
if (await db.getAuthToken() == null) {
final email = Platform.environment['ST_EMAIL'];
logger.info('No auth token found.');
// Otherwise, register a new user.
final token = await register(db, agentSymbol: agentSymbol, email: email);
await db.setAuthToken(token);
api = apiFromAuthToken(token, db);
} else {
api = await defaultApi(db);
}

if (results['selloff'] as bool) {
logger.err('Selling all ships!');
db.setGamePhase(GamePhase.selloff);
}

config = await Config.fromDb(db);

final caches = await Caches.loadOrFetch(fs, api, db);
final marketPricesCount = await db.marketPricesCount();
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/bin/squads.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ Future<void> command(FileSystem fs, Database db, ArgResults argResults) async {
final charting = ChartingCache(db);
final ships = await ShipSnapshot.load(db);
final shipyardShipCache = ShipyardShipCache.load(fs);
final agentSymbol = await db.getAgentSymbol();
if (agentSymbol == null) {
throw StateError('No agent symbol found in database.');
}
// TODO(eseidel): Compute the current phase or read from db.
config = Config(GamePhase.construction);
config = Config(agentSymbol, GamePhase.construction);

final squads = await assignShipsToSquads(
db,
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/lib/central_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,10 @@ class CentralCommand {
final ships = await ShipSnapshot.load(db);
final phase = _determineGamePhase(ships);
logger.info("$phase");
config = Config(phase);
if (phase != config.gamePhase) {
await db.setGamePhase(phase);
config = await Config.fromDb(db);
}

final marketListings = await MarketListingSnapshot.load(db);
final shipyardListings = await ShipyardListingSnapshot.load(db);
Expand Down
34 changes: 13 additions & 21 deletions packages/cli/lib/config.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
import 'package:db/db.dart';
import 'package:types/types.dart';

/// The default max age for our caches is 3 days.
/// This is used as a default argument and must be const.
const defaultMaxAge = Duration(days: 3);

/// Which game phase are we in.
enum GamePhase with EnumIndexOrdering {
/// Initially just buying haulers and getting trading going.
bootstrap,

/// Focused on building the jumpgate.
construction,

/// Focused on exploring the galaxy to find better ships.
exploration,

/// Sell off all our ships and retire.
selloff
}

/// Class for holding our hard-coded configuration values.
class Config {
/// Create a new Config object.
Config(this.gamePhase);
Config(this.agentSymbol, this.gamePhase);

/// Which phase are we in.
final GamePhase gamePhase;

// TODO(eseidel): This should be configured at runtime.
/// The symbol of the agent we are controlling.
final String agentSymbol = 'ESEIDEL';
final String agentSymbol;

/// Whether or not we should enable mining behaviors.
bool get enableMining => gamePhase < GamePhase.exploration;
Expand Down Expand Up @@ -259,9 +244,16 @@ class Config {

/// Maximum markup we will tolerate when refueling (otherwise we will drift).
final fuelMaxMarkup = 10.0;

static Future<Config> fromDb(Database db) async {
final agentSymbol = await db.getAgentSymbol();
if (agentSymbol == null) {
throw StateError('No agent symbol found in database.');
}
final gamePhase = await db.getGamePhase() ?? GamePhase.bootstrap;
return Config(agentSymbol, gamePhase);
}
}

/// Our global configuration object.
// TODO(eseidel): Correctly detect when we've finished construction but not
// bought our second probe yet (our first one is system-watching).
Config config = Config(GamePhase.exploration);
late Config config;
26 changes: 3 additions & 23 deletions packages/cli/lib/net/register.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,6 @@ import 'package:cli/net/exceptions.dart';
import 'package:db/db.dart';
import 'package:types/types.dart';

/// loadAuthTokenOrRegister loads the auth token from the given file system
/// or registers a new user and returns the auth token.
Future<String> loadAuthTokenOrRegister(
Database db, {
String? agentName,
String? email,
}) async {
final token = await db.getAuthToken();
if (token != null) {
return token;
} else {
logger.info('No auth token found.');
// Otherwise, register a new user.
final name = agentName ?? logger.prompt('What is your agent name?');
final token = await register(db, agentName: name, email: email);
await db.setAuthToken(token);
return token;
}
}

Future<String> _tryRegister(
DefaultApi api, {
required String symbol,
Expand All @@ -48,7 +28,7 @@ Future<String> _tryRegister(
/// associated with the call sign.
Future<String> register(
Database db, {
required String agentName,
required String agentSymbol,
String? email,
String? faction,
}) async {
Expand All @@ -75,7 +55,7 @@ Future<String> register(
try {
return await _tryRegister(
defaultApi,
symbol: agentName,
symbol: agentSymbol,
faction: chosenFaction.symbol,
email: email,
);
Expand All @@ -94,7 +74,7 @@ Future<String> register(
);
return await _tryRegister(
defaultApi,
symbol: agentName,
symbol: agentSymbol,
faction: chosenFaction.symbol,
email: email,
);
Expand Down
32 changes: 32 additions & 0 deletions packages/db/lib/db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,38 @@ class Database {
return result[0][0]! as int;
}

Future<String?> getAgentSymbol() async {
final result = await executeSql(
'SELECT value FROM config WHERE key = \'agent_symbol\'');
if (result.isEmpty) {
return null;
}
return result[0][0] as String;
}

Future<void> setAgentSymbol(String symbol) async {
await executeSql(
'INSERT INTO config (key, value) VALUES (\'agent_symbol\', '
'\'$symbol\') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value');
}

Future<GamePhase?> getGamePhase() async {
final result =
await executeSql('SELECT value FROM config WHERE key = \'game_phase\'');
if (result.isEmpty) {
return null;
}
return GamePhase.values.firstWhere(
(phase) => phase.name == result[0][0] as String,
orElse: () => throw ArgumentError('Unknown game phase: ${result[0][0]}'),
);
}

Future<void> setGamePhase(GamePhase phase) async {
await executeSql('INSERT INTO config (key, value) VALUES (\'game_phase\', '
'\'$phase\') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value');
}

Future<String?> getAuthToken() async {
final result =
await executeSql('SELECT value FROM config WHERE key = \'auth_token\'');
Expand Down
16 changes: 16 additions & 0 deletions packages/types/lib/src/game_phase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:types/enum.dart';

/// Which game phase are we in.
enum GamePhase with EnumIndexOrdering {
/// Initially just buying haulers and getting trading going.
bootstrap,

/// Focused on building the jumpgate.
construction,

/// Focused on exploring the galaxy to find better ships.
exploration,

/// Sell off all our ships and retire.
selloff
}
1 change: 1 addition & 0 deletions packages/types/lib/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export 'src/counts.dart';
export 'src/deal.dart';
export 'src/export.dart';
export 'src/extraction.dart';
export 'src/game_phase.dart';
export 'src/jump_gate.dart';
export 'src/market_listing.dart';
export 'src/market_price.dart';
Expand Down

0 comments on commit c1ecf9b

Please sign in to comment.