Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions trade_factory_masters/lib/domain/core/result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/// Generic Result type for error handling in use cases
/// Represents either a successful value (Success) or a failure (Failure)
abstract class Result<T, E> {
const Result();

/// Creates a successful result with a value
factory Result.success(T value) = Success<T, E>;

/// Creates a failed result with an error
factory Result.failure(E error) = Failure<T, E>;

/// Check if this result is a success
bool get isSuccess => this is Success<T, E>;

/// Check if this result is a failure
bool get isFailure => this is Failure<T, E>;

/// Get the success value, or null if this is a failure
T? get valueOrNull => isSuccess ? (this as Success<T, E>).value : null;

/// Get the error, or null if this is a success
E? get errorOrNull => isFailure ? (this as Failure<T, E>).error : null;

/// Execute a function based on whether this is a success or failure
R fold<R>({
required R Function(T value) onSuccess,
required R Function(E error) onFailure,
}) {
if (this is Success<T, E>) {
return onSuccess((this as Success<T, E>).value);
} else {
return onFailure((this as Failure<T, E>).error);
}
}
}

/// Represents a successful result containing a value
class Success<T, E> extends Result<T, E> {
final T value;

const Success(this.value);

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Success<T, E> &&
runtimeType == other.runtimeType &&
value == other.value;

@override
int get hashCode => value.hashCode;

@override
String toString() => 'Success($value)';
}

/// Represents a failed result containing an error
class Failure<T, E> extends Result<T, E> {
final E error;

const Failure(this.error);

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Failure<T, E> &&
runtimeType == other.runtimeType &&
error == other.error;

@override
int get hashCode => error.hashCode;

@override
String toString() => 'Failure($error)';
}
117 changes: 117 additions & 0 deletions trade_factory_masters/lib/domain/usecases/collect_resources.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import 'dart:math';
import '../entities/player_economy.dart';
import '../entities/building.dart';

/// Use case for collecting produced resources from a building
/// Calculates production based on elapsed time since last collection
/// and updates player's economy with new resources
class CollectResourcesUseCase {
/// Execute the resource collection for a specific building
///
/// Calculates resources based on:
/// - Time elapsed since lastCollected
/// - Building's production rate (accounts for level multiplier)
/// - Storage capacity limit (prevents over-accumulation)
///
/// Returns updated PlayerEconomy with:
/// - Added resources to inventory
/// - Updated building with new lastCollected timestamp
CollectResourcesResult execute({
required PlayerEconomy economy,
required Building building,
DateTime? currentTime,
}) {
final now = currentTime ?? DateTime.now();

// Validate building is active
if (!building.isActive) {
return CollectResourcesResult(
economy: economy,
resourcesCollected: 0,
wasCapped: false,
);
}

// Calculate time elapsed
final elapsed = now.difference(building.lastCollected);
final hoursElapsed = elapsed.inMinutes / 60.0;

// Calculate production (productionRate already includes level multiplier)
final rawProduction = hoursElapsed * building.productionRate;

// Apply storage capacity limit
// Storage capacity = base production rate × 10 (equivalent to 10 hours storage)
final storageCapacity = building.production.baseRate * 10;
final cappedProduction = min(rawProduction, storageCapacity);
final resourceAmount = cappedProduction.toInt();

// Check if production was capped
final wasCapped = rawProduction > storageCapacity;

// Add resources to economy
final updatedEconomy = economy.addResource(
building.production.resourceType,
resourceAmount,
);

// Update building's lastCollected timestamp
final updatedBuilding = building.copyWith(lastCollected: now);

// Replace building in economy's building list
final buildingIndex = economy.buildings.indexWhere((b) => b.id == building.id);
if (buildingIndex == -1) {
// Building not found in economy, return economy with added resources only
return CollectResourcesResult(
economy: updatedEconomy,
resourcesCollected: resourceAmount,
wasCapped: wasCapped,
);
}

final updatedBuildings = List<Building>.from(economy.buildings);
updatedBuildings[buildingIndex] = updatedBuilding;

final finalEconomy = updatedEconomy.copyWith(buildings: updatedBuildings);

return CollectResourcesResult(
economy: finalEconomy,
resourcesCollected: resourceAmount,
wasCapped: wasCapped,
);
}
}

/// Result of collecting resources from a building
class CollectResourcesResult {
/// Updated player economy with collected resources
final PlayerEconomy economy;

/// Amount of resources collected
final int resourcesCollected;

/// Whether production was capped by storage capacity
final bool wasCapped;

const CollectResourcesResult({
required this.economy,
required this.resourcesCollected,
required this.wasCapped,
});

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CollectResourcesResult &&
runtimeType == other.runtimeType &&
economy == other.economy &&
resourcesCollected == other.resourcesCollected &&
wasCapped == other.wasCapped;

@override
int get hashCode =>
economy.hashCode ^ resourcesCollected.hashCode ^ wasCapped.hashCode;

@override
String toString() =>
'CollectResourcesResult(collected: $resourcesCollected, wasCapped: $wasCapped)';
}
99 changes: 99 additions & 0 deletions trade_factory_masters/lib/domain/usecases/upgrade_building.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import '../entities/player_economy.dart';
import '../entities/building.dart';
import '../core/result.dart';

/// Possible errors when upgrading a building
enum UpgradeError {
/// Building is already at maximum level
maxLevelReached,

/// Player doesn't have enough gold
insufficientGold,

/// Building is not active
buildingInactive,

/// Building not found in economy
buildingNotFound,
}

/// Use case for upgrading a building to increase production rate
/// Validates upgrade requirements and deducts gold from player's economy
class UpgradeBuildingUseCase {
/// Execute the building upgrade
///
/// Performs the following checks:
/// 1. Building is active
/// 2. Building is not at max level
/// 3. Player has enough gold
///
/// If all checks pass:
/// - Deducts upgrade cost from player's gold
/// - Increases building level by 1
/// - Updates building in economy's building list
///
/// Returns:
/// - Success(PlayerEconomy) with updated state
/// - Failure(UpgradeError) if any validation fails
Result<PlayerEconomy, UpgradeError> execute({
required PlayerEconomy economy,
required Building building,
}) {
// Validate building is active
if (!building.isActive) {
return Result.failure(UpgradeError.buildingInactive);
}

// Validate building is not at max level
if (building.level >= building.upgradeConfig.maxLevel) {
return Result.failure(UpgradeError.maxLevelReached);
}

// Calculate upgrade cost
final upgradeCost = building.calculateUpgradeCost();

// Validate player can afford upgrade
if (!economy.canAfford(upgradeCost)) {
return Result.failure(UpgradeError.insufficientGold);
}

// Deduct gold from economy
final economyAfterPayment = economy.deductGold(upgradeCost);

// Upgrade building (increase level)
final upgradedBuilding = building.copyWith(
level: building.level + 1,
);

// Update building in economy's building list
final buildingIndex = economy.buildings.indexWhere((b) => b.id == building.id);
if (buildingIndex == -1) {
return Result.failure(UpgradeError.buildingNotFound);
}

final updatedBuildings = List<Building>.from(economyAfterPayment.buildings);
updatedBuildings[buildingIndex] = upgradedBuilding;

final finalEconomy = economyAfterPayment.copyWith(
buildings: updatedBuildings,
);

return Result.success(finalEconomy);
}
}

/// Extension to provide human-readable error messages
extension UpgradeErrorMessage on UpgradeError {
String get message {
switch (this) {
case UpgradeError.maxLevelReached:
return 'Building is already at maximum level';
case UpgradeError.insufficientGold:
return 'Not enough gold to upgrade';
case UpgradeError.buildingInactive:
return 'Cannot upgrade inactive building';
case UpgradeError.buildingNotFound:
return 'Building not found in player economy';
}
}
}
Loading
Loading