diff --git a/ecs.php b/ecs.php index eb70eb7..e5b59bc 100644 --- a/ecs.php +++ b/ecs.php @@ -3,7 +3,8 @@ declare(strict_types=1); use PhpCsFixer\Fixer\Import\NoUnusedImportsFixer; -use \PhpCsFixer\Fixer\Phpdoc\PhpdocInlineTagNormalizerFixer; +use PhpCsFixer\Fixer\Phpdoc\PhpdocInlineTagNormalizerFixer; +use PhpCsFixer\Fixer\Whitespace\TypeDeclarationSpacesFixer; use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; @@ -20,6 +21,7 @@ $ecsConfig->rules([ NoUnusedImportsFixer::class, PhpdocInlineTagNormalizerFixer::class, + TypeDeclarationSpacesFixer::class, ]); // this way you can add sets - group of rules diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 341cd5c..60ba28e 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -7,3 +7,4 @@ parameters: - src/ - spec/ - tests/ + treatPhpDocTypesAsCertain: false diff --git a/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/CancelConstructingCommandHandlerSpec.php b/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/CancelConstructingCommandHandlerSpec.php index 9d3b7a1..8b63fba 100644 --- a/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/CancelConstructingCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/CancelConstructingCommandHandlerSpec.php @@ -14,7 +14,7 @@ use TheGame\Application\Component\BuildingConstruction\Domain\Event\BuildingConstructionHasBeenCancelledEvent; use TheGame\Application\SharedKernel\Domain\BuildingType; use TheGame\Application\SharedKernel\Domain\PlanetId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\EventBusInterface; use TheGame\Application\SharedKernel\Exception\InconsistentModelException; @@ -53,7 +53,7 @@ public function it_cancels_constructing( BuildingContextInterface $buildingBalanceContext, EventBusInterface $eventBus, Building $building, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $planetId = "1D632422-951F-4181-A48D-5AD654260B2B"; $buildingId = "d6949ca7-157d-4019-9267-c7a61af33b01"; diff --git a/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartConstructingNewBuildingCommandHandlerSpec.php b/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartConstructingNewBuildingCommandHandlerSpec.php index 21075e6..7d7a9f7 100644 --- a/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartConstructingNewBuildingCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartConstructingNewBuildingCommandHandlerSpec.php @@ -20,7 +20,7 @@ use TheGame\Application\SharedKernel\Domain\BuildingType; use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\Domain\ResourceId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\EventBusInterface; final class StartConstructingNewBuildingCommandHandlerSpec extends ObjectBehavior @@ -47,7 +47,7 @@ public function it_creates_building_when_building_for_the_specified_type_is_not_ Building $building, BuildingContextInterface $buildingBalanceContext, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, EventBusInterface $eventBus, ): void { $planetId = "E7AF94C7-488C-46E4-8C44-DCD8F62B2A45"; @@ -101,7 +101,7 @@ public function it_throws_exception_when_storage_hasnt_enough_resources( Building $building, BuildingContextInterface $buildingBalanceContext, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $planetId = "E7AF94C7-488C-46E4-8C44-DCD8F62B2A45"; $resourceContextId = "52B4E60C-5CCE-4483-968E-D23D9240A18A"; diff --git a/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartUpgradingBuildingCommandHandlerSpec.php b/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartUpgradingBuildingCommandHandlerSpec.php index aad40de..217975c 100644 --- a/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartUpgradingBuildingCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/BuildingConstruction/CommandHandler/StartUpgradingBuildingCommandHandlerSpec.php @@ -17,7 +17,7 @@ use TheGame\Application\Component\ResourceStorage\Bridge\ResourceAvailabilityCheckerInterface; use TheGame\Application\SharedKernel\Domain\BuildingType; use TheGame\Application\SharedKernel\Domain\PlanetId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\EventBusInterface; use TheGame\Application\SharedKernel\Exception\InconsistentModelException; @@ -58,7 +58,7 @@ public function it_throws_exception_when_storage_hasnt_enough_resources( Building $building, BuildingContextInterface $buildingBalanceContext, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $planetId = "E7AF94C7-488C-46E4-8C44-DCD8F62B2A45"; $buildingId = "52B4E60C-5CCE-4483-968E-D23D9240A18A"; @@ -89,7 +89,7 @@ public function it_starts_upgrading_the_building( Building $building, BuildingContextInterface $buildingBalanceContext, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, EventBusInterface $eventBus, ): void { $planetId = "E7AF94C7-488C-46E4-8C44-DCD8F62B2A45"; diff --git a/spec/TheGame/Application/Component/FleetJourney/Command/CancelJourneyCommandSpec.php b/spec/TheGame/Application/Component/FleetJourney/Command/CancelJourneyCommandSpec.php new file mode 100644 index 0000000..92b9811 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Command/CancelJourneyCommandSpec.php @@ -0,0 +1,21 @@ +beConstructedWith($fleetId); + } + + public function it_has_fleet_id(): void + { + $this->getFleetId()->shouldReturn("06d98267-6399-426f-be86-b79ea570046b"); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Command/ReturnJourneysCommandSpec.php b/spec/TheGame/Application/Component/FleetJourney/Command/ReturnJourneysCommandSpec.php new file mode 100644 index 0000000..9a1eb20 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Command/ReturnJourneysCommandSpec.php @@ -0,0 +1,21 @@ +beConstructedWith($userId); + } + + public function it_has_user_id(): void + { + $this->getUserId()->shouldReturn("0cfe1c65-2cac-4138-92bd-fac68027c39b"); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Command/StartJourneyCommandSpec.php b/spec/TheGame/Application/Component/FleetJourney/Command/StartJourneyCommandSpec.php new file mode 100644 index 0000000..a9174fb --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Command/StartJourneyCommandSpec.php @@ -0,0 +1,161 @@ + 25, + ]; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => 500, + ]; + + $this->beConstructedWith( + $planetId, + $targetGalaxyPoint, + $mission, + $shipsTakingJourney, + $resourcesLoad + ); + } + + public function it_has_planet_id(): void + { + $this->getPlanetId()->shouldReturn("e5835dba-aef6-4b15-9d7c-cfb177959f6d"); + } + + public function it_has_target_galaxy_point(): void + { + $this->getTargetGalaxyPoint()->shouldReturn("[1:2:3]"); + } + + public function it_has_mission_type(): void + { + $this->getMissionType()->shouldReturn("transport"); + } + + public function it_has_ships_taking_journey(): void + { + $this->getShipsTakingJourney()->shouldReturn([ + "light-fighter" => 25, + ]); + } + + public function it_throws_exception_when_initializing_with_ships_taking_journey_containing_invalid_array_key(): void + { + $planetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $mission = "transport"; + $shipsTakingJourney = [ + 500 => 25, + ]; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => 500, + ]; + + $this->beConstructedWith( + $planetId, + $targetGalaxyPoint, + $mission, + $shipsTakingJourney, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $targetGalaxyPoint, $mission, $shipsTakingJourney, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_ships_taking_journey_containing_invalid_array_value(): void + { + $planetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $mission = "transport"; + $shipsTakingJourney = [ + "light-fighter" => 25, + ]; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => "8bd4aaac-2d64-4b3e-be30-56be1096d9e9", + ]; + + $this->beConstructedWith( + $planetId, + $targetGalaxyPoint, + $mission, + $shipsTakingJourney, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $targetGalaxyPoint, $mission, $shipsTakingJourney, $resourcesLoad, + ]); + } + + public function it_has_resources_load(): void + { + $this->getResourcesLoad()->shouldReturn([ + "5966f38b-2add-43e7-884b-64cf6569666f" => 500, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_key(): void + { + $planetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $mission = "transport"; + $shipsTakingJourney = [ + "light-fighter" => 25, + ]; + $resourcesLoad = [ + 350 => 500, + ]; + + $this->beConstructedWith( + $planetId, + $targetGalaxyPoint, + $mission, + $shipsTakingJourney, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $targetGalaxyPoint, $mission, $shipsTakingJourney, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_value(): void + { + $planetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $mission = "transport"; + $shipsTakingJourney = [ + "light-fighter" => 25, + ]; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => "1d9a563e-ed8a-4c5a-a918-903873f2adff", + ]; + + $this->beConstructedWith( + $planetId, + $targetGalaxyPoint, + $mission, + $shipsTakingJourney, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $targetGalaxyPoint, $mission, $shipsTakingJourney, $resourcesLoad, + ]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Command/TargetJourneysCommandSpec.php b/spec/TheGame/Application/Component/FleetJourney/Command/TargetJourneysCommandSpec.php new file mode 100644 index 0000000..f1e722d --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Command/TargetJourneysCommandSpec.php @@ -0,0 +1,21 @@ +beConstructedWith($userId); + } + + public function it_has_user_id(): void + { + $this->getUserId()->shouldReturn("c20f9c5c-09eb-4eba-bf7a-899507264f6a"); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/CommandHandler/CancelJourneyCommandHandlerSpec.php b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/CancelJourneyCommandHandlerSpec.php new file mode 100644 index 0000000..a1e2868 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/CancelJourneyCommandHandlerSpec.php @@ -0,0 +1,64 @@ +beConstructedWith($fleetRepository, $eventBus); + } + + public function it_cancels_journey( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + Fleet $fleet, + GalaxyPointInterface $fleetTargetPoint, + ): void { + $fleetId = "f164e51d-2398-44fb-9c9d-7744681de1fe"; + $fleetRepository->find(new FleetId($fleetId))->willReturn($fleet); + + $fleet->cancelJourney()->shouldBeCalledOnce(); + + $fleet->getJourneyTargetPoint()->willReturn($fleetTargetPoint); + $fleetTargetPoint->format()->willReturn("[1:2:3]"); + + $eventBus->dispatch(Argument::type(FleetHasCancelledJourneyEvent::class)) + ->shouldBeCalledOnce(); + $fleet->getResourcesLoad()->willReturn([ + "3cacc092-af4a-45ca-846e-0c7060a60c43" => 500, + ]); + + $this->__invoke( + new CancelJourneyCommand($fleetId), + ); + } + + public function it_throws_exception_when_fleet_has_not_been_found( + FleetRepositoryInterface $fleetRepository, + ): void { + $fleetId = "f164e51d-2398-44fb-9c9d-7744681de1fe"; + + $fleetRepository->find(new FleetId($fleetId))->willReturn(null); + + $this->shouldThrow(InconsistentModelException::class) + ->during('__invoke', [ + new CancelJourneyCommand($fleetId), + ]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/CommandHandler/ReturnJourneysCommandHandlerSpec.php b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/ReturnJourneysCommandHandlerSpec.php new file mode 100644 index 0000000..fe605af --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/ReturnJourneysCommandHandlerSpec.php @@ -0,0 +1,111 @@ +beConstructedWith($fleetRepository, $eventBus); + } + + public function it_returns_journey_of_two_fleets( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + Fleet $fleet1, + Fleet $fleet2, + ): void { + $userId = "8e807726-4a48-489e-9706-16061705bb6a"; + + $fleetRepository->findFlyingBackFromJourneyForUser(new UserId($userId)) + ->willReturn([ + $fleet1->getWrappedObject(), + $fleet2->getWrappedObject(), + ]); + + $fleet1->tryToReachJourneyReturnPoint()->shouldBeCalledOnce(); + $fleet1->didReturnFromJourney()->willReturn(true); + $fleet1->getId()->willReturn(new FleetId("78131199-faa2-456f-9419-10d77ac92e13")); + $fleet1->getJourneyStartPoint()->willReturn(new GalaxyPoint(1, 2, 3)); + $fleet1->getJourneyTargetPoint()->willReturn(new GalaxyPoint(2, 3, 4)); + $fleet1->getJourneyReturnPoint()->willReturn(new GalaxyPoint(7, 8, 9)); + $fleet1->getResourcesLoad()->willReturn([ + "8de65203-ad4c-4ce7-bced-cfeda9107b5d" => 300, + "42bb40d7-1d8c-4a88-86db-c10ffd68570e" => 200, + ]); + + $fleet2->tryToReachJourneyReturnPoint()->shouldBeCalledOnce(); + $fleet2->didReturnFromJourney()->willReturn(true); + $fleet2->getId()->willReturn(new FleetId("a63c152f-86d5-4ed6-8eb0-c7beb05ee517")); + $fleet2->getJourneyStartPoint()->willReturn(new GalaxyPoint(3, 4, 5)); + $fleet2->getJourneyTargetPoint()->willReturn(new GalaxyPoint(4, 5, 6)); + $fleet2->getJourneyReturnPoint()->willReturn(new GalaxyPoint(7, 8, 9)); + $fleet2->getResourcesLoad()->willReturn([]); + + $eventBus->dispatch(Argument::type(FleetHasReachedJourneyReturnPointEvent::class)) + ->shouldBeCalledTimes(2); + + $this->__invoke(new ReturnJourneysCommand($userId)); + } + + public function it_skips_fleet_when_it_has_not_returned_from_journey( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + Fleet $fleet1, + Fleet $fleet2, + ): void { + $userId = "8e807726-4a48-489e-9706-16061705bb6a"; + + $fleetRepository->findFlyingBackFromJourneyForUser(new UserId($userId)) + ->willReturn([ + $fleet1->getWrappedObject(), + $fleet2->getWrappedObject(), + ]); + + $fleet1->tryToReachJourneyReturnPoint()->shouldBeCalledOnce(); + $fleet1->didReturnFromJourney()->willReturn(false); + + $fleet2->tryToReachJourneyReturnPoint()->shouldBeCalledOnce(); + $fleet2->didReturnFromJourney()->willReturn(true); + $fleet2->getId()->willReturn(new FleetId("a63c152f-86d5-4ed6-8eb0-c7beb05ee517")); + $fleet2->getJourneyStartPoint()->willReturn(new GalaxyPoint(3, 4, 5)); + $fleet2->getJourneyTargetPoint()->willReturn(new GalaxyPoint(4, 5, 6)); + $fleet2->getJourneyReturnPoint()->willReturn(new GalaxyPoint(7, 8, 9)); + $fleet2->getResourcesLoad()->willReturn([]); + + $eventBus->dispatch(Argument::type(FleetHasReachedJourneyReturnPointEvent::class)) + ->shouldBeCalledTimes(1); + + $this->__invoke(new ReturnJourneysCommand($userId)); + } + + public function it_does_nothing_when_no_returning_fleet_found( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + ): void { + $userId = "8e807726-4a48-489e-9706-16061705bb6a"; + + $fleetRepository->findFlyingBackFromJourneyForUser(new UserId($userId)) + ->willReturn([]); + + $eventBus->dispatch(Argument::type(FleetHasReachedJourneyReturnPointEvent::class)) + ->shouldNotBeCalled(); + + $this->__invoke(new ReturnJourneysCommand($userId)); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/CommandHandler/StartJourneyCommandHandlerSpec.php b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/StartJourneyCommandHandlerSpec.php new file mode 100644 index 0000000..48c90b9 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/StartJourneyCommandHandlerSpec.php @@ -0,0 +1,181 @@ +beConstructedWith( + $galaxyNavigator, + $fleetResolver, + $journeyFactory, + $journeyContext, + $eventBus, + ); + } + + public function it_starts_journey( + NavigatorInterface $galaxyNavigator, + FleetResolverInterface $fleetResolver, + JourneyFactoryInterface $journeyFactory, + FleetJourneyContextInterface $journeyContext, + EventBusInterface $eventBus, + Fleet $fleetTakingJourney, + GalaxyPointInterface $startGalaxyPoint, + Journey $journey, + FleetIdInterface $fleetId, + ResourcesInterface $fuelRequirements, + ): void { + $planetId = "935b7ecd-739d-44b2-9c1d-51d7eaa6a937"; + $targetGalaxyPoint = "[1:2:3]"; + $missionType = "transport"; + $shipsTakingJourney = [ + "light-fighter" => 20, + ]; + $resourcesLoad = [ + "f6f8ca08-39c7-4cca-ba95-ddda5735f7d4" => 400, + ]; + + $targetGalaxyPointStub = Argument::type(GalaxyPointInterface::class); + $galaxyNavigator->isWithinBoundaries($targetGalaxyPointStub) + ->willReturn(true); + + $resourcesLoadStub = Argument::type(ResourcesInterface::class); + $fleetResolver->resolveFromPlanet( + new PlanetId($planetId), + $shipsTakingJourney, + $resourcesLoadStub, + $targetGalaxyPointStub, + )->willReturn($fleetTakingJourney); + + $fleetSpeed = 30; + $fleetId->getUuid()->willReturn("9db97f06-7154-4276-a99a-f82ba9e890ff"); + $fleetTakingJourney->getId()->willReturn($fleetId); + $fleetTakingJourney->getStationingGalaxyPoint()->willReturn($startGalaxyPoint); + $fleetTakingJourney->getId()->willReturn($fleetId); + $fleetTakingJourney->getSpeed()->willReturn($fleetSpeed); + + $galaxyNavigator->isMissionEligible($missionType, $startGalaxyPoint, $targetGalaxyPointStub) + ->willReturn(true); + + $journeyDuration = 50; + $journeyContext->calculateJourneyDuration($fleetSpeed, $startGalaxyPoint, $targetGalaxyPointStub) + ->willReturn($journeyDuration); + + $journeyFactory->createJourney( + $fleetId, + MissionType::Transport, + $startGalaxyPoint, + $targetGalaxyPointStub, + $journeyDuration, + )->willReturn($journey); + + $fleetTakingJourney->startJourney($journey) + ->shouldBeCalledOnce(); + + $journeyContext->calculateFuelRequirements($startGalaxyPoint, $targetGalaxyPointStub, $shipsTakingJourney) + ->willReturn($fuelRequirements); + + $fuelRequirements->toScalarArray() + ->willReturn([ + "059e1846-d36a-43b8-9e02-81dd552844ce" => 500, + ]); + + $startGalaxyPoint->format()->willReturn("[1:2:3]"); + + $eventBus->dispatch(Argument::type(FleetHasStartedJourneyEvent::class)) + ->shouldBeCalledOnce(); + + $command = new StartJourneyCommand($planetId, $targetGalaxyPoint, $missionType, $shipsTakingJourney, $resourcesLoad); + $this->__invoke($command); + } + + public function it_throws_exception_when_target_point_is_not_in_galaxy_boundaries( + NavigatorInterface $galaxyNavigator, + ): void { + $planetId = "935b7ecd-739d-44b2-9c1d-51d7eaa6a937"; + $targetGalaxyPoint = "[1:2:3]"; + $missionType = "transport"; + $shipsTakingJourney = [ + "light-fighter" => 20, + ]; + $resourcesLoad = [ + "f6f8ca08-39c7-4cca-ba95-ddda5735f7d4" => 400, + ]; + + $targetGalaxyPointStub = Argument::type(GalaxyPointInterface::class); + $galaxyNavigator->isWithinBoundaries($targetGalaxyPointStub) + ->willReturn(false); + + $command = new StartJourneyCommand($planetId, $targetGalaxyPoint, $missionType, $shipsTakingJourney, $resourcesLoad); + $this->shouldThrow(CannotTakeJourneyToOutOfBoundGalaxyPointException::class) + ->during('__invoke', [$command]); + } + + public function it_throws_exception_when_mission_is_not_eligible( + NavigatorInterface $galaxyNavigator, + FleetResolverInterface $fleetResolver, + Fleet $fleetTakingJourney, + GalaxyPointInterface $startGalaxyPoint, + FleetIdInterface $fleetId, + ): void { + $planetId = "935b7ecd-739d-44b2-9c1d-51d7eaa6a937"; + $targetGalaxyPoint = "[1:2:3]"; + $missionType = "transport"; + $shipsTakingJourney = [ + "light-fighter" => 20, + ]; + $resourcesLoad = [ + "f6f8ca08-39c7-4cca-ba95-ddda5735f7d4" => 400, + ]; + + $targetGalaxyPointStub = Argument::type(GalaxyPointInterface::class); + $galaxyNavigator->isWithinBoundaries($targetGalaxyPointStub) + ->willReturn(true); + + $resourcesLoadStub = Argument::type(ResourcesInterface::class); + $fleetResolver->resolveFromPlanet( + new PlanetId($planetId), + $shipsTakingJourney, + $resourcesLoadStub, + $targetGalaxyPointStub, + )->willReturn($fleetTakingJourney); + + $fleetId->getUuid()->willReturn("9db97f06-7154-4276-a99a-f82ba9e890ff"); + $fleetTakingJourney->getStationingGalaxyPoint()->willReturn($startGalaxyPoint); + + $galaxyNavigator->isMissionEligible($missionType, $startGalaxyPoint, $targetGalaxyPointStub) + ->willReturn(false); + + $command = new StartJourneyCommand($planetId, $targetGalaxyPoint, $missionType, $shipsTakingJourney, $resourcesLoad); + $this->shouldThrow(JourneyMissionIsNotEligibleException::class) + ->during('__invoke', [$command]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/CommandHandler/TargetJourneysCommandHandlerSpec.php b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/TargetJourneysCommandHandlerSpec.php new file mode 100644 index 0000000..f552829 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/CommandHandler/TargetJourneysCommandHandlerSpec.php @@ -0,0 +1,115 @@ +beConstructedWith($fleetRepository, $eventBus); + } + + public function it_reaches_journey_target_for_two_fleets( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + Fleet $fleet1, + Fleet $fleet2, + ): void { + $userId = "f313d56d-1a27-46fb-a1a5-1fb4b8a1e88b"; + + $fleetRepository->findInJourneyForUser(new UserId($userId)) + ->willReturn([ + $fleet1->getWrappedObject(), + $fleet2->getWrappedObject(), + ]); + + $fleet1->tryToReachJourneyTargetPoint()->shouldBeCalledOnce(); + $fleet1->didReachJourneyTargetPoint()->willReturn(true); + $fleet1->getId()->willReturn(new FleetId("dfdd04c6-9243-4775-834c-ad702003ef6b")); + $fleet1->getJourneyMissionType()->willReturn(MissionType::Transport); + $fleet1->getJourneyTargetPoint()->willReturn(new GalaxyPoint(1, 2, 3)); + $fleet1->getResourcesLoad()->willReturn([ + "8de65203-ad4c-4ce7-bced-cfeda9107b5d" => 300, + "42bb40d7-1d8c-4a88-86db-c10ffd68570e" => 200, + ]); + + $fleet2->tryToReachJourneyTargetPoint()->shouldBeCalledOnce(); + $fleet2->didReachJourneyTargetPoint()->willReturn(true); + $fleet2->getId()->willReturn(new FleetId("6da596f9-f66a-4912-9603-06f538621695")); + $fleet2->getJourneyMissionType()->willReturn(MissionType::Stationing); + $fleet2->getJourneyTargetPoint()->willReturn(new GalaxyPoint(2, 3, 4)); + $fleet2->getResourcesLoad()->willReturn([]); + + $eventBus->dispatch(Argument::type(FleetHasReachedJourneyTargetPointEvent::class)) + ->shouldBeCalledTimes(2); + + $command = new TargetJourneysCommand($userId); + $this->__invoke($command); + } + + public function it_skips_one_fleet_which_didnt_reach_target_point( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + Fleet $fleet1, + Fleet $fleet2, + ): void { + $userId = "f313d56d-1a27-46fb-a1a5-1fb4b8a1e88b"; + + $fleetRepository->findInJourneyForUser(new UserId($userId)) + ->willReturn([ + $fleet1->getWrappedObject(), + $fleet2->getWrappedObject(), + ]); + + $fleet1->tryToReachJourneyTargetPoint()->shouldBeCalledOnce(); + $fleet1->didReachJourneyTargetPoint()->willReturn(true); + $fleet1->getId()->willReturn(new FleetId("dfdd04c6-9243-4775-834c-ad702003ef6b")); + $fleet1->getJourneyMissionType()->willReturn(MissionType::Transport); + $fleet1->getJourneyTargetPoint()->willReturn(new GalaxyPoint(1, 2, 3)); + $fleet1->getResourcesLoad()->willReturn([ + "8de65203-ad4c-4ce7-bced-cfeda9107b5d" => 300, + "42bb40d7-1d8c-4a88-86db-c10ffd68570e" => 200, + ]); + + $fleet2->tryToReachJourneyTargetPoint()->shouldBeCalledOnce(); + $fleet2->didReachJourneyTargetPoint()->willReturn(false); + + $eventBus->dispatch(Argument::type(FleetHasReachedJourneyTargetPointEvent::class)) + ->shouldBeCalledOnce(); + + $command = new TargetJourneysCommand($userId); + $this->__invoke($command); + } + + public function it_does_nothing_when_no_fleet_reaching_target_found( + FleetRepositoryInterface $fleetRepository, + EventBusInterface $eventBus, + ): void { + $userId = "f313d56d-1a27-46fb-a1a5-1fb4b8a1e88b"; + + $fleetRepository->findInJourneyForUser(new UserId($userId)) + ->willReturn([]); + + $eventBus->dispatch(Argument::type(FleetHasReachedJourneyTargetPointEvent::class)) + ->shouldNotBeCalled(); + + $command = new TargetJourneysCommand($userId); + $this->__invoke($command); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Entity/FleetSpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Entity/FleetSpec.php new file mode 100644 index 0000000..d5dfda8 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Entity/FleetSpec.php @@ -0,0 +1,862 @@ +initialize([]); + } + + private function initialize(array $ships): void + { + $fleetId = new FleetId("c5ec0807-a6e0-4555-b1ab-4ca6ec3bfb93"); + $stationingPoint = new GalaxyPoint(1, 2, 3); + + $resourcesLoad = new Resources(); + $resourceAmount1 = new ResourceAmount( + new ResourceId("7dbe6a5c-e12c-4325-a38a-f2165873c263"), + 500, + ); + $resourcesLoad->addResource($resourceAmount1); + + $resourceAmount2 = new ResourceAmount( + new ResourceId("e2a1295c-9390-47b9-99c6-dd5f0798954d"), + 350, + ); + $resourcesLoad->addResource($resourceAmount2); + + $this->beConstructedWith( + $fleetId, + $stationingPoint, + $resourcesLoad, + $ships, + ); + } + + public function it_has_identifier(): void + { + $this->getId()->getUuid()->shouldReturn("c5ec0807-a6e0-4555-b1ab-4ca6ec3bfb93"); + } + + public function it_is_stationing_on_the_planet(): void + { + $this->getStationingGalaxyPoint()->format()->shouldReturn("[1:2:3]"); + } + + public function it_lands_on_different_planet(): void + { + $this->landOnPlanet(new GalaxyPoint(4, 5, 6)); + + $this->getStationingGalaxyPoint()->format()->shouldReturn("[4:5:6]"); + } + + public function it_merges_two_fleets( + ): void { + throw new SkippingException("Cannot test the behaviour with current implementation"); + } + + public function it_adds_ships_of_new_type( + ShipsGroupInterface $lightFighterShipsGroup, + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $destroyerShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + $warshipShipsGroup->getWrappedObject(), + ]); + + $destroyerType = 'destroyer'; + $destroyerShipsGroup->getType() + ->willReturn($destroyerType); + + $destroyerShipsGroup->hasType($destroyerType) + ->willReturn(true); + + $lightFighterShipsGroup->hasType($destroyerType) + ->willReturn(false); + + $warshipShipsGroup->hasType($destroyerType) + ->willReturn(false); + + $destroyerShipsGroup->hasEnoughShips(10) + ->willReturn(true); + + $this->addShips([ + $destroyerShipsGroup, + ]); + + $this->hasEnoughShips([ + $destroyerType => 10, + ]) + ->shouldReturn(true); + } + + public function it_adds_ships_of_known_type( + ShipsGroupInterface $lightFighterShipsGroup, + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFightersToAdd, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + $warshipShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $lightFightersToAdd->getType() + ->willReturn($lightFighterType); + + $lightFightersToAdd->hasType($lightFighterType) + ->willReturn(true); + + $lightFighterShipsGroup->hasType($lightFighterType) + ->willReturn(true); + + $lightFighterShipsGroup->merge($lightFightersToAdd) + ->shouldBeCalledOnce(); + + $warshipShipsGroup->hasType($lightFighterType) + ->willReturn(false); + + $this->addShips([ + $lightFightersToAdd, + ]); + } + + public function it_throws_exception_on_adding_ships_to_a_fleet_already_being_in_journey( + Journey $journey, + ShipsGroupInterface $destroyerShipsGroup, + ): void { + $this->startJourney($journey); + + $this->shouldThrow(FleetAlreadyInJourneyException::class) + ->during('addShips', [[$destroyerShipsGroup]]); + } + + public function it_returns_zero_speed_on_empty_fleet(): void + { + $this->initialize([]); + + $this->getSpeed()->shouldReturn(0); + } + + public function it_returns_speed_of_slowest_ship_in_the_fleet( + ShipsGroupInterface $lightFighterShipsGroup, + ShipsGroupInterface $warshipShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + $warshipShipsGroup->getWrappedObject(), + ]); + + $lightFighterShipsGroup->getSpeed()->willReturn(15); + $warshipShipsGroup->getSpeed()->willReturn(500); + + $this->getSpeed()->shouldReturn(15); + } + + public function it_has_enough_ships_comparing_two_groups_of_ships( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + + $lightFighterShipsGroup->hasEnoughShips(15) + ->willReturn(true); + + $this->hasEnoughShips([ + $lightFighterType => 15, + ])->shouldReturn(true); + } + + public function it_has_not_enough_ships_comparing_two_groups_of_ships( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasEnoughShips(15) + ->willReturn(false); + + $this->hasEnoughShips([ + $lightFighterType => 15, + ])->shouldReturn(false); + } + + public function it_has_not_enough_ships_when_ship_type_has_not_been_found( + ShipsGroupInterface $warshipShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + + $this->hasEnoughShips([ + $lightFighterType => 15, + ])->shouldReturn(false); + } + + public function it_has_enough_ships_on_empty_input_array( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $this->hasEnoughShips([])->shouldReturn(false); + } + + public function it_has_more_ships_when_empty_array_is_given_as_an_input_but_it_has_any_ships_group( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $this->hasMoreShipsThan([])->shouldReturn(true); + } + + public function it_hasnt_more_ships_than_input_when_there_is_not_enough_ships( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasEnoughShips(15)->willReturn(true); + $lightFighterShipsGroup->hasMoreShipsThan(15)->willReturn(false); + + $this->hasMoreShipsThan([ + $lightFighterType => 15, + ])->shouldReturn(false); + } + + public function it_has_more_ships_than_input_when_has_enough_of_all_ships_and_at_least_one_ship_group_has_more_ships( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $warshipType = 'warship'; + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + $warshipShipsGroup->hasType($warshipType)->willReturn(true); + + $warshipShipsGroup->hasEnoughShips(300)->willReturn(true); + $warshipShipsGroup->hasMoreShipsThan(300)->willReturn(false); + + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasType($warshipType)->willReturn(false); + + $lightFighterShipsGroup->hasEnoughShips(15)->willReturn(true); + $lightFighterShipsGroup->hasMoreShipsThan(15)->willReturn(true); + + $this->hasMoreShipsThan([ + $lightFighterType => 15, + $warshipType => 300, + ])->shouldReturn(true); + } + + public function it_hasnt_more_ships_than_input_when_no_ship_group_has_more_ships( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup->getWrappedObject(), + $lightFighterShipsGroup->getWrappedObject(), + ]); + + $lightFighterType = 'light-fighter'; + $warshipType = 'warship'; + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + $warshipShipsGroup->hasType($warshipType)->willReturn(true); + + $warshipShipsGroup->hasEnoughShips(300)->willReturn(true); + $warshipShipsGroup->hasMoreShipsThan(300)->willReturn(false); + + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasType($warshipType)->willReturn(false); + + $lightFighterShipsGroup->hasEnoughShips(15)->willReturn(true); + $lightFighterShipsGroup->hasMoreShipsThan(15)->willReturn(false); + + $this->hasMoreShipsThan([ + $lightFighterType => 15, + $warshipType => 300, + ])->shouldReturn(false); + } + + public function it_throws_exception_when_cannot_split_fleet_having_not_enough_ships( + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $lightFighterType = 'light-fighter'; + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasEnoughShips(15)->willReturn(false); + + $this->shouldThrow(NotEnoughShipsException::class)->during('split', [ + [ + $lightFighterType => 15, + ], + ]); + } + + public function it_splits_ships( + ShipsGroupInterface $lightFighterShipsGroup, + ShipsGroupInterface $splitLightFighterShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup, + ]); + + $lightFighterType = 'light-fighter'; + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasEnoughShips(15)->willReturn(true); + $lightFighterShipsGroup->split(15)->willReturn($splitLightFighterShipsGroup); + + $splitLightFighterShipsGroup->getType()->willReturn($lightFighterType); + $splitLightFighterShipsGroup->getQuantity()->willReturn(15); + + $splitResult = $this->split([ + $lightFighterType => 15, + ]); + $splitResult->shouldBeArray(); + $splitResult->shouldHaveCount(1); + $splitResult[0]->shouldImplement(ShipsGroupInterface::class); + $splitResult[0]->getType()->shouldReturn($lightFighterType); + $splitResult[0]->getQuantity()->shouldReturn(15); + } + + public function it_does_nothing_on_splitting_fleet_when_requested_quantity_is_zero( + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $lightFighterType = 'light-fighter'; + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + + $this->shouldThrow(NotEnoughShipsException::class)->during('split', [ + [ + $lightFighterType => 0, + ], + ]); + } + + public function it_does_nothing_on_splitting_fleet_when_requested_quantity_is_less_than_zero( + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $lightFighterType = 'light-fighter'; + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + + $this->shouldThrow(NotEnoughShipsException::class)->during('split', [ + [ + $lightFighterType => -15, + ], + ]); + } + + public function it_omits_splitting_ships_group_when_requested_zero_ships_for_split( + ShipsGroupInterface $warshipShipsGroup, + ShipsGroupInterface $lightFighterShipsGroup, + ShipsGroupInterface $splitLightFighterShipsGroup, + ): void { + $this->initialize([ + $warshipShipsGroup, + $lightFighterShipsGroup, + ]); + + $lightFighterType = 'light-fighter'; + $warshipType = 'warship'; + + $warshipShipsGroup->hasType($lightFighterType)->willReturn(false); + $warshipShipsGroup->hasType($warshipType)->willReturn(true); + $warshipShipsGroup->hasEnoughShips(0)->willReturn(true); + + $lightFighterShipsGroup->hasType($warshipType)->willReturn(false); + $lightFighterShipsGroup->hasType($lightFighterType)->willReturn(true); + $lightFighterShipsGroup->hasEnoughShips(15)->willReturn(true); + $lightFighterShipsGroup->split(15)->willReturn($splitLightFighterShipsGroup); + + $splitLightFighterShipsGroup->getType()->willReturn($lightFighterType); + $splitLightFighterShipsGroup->getQuantity()->willReturn(15); + + $splitResult = $this->split([ + $warshipType => 0, + $lightFighterType => 15, + ]); + $splitResult->shouldBeArray(); + $splitResult->shouldHaveCount(1); + $splitResult[0]->shouldImplement(ShipsGroupInterface::class); + $splitResult[0]->getType()->shouldReturn($lightFighterType); + $splitResult[0]->getQuantity()->shouldReturn(15); + } + + public function it_is_not_during_journey_when_journey_is_null(): void + { + $this->isDuringJourney()->shouldReturn(false); + } + + public function it_is_during_journey_when_didnt_reach_target_and_return_points( + Journey $journey, + ): void { + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(false); + + $this->startJourney($journey); + + $this->isDuringJourney()->shouldReturn(true); + } + + public function it_is_not_during_journey_when_did_reach_target_point( + Journey $journey, + ): void { + $journey->didReachTargetPoint()->willReturn(true); + $journey->didReachReturnPoint()->willReturn(false); + + $this->startJourney($journey); + + $this->isDuringJourney()->shouldReturn(false); + } + + public function it_is_not_during_journey_when_did_reach_return_point( + Journey $journey, + ): void { + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(true); + + $this->startJourney($journey); + + $this->isDuringJourney()->shouldReturn(false); + } + + public function it_throws_exception_when_trying_to_start_journey_already_being_in_journey( + Journey $currentJourney, + Journey $nextJourney, + ): void { + $this->startJourney($currentJourney); + + $currentJourney->didReachTargetPoint()->willReturn(false); + $currentJourney->didReachReturnPoint()->willReturn(false); + + $this->shouldThrow(FleetAlreadyInJourneyException::class)->during('startJourney', [$nextJourney]); + } + + public function it_starts_the_journey( + Journey $journey, + ): void { + $this->startJourney($journey); + } + + public function it_throws_exception_on_returning_mission_type_not_being_in_journey(): void + { + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('getJourneyMissionType', []); + } + + public function it_returns_journey_mission_type( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(false); + $journey->getMissionType()->willReturn(MissionType::Transport); + + $this->getJourneyMissionType()->shouldReturn(MissionType::Transport); + } + + public function it_throws_exception_on_returning_journey_start_point_not_being_in_journey(): void + { + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('getJourneyStartPoint', []); + } + + public function it_returns_journey_start_point( + Journey $journey, + GalaxyPointInterface $startPoint, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(false); + $journey->getStartPoint()->willReturn($startPoint); + + $this->getJourneyStartPoint()->shouldReturn($startPoint); + } + + public function it_throws_exception_on_returning_journey_target_point_not_being_in_journey(): void + { + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('getJourneyTargetPoint', []); + } + + public function it_returns_journey_target_point( + Journey $journey, + GalaxyPointInterface $targetPoint, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(false); + $journey->getTargetPoint()->willReturn($targetPoint); + + $this->getJourneyTargetPoint()->shouldReturn($targetPoint); + } + + public function it_throws_exception_on_returning_journey_return_point_not_being_in_journey(): void + { + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('getJourneyReturnPoint', []); + } + + public function it_returns_journey_return_point( + Journey $journey, + GalaxyPointInterface $returnPoint, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(false); + $journey->getReturnPoint()->willReturn($returnPoint); + + $this->getJourneyReturnPoint()->shouldReturn($returnPoint); + } + + public function it_didnt_reach_journey_target_point_when_journey_is_null(): void + { + $this->didReachJourneyTargetPoint()->shouldReturn(false); + } + + public function it_didnt_reach_journey_target_point_when_journey_tells_that( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + + $this->didReachJourneyTargetPoint()->shouldReturn(false); + } + + public function it_did_reach_journey_target_point_when_journey_tells_that( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(true); + + $this->didReachJourneyTargetPoint()->shouldReturn(true); + } + + public function it_throws_exception_trying_to_reach_journey_target_point_when_fleet_is_not_in_journey_yet(): void + { + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('tryToReachJourneyTargetPoint'); + } + + public function it_throws_exception_trying_to_reach_journey_target_point_when_fleet_has_finished_the_journey( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachReturnPoint()->willReturn(true); + + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('tryToReachJourneyTargetPoint'); + } + + public function it_returns_early_when_trying_to_reach_journey_target_point_but_not_reached_it_yet( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + $journey->didReachReturnPoint()->willReturn(false); + + $this->tryToReachJourneyTargetPoint(); + } + + public function it_reaches_journey_target_point_and_station_there( + Journey $journey, + GalaxyPointInterface $journeyTargetPoint, + ): void { + $this->startJourney($journey); + + $journey->didReachReturnPoint()->willReturn(false); + $journey->didReachTargetPoint()->willReturn(true); + $journey->doesPlanToStationOnTarget()->willReturn(true); + $journey->getTargetPoint()->willReturn($journeyTargetPoint); + + $journey->reachTargetPoint()->shouldBeCalledOnce(); + + $this->tryToReachJourneyTargetPoint(); + $this->getStationingGalaxyPoint()->shouldReturn($journeyTargetPoint); + } + + public function it_does_nothing_when_trying_to_reach_journey_target_point_but_currently_flies_back( + Journey $journey, + GalaxyPointInterface $journeyTargetPoint, + ): void { + $this->startJourney($journey); + + $journey->didReachReturnPoint()->willReturn(false); + $journey->didReachTargetPoint()->willReturn(true); + $journey->doesPlanToStationOnTarget()->willReturn(false); + $journey->getTargetPoint()->willReturn($journeyTargetPoint); + $journey->doesFlyBack()->willReturn(true); + + $this->tryToReachJourneyTargetPoint(); + } + + public function it_tries_to_reach_journey_target_point_but_doesnt_fly_back_yet( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachReturnPoint()->willReturn(false); + $journey->didReachTargetPoint()->willReturn(true); + $journey->doesPlanToStationOnTarget()->willReturn(false); + $journey->doesFlyBack()->willReturn(false); + + $journey->reachTargetPoint(); + + $this->tryToReachJourneyTargetPoint(); + } + + public function it_throws_exception_trying_to_reach_journey_return_point_when_fleet_is_not_in_the_journey_yet(): void + { + $this->shouldThrow(FleetNotInJourneyYetException::class)->during('tryToReachJourneyReturnPoint'); + } + + public function it_throws_exception_trying_to_reach_journey_return_point_when_fleet_did_not_reach_target_point_yet( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(false); + + $this->shouldThrow(FleetHasNotYetReachedTheTargetPointException::class)->during('tryToReachJourneyReturnPoint'); + } + + public function it_returns_early_when_trying_to_reach_journey_return_point_but_not_reached_it_yet( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachTargetPoint()->willReturn(true); + $journey->didReachReturnPoint()->willReturn(false); + + $this->tryToReachJourneyReturnPoint(); + } + + public function it_didnt_return_from_journey_when_journey_is_null(): void + { + $this->didReturnFromJourney()->shouldReturn(false); + } + + public function it_didnt_return_from_journey_when_journey_says_that( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachReturnPoint()->willReturn(false); + + $this->didReturnFromJourney()->shouldReturn(false); + } + + public function it_did_return_from_journey_when_journey_says_that( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->didReachReturnPoint()->willReturn(true); + + $this->didReturnFromJourney()->shouldReturn(true); + } + + public function it_does_no_flyback_when_journey_is_null(): void + { + $this->doesFlyBack()->shouldReturn(false); + } + + public function it_does_no_flyback_when_journey_tells_that( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->doesFlyBack()->willReturn(false); + + $this->doesFlyBack()->shouldReturn(false); + } + + public function it_does_the_flyback_when_journey_tells_that( + Journey $journey, + ): void { + $this->startJourney($journey); + + $journey->doesFlyBack()->willReturn(true); + + $this->doesFlyBack()->shouldReturn(true); + } + + public function it_throws_exception_on_cancelling_journey_which_is_null(): void + { + $this->shouldThrow()->during('cancelJourney', []); + } + + public function it_throws_exception_on_cancelling_journey_when_it_already_reached_target_point( + Journey $journey, + ): void { + $this->startJourney($journey); + $journey->didReachTargetPoint()->willReturn(true); + + $this->shouldThrow()->during('cancelJourney', []); + } + + public function it_cancels_journey( + Journey $journey, + ): void { + $this->startJourney($journey); + $journey->didReachTargetPoint()->willReturn(false); + $journey->cancel()->shouldBeCalledOnce(); + + $this->cancelJourney(); + } + + public function it_returns_load_capacity( + ShipsGroupInterface $lightFighterShipsGroup, + ShipsGroupInterface $warshipShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + $warshipShipsGroup->getWrappedObject(), + ]); + + $lightFighterShipsGroup->getLoadCapacity()->willReturn(500); + $warshipShipsGroup->getLoadCapacity()->willReturn(7500); + + $this->getLoadCapacity()->shouldReturn(8000); + } + + public function it_returns_zero_load_capacity_when_has_no_ships(): void + { + $this->getLoadCapacity()->shouldReturn(0); + } + + public function it_returns_resource_load_as_scalar_array(): void + { + $this->getResourcesLoad()->shouldReturn([ + "7dbe6a5c-e12c-4325-a38a-f2165873c263" => 500, + "e2a1295c-9390-47b9-99c6-dd5f0798954d" => 350, + ]); + } + + public function it_throws_exception_when_loading_resources_but_there_is_no_enough_capacity( + ResourcesInterface $newLoad, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + ]); + $newLoad->sum()->willReturn(500); + + $lightFighterShipsGroup->getLoadCapacity()->willReturn(300); + + $this->shouldThrow(NotEnoughFleetLoadCapacityException::class) + ->during('load', [ + $newLoad, + ]); + } + + public function it_throws_exception_when_loading_resources_on_already_loaded_fleet( + ResourcesInterface $newLoad, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + ]); + $newLoad->sum()->willReturn(500); + + $lightFighterShipsGroup->getLoadCapacity()->willReturn(1000); + + $this->shouldThrow(FleetAlreadyLoadedException::class) + ->during('load', [ + $newLoad, + ]); + } + + public function it_loads_resources( + ResourcesInterface $resourcesLoad, + ShipsGroupInterface $lightFighterShipsGroup, + ): void { + $this->initialize([ + $lightFighterShipsGroup->getWrappedObject(), + ]); + $this->unload(); + + $lightFighterShipsGroup->getLoadCapacity()->willReturn(500); + $resourcesLoad->sum()->willReturn(500); + + $this->load($resourcesLoad); + } + + public function it_returns_unloaded_resources(): void + { + $load = $this->unload(); + + $load->getAmount( + new ResourceId("7dbe6a5c-e12c-4325-a38a-f2165873c263") + )->shouldReturn(500); + $load->getAmount( + new ResourceId("e2a1295c-9390-47b9-99c6-dd5f0798954d") + )->shouldReturn(350); + } + + public function it_clears_on_unload(): void + { + $this->unload(); + + $this->getResourcesLoad()->shouldReturn([]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Entity/JourneySpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Entity/JourneySpec.php new file mode 100644 index 0000000..a6d86eb --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Entity/JourneySpec.php @@ -0,0 +1,277 @@ +initialize(MissionType::Transport, 50); + } + + private function initialize(MissionType $missionType, int $duration): void + { + $journeyId = "35f6e0a6-e9bb-4344-b1f1-299cbaeb1f25"; + $fleetId = "282294b8-ba92-46d1-b86a-4768e2a664b9"; + $startPoint = new GalaxyPoint(1, 2, 3); + $targetPoint = new GalaxyPoint(4, 5, 6); + + $this->beConstructedWith( + new JourneyId($journeyId), + new FleetId($fleetId), + $missionType, + $startPoint, + $targetPoint, + $duration, + ); + } + + public function it_has_identifier(): void + { + $this->getId()->getUuid()->shouldReturn("35f6e0a6-e9bb-4344-b1f1-299cbaeb1f25"); + } + + public function it_has_mission_type(): void + { + $this->getMissionType()->shouldReturn(MissionType::Transport); + } + + public function it_has_start_point(): void + { + $this->getStartPoint()->format()->shouldReturn("[1:2:3]"); + } + + public function it_has_target_point(): void + { + $this->getTargetPoint()->format()->shouldReturn("[4:5:6]"); + } + + public function it_has_return_point(): void + { + $this->getReturnPoint()->format()->shouldReturn("[1:2:3]"); + } + + public function it_remembers_when_started_mission(): void + { + $now = new DateTime(); + $this->getStartedAt()->getTimestamp()->shouldReturn($now->getTimestamp()); + } + + public function it_knows_when_planned_to_reach_the_target_point(): void + { + $now = new DateTime(); + $this->getReachesTargetAt()->getTimestamp()->shouldReturn($now->getTimestamp() + 50); + } + + public function it_knows_the_default_value_of_real_time_for_reaching_the_target_point(): void + { + $now = new DateTime(); + $this->getPlannedReachTargetAt()->getTimestamp()->shouldReturn($now->getTimestamp() + 50); + } + + public function it_knows_when_planned_to_reach_the_return_point(): void + { + $now = new DateTime(); + $this->getPlannedReturnAt()->getTimestamp()->shouldReturn($now->getTimestamp() + 100); + } + + public function it_knows_the_default_value_of_real_time_for_reaching_the_return_point(): void + { + $now = new DateTime(); + $this->getReturnsAt()->getTimestamp()->shouldReturn($now->getTimestamp() + 100); + } + + public function it_does_plan_to_station_on_the_target_point(): void + { + $this->initialize(MissionType::Stationing, 50); + + $this->doesPlanToStationOnTarget()->shouldReturn(true); + } + + public function it_doesnt_plan_to_station_on_the_target_point(): void + { + $this->doesPlanToStationOnTarget()->shouldReturn(false); + } + + public function it_does_attack(): void + { + $this->initialize(MissionType::Attack, 50); + + $this->doesAttack()->shouldReturn(true); + } + + public function it_doesnt_attach(): void + { + $this->doesAttack()->shouldReturn(false); + } + + public function it_does_transport_resources(): void + { + $this->doesTransportResources()->shouldReturn(true); + } + + public function it_doesnt_transport_resources(): void + { + $this->initialize(MissionType::Attack, 50); + + $this->doesTransportResources()->shouldReturn(false); + } + + public function it_doesnt_do_flyback_by_default(): void + { + $this->doesFlyBack()->shouldReturn(false); + } + + public function it_does_flyback_after_reaching_target_point(): void + { + $this->initialize(MissionType::Transport, 0); + + $this->reachTargetPoint(); + $this->doesFlyBack()->shouldReturn(true); + } + + public function it_does_not_flyback_after_reaching_return_point(): void + { + $this->initialize(MissionType::Transport, 0); + + $this->reachTargetPoint(); + $this->reachReturnPoint(); + $this->doesFlyBack()->shouldReturn(false); + } + + public function it_does_not_flyback_after_cancelling_journey(): void + { + $this->cancel(); + $this->doesFlyBack()->shouldReturn(true); + } + + public function it_did_reach_target_point(): void + { + $this->initialize(MissionType::Transport, 0); + + $this->didReachTargetPoint()->shouldReturn(true); + } + + public function it_didnt_reach_target_point_yet(): void + { + $this->didReachTargetPoint()->shouldReturn(false); + } + + public function it_throws_exception_when_cant_reach_target_point_on_flyback(): void + { + $this->initialize(MissionType::Transport, 0); + $this->reachTargetPoint(); + + $this->shouldThrow(FleetOnFlyBackException::class) + ->during('reachTargetPoint', []); + } + + public function it_throws_exception_when_reaching_target_point_but_flying_time_didnt_pass(): void + { + $this->shouldThrow(FleetHasNotYetReachedTheTargetPointException::class) + ->during('reachTargetPoint', []); + } + + public function it_reaches_target_point_and_station_on_planet(): void + { + $this->initialize(MissionType::Stationing, 0); + $this->reachTargetPoint(); + + $now = new DateTimeImmutable(); + $this->getReturnsAt()->getTimestamp()->shouldReturn($now->getTimestamp()); + $this->doesFlyBack()->shouldReturn(false); + } + + public function it_reaches_target_point_and_turns_around(): void + { + $this->initialize(MissionType::Transport, 0); + $this->reachTargetPoint(); + + $this->doesFlyBack()->shouldReturn(true); + } + + public function it_did_reach_return_point(): void + { + $this->initialize(MissionType::Transport, 0); + + $this->didReachReturnPoint()->shouldReturn(true); + } + + public function it_didnt_reach_return_point_yet(): void + { + $this->didReachReturnPoint()->shouldReturn(false); + } + + public function it_throws_exception_when_reaching_return_point_not_being_on_flyback(): void + { + $this->shouldThrow(FleetNotOnFlyBackException::class) + ->during('reachReturnPoint', []); + } + + public function it_throws_exception_when_reaching_return_point_but_flying_time_didnt_pass(): void + { + throw new SkippingException("Cannot test the behaviour with current implementation"); + } + + public function it_reaches_return_point(): void + { + throw new SkippingException("Cannot test the behaviour with current implementation"); + } + + public function it_is_cancelled(): void + { + $this->cancel(); + + $this->isCancelled()->shouldReturn(true); + } + + public function it_is_not_cancelled(): void + { + $this->isCancelled()->shouldReturn(false); + } + + public function it_throws_exception_when_cancelling_fleet_on_flyback(): void + { + $this->initialize(MissionType::Transport, 0); + $this->reachTargetPoint(); + + $this->shouldThrow(CannotCancelFleetJourneyOnFlyBackException::class) + ->during('cancel', []); + } + + public function it_throws_exception_when_cancelling_fleet_which_did_reach_target_point(): void + { + $this->initialize(MissionType::Transport, 0); + + $this->shouldThrow(CannotCancelFleetJourneyOnReachingTargetPointException::class) + ->during('cancel', []); + } + + public function it_throws_exception_when_cancelling_fleet_which_did_reach_return_point(): void + { + throw new SkippingException("Cannot test the behaviour with current implementation"); + } + + public function it_cancels_journey_and_turns_around(): void + { + $this->cancel(); + + $this->doesFlyBack()->shouldReturn(true); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasCancelledJourneyEventSpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasCancelledJourneyEventSpec.php new file mode 100644 index 0000000..505d49d --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasCancelledJourneyEventSpec.php @@ -0,0 +1,77 @@ + 500, + ]; + + $this->beConstructedWith($fleetId, $targetGalaxyPoint, $resourcesLoad); + } + + public function it_has_fleet_id(): void + { + $this->getFleetId()->shouldReturn("e29a0a69-380d-4871-8daa-ed8e42696fba"); + } + + public function it_has_target_galaxy_point(): void + { + $this->getTargetGalaxyPoint()->shouldReturn("[1:2:3]"); + } + + public function it_has_resources_load(): void + { + $this->getResourcesLoad()->shouldReturn([ + "e0c85daa-2b52-466d-9c7d-34063c0646d9" => 500, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_key(): void + { + $fleetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = [ + 350 => 500, + ]; + + $this->beConstructedWith( + $fleetId, + $targetGalaxyPoint, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $fleetId, $targetGalaxyPoint, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_value(): void + { + $fleetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => "1d9a563e-ed8a-4c5a-a918-903873f2adff", + ]; + + $this->beConstructedWith( + $fleetId, + $targetGalaxyPoint, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $fleetId, $targetGalaxyPoint, $resourcesLoad, + ]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyReturnPointEventSpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyReturnPointEventSpec.php new file mode 100644 index 0000000..288ab45 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyReturnPointEventSpec.php @@ -0,0 +1,97 @@ + 500, + ]; + + $this->beConstructedWith($fleetId, $startGalaxyPoint, $targetGalaxyPoint, $returnGalaxyPoint, $resourcesLoad); + } + + public function it_has_fleet_id(): void + { + $this->getFleetId()->shouldReturn("e29a0a69-380d-4871-8daa-ed8e42696fba"); + } + + public function it_has_start_galaxy_point(): void + { + $this->getStartGalaxyPoint()->shouldReturn("[1:2:3]"); + } + + public function it_has_target_galaxy_point(): void + { + $this->getTargetGalaxyPoint()->shouldReturn("[2:3:4]"); + } + + public function it_has_return_galaxy_point(): void + { + $this->getReturnGalaxyPoint()->shouldReturn("[5:6:7]"); + } + + public function it_has_resources_load(): void + { + $this->getResourcesLoad()->shouldReturn([ + "e0c85daa-2b52-466d-9c7d-34063c0646d9" => 500, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_key(): void + { + $fleetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $startGalaxyPoint = "[1:2:3]"; + $targetGalaxyPoint = "[2:3:4]"; + $returnGalaxyPoint = "[5:6:7]"; + $resourcesLoad = [ + 350 => 500, + ]; + + $this->beConstructedWith( + $fleetId, + $startGalaxyPoint, + $targetGalaxyPoint, + $returnGalaxyPoint, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $fleetId, $startGalaxyPoint, $targetGalaxyPoint, $returnGalaxyPoint, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_value(): void + { + $fleetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $startGalaxyPoint = "[1:2:3]"; + $targetGalaxyPoint = "[2:3:4]"; + $returnGalaxyPoint = "[5:6:7]"; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => "1d9a563e-ed8a-4c5a-a918-903873f2adff", + ]; + + $this->beConstructedWith( + $fleetId, + $startGalaxyPoint, + $targetGalaxyPoint, + $returnGalaxyPoint, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $fleetId, $startGalaxyPoint, $targetGalaxyPoint, $returnGalaxyPoint, $resourcesLoad, + ]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyTargetPointEventSpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyTargetPointEventSpec.php new file mode 100644 index 0000000..a863487 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyTargetPointEventSpec.php @@ -0,0 +1,92 @@ + 500, + ]; + + $this->beConstructedWith( + $mission, + $fleetId, + $targetGalaxyPoint, + $resourcesLoad, + ); + } + + public function it_has_mission(): void + { + $this->getMission()->shouldReturn("transport"); + } + + public function it_fleet_id(): void + { + $this->getFleetId()->shouldReturn("99315453-a2a6-4fa1-8f77-c99f7cfa0e10"); + } + + public function it_has_target_galaxy_point(): void + { + $this->getTargetGalaxyPoint()->shouldReturn("[1:2:3]"); + } + + public function it_has_resources_load(): void + { + $this->getResourcesLoad()->shouldReturn([ + "879dcdb6-4383-4b5c-89f9-6239cbfae8b0" => 500, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_key(): void + { + $mission = 'transport'; + $fleetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = [ + 350 => 500, + ]; + + $this->beConstructedWith( + $mission, + $fleetId, + $targetGalaxyPoint, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $mission, $fleetId, $targetGalaxyPoint, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_value(): void + { + $mission = 'transport'; + $fleetId = "e5835dba-aef6-4b15-9d7c-cfb177959f6d"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = [ + "5966f38b-2add-43e7-884b-64cf6569666f" => "1d9a563e-ed8a-4c5a-a918-903873f2adff", + ]; + + $this->beConstructedWith( + $mission, + $fleetId, + $targetGalaxyPoint, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $mission, $fleetId, $targetGalaxyPoint, $resourcesLoad, + ]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasStartedJourneyEventSpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasStartedJourneyEventSpec.php new file mode 100644 index 0000000..199d85a --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Event/FleetHasStartedJourneyEventSpec.php @@ -0,0 +1,176 @@ + 350, + ]; + $resourcesLoad = [ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => 500, + ]; + + $this->beConstructedWith( + $planetId, + $fleetId, + $fromGalaxyPoint, + $toGalaxyPoint, + $fuelRequirements, + $resourcesLoad + ); + } + + public function it_has_planet_id(): void + { + $this->getPlanetId()->shouldReturn("c64b2d71-b70c-428e-9395-ce7ffbf74945"); + } + + public function it_has_fleet_id(): void + { + $this->getFleetId()->shouldReturn("dc14be47-7c6a-4530-9327-1a6b3467ae23"); + } + + public function it_from_galaxy_point(): void + { + $this->getFromGalaxyPoint()->shouldReturn("[1:2:3]"); + } + + public function it_target_galaxy_point(): void + { + $this->getTargetGalaxyPoint()->shouldReturn("[2:3:4]"); + } + + public function it_has_fuel_requirements(): void + { + $this->getFuelRequirements()->shouldReturn([ + "00fb6403-ad85-4361-b4fe-785ba3075172" => 350, + ]); + } + + public function it_throws_exception_when_initializing_with_fuel_requirements_containing_invalid_array_key(): void + { + $planetId = "c64b2d71-b70c-428e-9395-ce7ffbf74945"; + $fleetId = "dc14be47-7c6a-4530-9327-1a6b3467ae23"; + $fromGalaxyPoint = "[1:2:3]"; + $toGalaxyPoint = "[2:3:4]"; + $fuelRequirements = [ + 500 => 350, + ]; + $resourcesLoad = [ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => 500, + ]; + + $this->beConstructedWith( + $planetId, + $fleetId, + $fromGalaxyPoint, + $toGalaxyPoint, + $fuelRequirements, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $fleetId, $fromGalaxyPoint, $toGalaxyPoint, $fuelRequirements, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_fuel_requirements_containing_invalid_array_value(): void + { + $planetId = "c64b2d71-b70c-428e-9395-ce7ffbf74945"; + $fleetId = "dc14be47-7c6a-4530-9327-1a6b3467ae23"; + $fromGalaxyPoint = "[1:2:3]"; + $toGalaxyPoint = "[2:3:4]"; + $fuelRequirements = [ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => "e6582b8e-0c64-44c7-bffb-76717b9a4473", + ]; + $resourcesLoad = [ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => 500, + ]; + + $this->beConstructedWith( + $planetId, + $fleetId, + $fromGalaxyPoint, + $toGalaxyPoint, + $fuelRequirements, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $fleetId, $fromGalaxyPoint, $toGalaxyPoint, $fuelRequirements, $resourcesLoad, + ]); + } + + public function it_has_resources_load(): void + { + $this->getResourcesLoad()->shouldReturn([ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => 500, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_key(): void + { + $planetId = "c64b2d71-b70c-428e-9395-ce7ffbf74945"; + $fleetId = "dc14be47-7c6a-4530-9327-1a6b3467ae23"; + $fromGalaxyPoint = "[1:2:3]"; + $toGalaxyPoint = "[2:3:4]"; + $fuelRequirements = [ + "00fb6403-ad85-4361-b4fe-785ba3075172" => 350, + ]; + $resourcesLoad = [ + 500 => 350, + ]; + + $this->beConstructedWith( + $planetId, + $fleetId, + $fromGalaxyPoint, + $toGalaxyPoint, + $fuelRequirements, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $fleetId, $fromGalaxyPoint, $toGalaxyPoint, $fuelRequirements, $resourcesLoad, + ]); + } + + public function it_throws_exception_when_initializing_with_resources_load_containing_invalid_array_value(): void + { + $planetId = "c64b2d71-b70c-428e-9395-ce7ffbf74945"; + $fleetId = "dc14be47-7c6a-4530-9327-1a6b3467ae23"; + $fromGalaxyPoint = "[1:2:3]"; + $toGalaxyPoint = "[2:3:4]"; + $fuelRequirements = [ + "00fb6403-ad85-4361-b4fe-785ba3075172" => 350, + ]; + $resourcesLoad = [ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => "e6582b8e-0c64-44c7-bffb-76717b9a4473", + ]; + + $this->beConstructedWith( + $planetId, + $fleetId, + $fromGalaxyPoint, + $toGalaxyPoint, + $fuelRequirements, + $resourcesLoad + ); + + $this->shouldThrow(InvalidArgumentException::class)->during('__construct', [ + $planetId, $fleetId, $fromGalaxyPoint, $toGalaxyPoint, $fuelRequirements, $resourcesLoad, + ]); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Factory/FleetFactorySpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Factory/FleetFactorySpec.php new file mode 100644 index 0000000..9ecc71c --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Factory/FleetFactorySpec.php @@ -0,0 +1,57 @@ +beConstructedWith($uuidGenerator); + } + + public function it_creates_fleet( + UuidGeneratorInterface $uuidGenerator, + ): void { + $fleetId = new FleetId("393acaa4-0fdf-44ab-afa9-352b3ac8e826"); + $uuidGenerator->generateNewFleetId()->willReturn($fleetId); + + $shipsTakingJourney = [new ShipsGroup( + 'light-fighter', + 10, + 50, + 200, + ), new ShipsGroup( + 'warship', + 100, + 20, + 5000, + )]; + $stationingGalaxyPoint = new GalaxyPoint(1, 2, 3); + $resourcesLoad = new Resources(); + $resourcesLoad->addResource( + new ResourceAmount(new ResourceId("8ddf9a4b-380d-4051-a1d7-9370ca4d7b3e"), 100), + ); + + $createdFleet = $this->create( + $shipsTakingJourney, + $stationingGalaxyPoint, + $resourcesLoad, + ); + $createdFleet->shouldBeAnInstanceOf(Fleet::class); + $createdFleet->getId()->shouldReturn($fleetId); + $createdFleet->getStationingGalaxyPoint()->shouldReturn($stationingGalaxyPoint); + $createdFleet->getResourcesLoad()->shouldReturn($resourcesLoad->toScalarArray()); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/Factory/JourneyFactorySpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/Factory/JourneyFactorySpec.php new file mode 100644 index 0000000..32fe717 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/Factory/JourneyFactorySpec.php @@ -0,0 +1,50 @@ +beConstructedWith($uuidGenerator); + } + + public function it_creates_journey( + UuidGeneratorInterface $uuidGenerator, + ): void { + $journeyId = new JourneyId("443f9c57-b73a-4230-af53-8eaf2c33ac93"); + $uuidGenerator->generateNewJourneyId()->willReturn($journeyId); + + $fleetId = new FleetId("8804df77-de2d-46b2-b5f9-bffe9b695fd6"); + $startGalaxyPoint = new GalaxyPoint(1, 2, 3); + $targetGalaxyPoint = new GalaxyPoint(4, 5, 6); + $journeyDuration = 500; + + $createdJourney = $this->createJourney( + $fleetId, + MissionType::Transport, + $startGalaxyPoint, + $targetGalaxyPoint, + $journeyDuration, + ); + $createdJourney->shouldBeAnInstanceOf(Journey::class); + $createdJourney->getStartPoint()->shouldReturn($startGalaxyPoint); + $createdJourney->getTargetPoint()->shouldReturn($targetGalaxyPoint); + + $now = new DateTimeImmutable(); + $createdJourney->getPlannedReachTargetAt()->getTimestamp()->shouldReturn($now->getTimestamp() + 500); + $createdJourney->getPlannedReturnAt()->getTimestamp()->shouldReturn($now->getTimestamp() + 1000); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/Domain/ShipsGroupSpec.php b/spec/TheGame/Application/Component/FleetJourney/Domain/ShipsGroupSpec.php new file mode 100644 index 0000000..e936abb --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/Domain/ShipsGroupSpec.php @@ -0,0 +1,125 @@ +beConstructedWith($type, $quantity, $speed, $unitLoadCapacity); + } + + public function it_has_type(): void + { + $this->getType()->shouldReturn("light-fighter"); + } + + public function it_has_quantity(): void + { + $this->getQuantity()->shouldReturn(10); + } + + public function it_checks_the_correct_type(): void + { + $this->hasType("light-fighter")->shouldReturn(true); + } + + public function it_checks_the_incorrect_type(): void + { + $this->hasType("warship")->shouldReturn(false); + } + + public function it_checks_whether_has_more_ships_than_quantity(): void + { + $this->hasMoreShipsThan(5)->shouldReturn(true); + } + + public function it_checks_whether_has_more_ships_than_a_quantity_but_has_not(): void + { + $this->hasMoreShipsThan(15)->shouldReturn(false); + } + + public function it_checks_whether_has_enough_ships(): void + { + $this->hasEnoughShips(5)->shouldReturn(true); + } + + public function it_checks_whether_has_enough_ships_but_has_not(): void + { + $this->hasEnoughShips(15)->shouldReturn(false); + } + + public function it_merges_ships( + ShipsGroupInterface $shipsGroup, + ): void { + $shipsGroup->getType()->willReturn('light-fighter'); + $shipsGroup->getQuantity()->willReturn(15); + $shipsGroup->setEmpty()->shouldBeCalledOnce(); + + $this->merge($shipsGroup); + $this->getQuantity()->shouldReturn(25); + } + + public function it_throws_exception_on_merging_ships_of_unsupported_type( + ShipsGroupInterface $shipsGroup, + ): void { + $shipsGroup->getType()->willReturn('warship'); + + $this->shouldThrow(CannotMergeShipGroupsOfDifferentTypeException::class)->during('merge', [$shipsGroup]); + } + + public function it_splits_into_second_group(): void + { + $secondGroup = $this->split(5); + $secondGroup->getType()->shouldReturn('light-fighter'); + $secondGroup->getQuantity()->shouldReturn(5); + $secondGroup->getSpeed()->shouldReturn(35); + $secondGroup->getUnitLoadCapacity()->shouldReturn(20); + + $this->getQuantity()->shouldReturn(5); + } + + public function it_throws_exception_on_splitting_when_hasnt_enough_ships(): void + { + $this->shouldThrow(NotEnoughShipsException::class)->during('split', [15]); + } + + public function it_has_speed(): void + { + $this->getSpeed()->shouldReturn(35); + } + + public function it_has_load_capacity(): void + { + $this->getLoadCapacity()->shouldReturn(200); + } + + public function it_has_load_capacity_of_single_ship(): void + { + $this->getUnitLoadCapacity()->shouldReturn(20); + } + + public function it_sets_group_empty(): void + { + $this->setEmpty(); + + $this->isEmpty()->shouldReturn(true); + } + + public function it_is_not_empty(): void + { + $this->isEmpty()->shouldReturn(false); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/EventListener/AddNewlyConstructedShipsToFleetEventListenerSpec.php b/spec/TheGame/Application/Component/FleetJourney/EventListener/AddNewlyConstructedShipsToFleetEventListenerSpec.php new file mode 100644 index 0000000..6f429df --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/EventListener/AddNewlyConstructedShipsToFleetEventListenerSpec.php @@ -0,0 +1,94 @@ +beConstructedWith( + $fleetRepository, + $fleetFactory, + $navigator, + $fleetJourneyContext, + ); + } + + public function it_adds_group_of_ships_to_the_fleet( + FleetRepositoryInterface $fleetRepository, + FleetJourneyContextInterface $fleetJourneyContext, + Fleet $fleet, + ): void { + $planetId = "a0fb9362-cbd3-4488-a65f-f835d318e2fc"; + $shipType = "light-fighter"; + $quantity = 10; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn($fleet); + + $shipBaseSpeed = 10; + $fleetJourneyContext->getShipBaseSpeed($shipType)->willReturn($shipBaseSpeed); + $shipCapacityLoad = 300; + $fleetJourneyContext->getShipLoadCapacity($shipType)->willReturn($shipCapacityLoad); + + $shipGroup = new ShipsGroup($shipType, $quantity, $shipBaseSpeed, $shipCapacityLoad); + $fleet->addShips([$shipGroup])->shouldBeCalledOnce(); + + $event = new NewShipsHaveBeenConstructedEvent($planetId, $shipType, $quantity); + $this->__invoke($event); + } + + public function it_creates_a_fleet_and_adds_group_of_ships_to_them( + FleetRepositoryInterface $fleetRepository, + FleetFactoryInterface $fleetFactory, + NavigatorInterface $navigator, + FleetJourneyContextInterface $fleetJourneyContext, + Fleet $fleet, + GalaxyPointInterface $planetGalaxyPoint, + ): void { + $planetId = "a0fb9362-cbd3-4488-a65f-f835d318e2fc"; + $shipType = "light-fighter"; + $quantity = 10; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn(null); + + $navigator->getPlanetPoint(new PlanetId($planetId)) + ->willReturn($planetGalaxyPoint); + + $fleetFactory->create( + [], + $planetGalaxyPoint, + new Resources(), + )->willReturn($fleet); + + $shipBaseSpeed = 10; + $fleetJourneyContext->getShipBaseSpeed($shipType)->willReturn($shipBaseSpeed); + $shipCapacityLoad = 300; + $fleetJourneyContext->getShipLoadCapacity($shipType)->willReturn($shipCapacityLoad); + + $shipGroup = new ShipsGroup($shipType, $quantity, $shipBaseSpeed, $shipCapacityLoad); + $fleet->addShips([$shipGroup])->shouldBeCalledOnce(); + + $event = new NewShipsHaveBeenConstructedEvent($planetId, $shipType, $quantity); + $this->__invoke($event); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/EventListener/StationFleetOnReachingTargetPointEventListenerSpec.php b/spec/TheGame/Application/Component/FleetJourney/EventListener/StationFleetOnReachingTargetPointEventListenerSpec.php new file mode 100644 index 0000000..cfe7d65 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/EventListener/StationFleetOnReachingTargetPointEventListenerSpec.php @@ -0,0 +1,120 @@ +beConstructedWith($fleetRepository, $navigator); + } + + public function it_does_nothing_when_mission_type_is_not_stationing(): void + { + $mission = "transport"; + $fleetId = "22610343-1ee7-4ef5-84b5-4e285a68dba5"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = []; + + $event = new FleetHasReachedJourneyTargetPointEvent($mission, $fleetId, $targetGalaxyPoint, $resourcesLoad); + $this->__invoke($event); + } + + public function it_throws_exception_when_fleet_joining_the_planet_has_not_been_found( + FleetRepositoryInterface $fleetRepository, + ): void { + $mission = "stationing"; + $fleetId = "22610343-1ee7-4ef5-84b5-4e285a68dba5"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = []; + + $fleetRepository->find(new FleetId($fleetId))->willReturn(null); + + $event = new FleetHasReachedJourneyTargetPointEvent($mission, $fleetId, $targetGalaxyPoint, $resourcesLoad); + $this->shouldThrow(InconsistentModelException::class)->during('__invoke', [$event]); + } + + public function it_throws_exception_when_landing_target_planet_doesnt_exist( + FleetRepositoryInterface $fleetRepository, + NavigatorInterface $navigator, + Fleet $joiningFleet, + ): void { + $mission = "stationing"; + $fleetId = "22610343-1ee7-4ef5-84b5-4e285a68dba5"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = []; + + $resolvedTargetGalaxyPoint = new GalaxyPoint(1, 2, 3); + $fleetRepository->find(new FleetId($fleetId))->willReturn($joiningFleet); + $navigator->getPlanetId($resolvedTargetGalaxyPoint) + ->willReturn(null); + + $event = new FleetHasReachedJourneyTargetPointEvent($mission, $fleetId, $targetGalaxyPoint, $resourcesLoad); + $this->shouldThrow(InconsistentModelException::class)->during('__invoke', [$event]); + } + + public function it_lands_on_planet_when_no_fleet_exists_on_the_planet( + FleetRepositoryInterface $fleetRepository, + NavigatorInterface $navigator, + PlanetIdInterface $landingPlanetId, + Fleet $joiningFleet, + ): void { + $mission = "stationing"; + $fleetId = "22610343-1ee7-4ef5-84b5-4e285a68dba5"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = []; + + $resolvedTargetGalaxyPoint = new GalaxyPoint(1, 2, 3); + $fleetRepository->find(new FleetId($fleetId))->willReturn($joiningFleet); + $navigator->getPlanetId($resolvedTargetGalaxyPoint) + ->willReturn($landingPlanetId); + $fleetRepository->findStationingOnPlanet($landingPlanetId) + ->willReturn(null); + + $joiningFleet->landOnPlanet($resolvedTargetGalaxyPoint) + ->shouldBeCalledOnce(); + + $event = new FleetHasReachedJourneyTargetPointEvent($mission, $fleetId, $targetGalaxyPoint, $resourcesLoad); + $this->__invoke($event); + } + + public function it_merges_incoming_fleet_with_the_fleet_already_stationing_on_planet( + FleetRepositoryInterface $fleetRepository, + NavigatorInterface $navigator, + PlanetIdInterface $landingPlanetId, + Fleet $alreadyStationingFleet, + Fleet $joiningFleet, + ): void { + $mission = "stationing"; + $fleetId = "22610343-1ee7-4ef5-84b5-4e285a68dba5"; + $targetGalaxyPoint = "[1:2:3]"; + $resourcesLoad = []; + + $resolvedTargetGalaxyPoint = new GalaxyPoint(1, 2, 3); + $fleetRepository->find(new FleetId($fleetId))->willReturn($joiningFleet); + $navigator->getPlanetId($resolvedTargetGalaxyPoint) + ->willReturn($landingPlanetId); + $fleetRepository->findStationingOnPlanet($landingPlanetId) + ->willReturn($alreadyStationingFleet); + + $alreadyStationingFleet->merge($joiningFleet) + ->shouldBeCalledOnce(); + + $event = new FleetHasReachedJourneyTargetPointEvent($mission, $fleetId, $targetGalaxyPoint, $resourcesLoad); + $this->__invoke($event); + } +} diff --git a/spec/TheGame/Application/Component/FleetJourney/FleetResolverSpec.php b/spec/TheGame/Application/Component/FleetJourney/FleetResolverSpec.php new file mode 100644 index 0000000..36c0bc2 --- /dev/null +++ b/spec/TheGame/Application/Component/FleetJourney/FleetResolverSpec.php @@ -0,0 +1,295 @@ +beConstructedWith( + $fleetRepository, + $fleetFactory, + $journeyContext, + $resourceAvailabilityChecker, + ); + } + + public function it_throws_exception_when_cannot_resolve_fleet_but_no_fleet_is_already_stationing_on_planet( + FleetRepositoryInterface $fleetRepository, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + ): void { + $planetId = "5fcb0b31-0393-495d-b7da-4bec562864e7"; + $shipsTakingJourney = [ + 'light-fighter' => 15, + 'warship' => 1200, + ]; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn(null); + + $this->shouldThrow(NoFleetStationingOnPlanetException::class)->during( + 'resolveFromPlanet', + [new PlanetId($planetId), $shipsTakingJourney, $resourcesLoad, $targetGalaxyPoint] + ); + } + + public function it_throws_exception_when_not_enough_ships_are_stationing_on_planet( + FleetRepositoryInterface $fleetRepository, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + Fleet $stationingFleet, + ): void { + $planetId = "5fcb0b31-0393-495d-b7da-4bec562864e7"; + $shipsTakingJourney = [ + 'light-fighter' => 15, + 'warship' => 1200, + ]; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn($stationingFleet); + + $stationingFleet->hasEnoughShips($shipsTakingJourney) + ->willReturn(false); + + $this->shouldThrow(NotEnoughShipsException::class)->during( + 'resolveFromPlanet', + [new PlanetId($planetId), $shipsTakingJourney, $resourcesLoad, $targetGalaxyPoint] + ); + } + + public function it_splits_ships_from_current_fleet_when_resolving_less_ships_that_stationing_fleet_contains( + FleetRepositoryInterface $fleetRepository, + FleetJourneyContextInterface $journeyContext, + ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, + FleetFactoryInterface $fleetFactory, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + Fleet $stationingFleet, + ShipsGroupInterface $lightFighterShips, + ShipsGroupInterface $warshipShips, + Fleet $resolvedFleet, + GalaxyPointInterface $startGalaxyPoint, + ResourcesInterface $fuelRequirements, + ): void { + $planetId = "5fcb0b31-0393-495d-b7da-4bec562864e7"; + $shipsTakingJourney = [ + 'light-fighter' => 15, + 'warship' => 1200, + ]; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn($stationingFleet); + + $stationingFleet->hasEnoughShips($shipsTakingJourney) + ->willReturn(true); + + $stationingFleet->hasMoreShipsThan($shipsTakingJourney) + ->willReturn(true); + + $fleetSplitResult = [ + $lightFighterShips->getWrappedObject(), + $warshipShips->getWrappedObject(), + ]; + $stationingFleet->split($shipsTakingJourney) + ->willReturn($fleetSplitResult); + + $fleetFactory->create( + $fleetSplitResult, + $startGalaxyPoint, + $resourcesLoad, + )->willReturn($resolvedFleet); + + $stationingFleet->getStationingGalaxyPoint() + ->willReturn($startGalaxyPoint); + + $journeyContext->calculateFuelRequirements( + $startGalaxyPoint, + $targetGalaxyPoint, + $shipsTakingJourney, + )->willReturn($fuelRequirements); + + $resourcesLoad->add($fuelRequirements)->shouldBeCalledOnce(); + + $resourceAvailabilityChecker->check(new PlanetId($planetId), $resourcesLoad) + ->willReturn(true); + + $resourcesLoad->sum()->willReturn(600); + $resolvedFleet->getLoadCapacity()->willReturn(600); + + $resolvedFleet->load($resourcesLoad) + ->shouldBeCalledOnce(); + + $this->resolveFromPlanet(new PlanetId($planetId), $shipsTakingJourney, $resourcesLoad, $targetGalaxyPoint) + ->shouldReturn($resolvedFleet); + } + + public function it_resolves_fleet_by_taking_all_already_stationing_fleet( + FleetRepositoryInterface $fleetRepository, + FleetJourneyContextInterface $journeyContext, + ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + Fleet $stationingFleet, + GalaxyPointInterface $startGalaxyPoint, + ResourcesInterface $fuelRequirements, + ): void { + $planetId = "5fcb0b31-0393-495d-b7da-4bec562864e7"; + $shipsTakingJourney = [ + 'light-fighter' => 15, + 'warship' => 1200, + ]; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn($stationingFleet); + + $stationingFleet->hasEnoughShips($shipsTakingJourney) + ->willReturn(true); + + $stationingFleet->hasMoreShipsThan($shipsTakingJourney) + ->willReturn(false); + + $stationingFleet->getStationingGalaxyPoint() + ->willReturn($startGalaxyPoint); + + $journeyContext->calculateFuelRequirements( + $startGalaxyPoint, + $targetGalaxyPoint, + $shipsTakingJourney, + )->willReturn($fuelRequirements); + + $resourcesLoad->add($fuelRequirements)->shouldBeCalledOnce(); + + $resourceAvailabilityChecker->check(new PlanetId($planetId), $resourcesLoad) + ->willReturn(true); + + $resourcesLoad->sum()->willReturn(600); + $stationingFleet->getLoadCapacity()->willReturn(600); + + $stationingFleet->load($resourcesLoad) + ->shouldBeCalledOnce(); + + $this->resolveFromPlanet(new PlanetId($planetId), $shipsTakingJourney, $resourcesLoad, $targetGalaxyPoint) + ->shouldReturn($stationingFleet); + } + + public function it_throws_exception_when_there_is_not_enough_resources_for_fleet_load_on_planet_to_start_journey( + FleetRepositoryInterface $fleetRepository, + FleetJourneyContextInterface $journeyContext, + ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + Fleet $stationingFleet, + GalaxyPointInterface $startGalaxyPoint, + ResourcesInterface $fuelRequirements, + ): void { + $planetId = "5fcb0b31-0393-495d-b7da-4bec562864e7"; + $shipsTakingJourney = [ + 'light-fighter' => 15, + 'warship' => 1200, + ]; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn($stationingFleet); + + $stationingFleet->hasEnoughShips($shipsTakingJourney) + ->willReturn(true); + + $stationingFleet->hasMoreShipsThan($shipsTakingJourney) + ->willReturn(false); + + $stationingFleet->getStationingGalaxyPoint() + ->willReturn($startGalaxyPoint); + + $journeyContext->calculateFuelRequirements( + $startGalaxyPoint, + $targetGalaxyPoint, + $shipsTakingJourney, + )->willReturn($fuelRequirements); + + $resourcesLoad->add($fuelRequirements)->shouldBeCalledOnce(); + + $resourceAvailabilityChecker->check(new PlanetId($planetId), $resourcesLoad) + ->willReturn(false); + + $this->shouldThrow(NotEnoughResourcesOnPlanetForFleetLoadException::class) + ->during('resolveFromPlanet', [ + new PlanetId($planetId), $shipsTakingJourney, $resourcesLoad, $targetGalaxyPoint, + ]); + } + + public function it_throws_exception_when_fleet_has_not_enough_load_capacity( + FleetRepositoryInterface $fleetRepository, + FleetJourneyContextInterface $journeyContext, + ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + Fleet $stationingFleet, + GalaxyPointInterface $startGalaxyPoint, + ResourcesInterface $fuelRequirements, + ): void { + $planetId = "5fcb0b31-0393-495d-b7da-4bec562864e7"; + $shipsTakingJourney = [ + 'light-fighter' => 15, + 'warship' => 1200, + ]; + + $fleetRepository->findStationingOnPlanet(new PlanetId($planetId)) + ->willReturn($stationingFleet); + + $stationingFleet->hasEnoughShips($shipsTakingJourney) + ->willReturn(true); + + $stationingFleet->hasMoreShipsThan($shipsTakingJourney) + ->willReturn(false); + + $stationingFleet->getStationingGalaxyPoint() + ->willReturn($startGalaxyPoint); + + $journeyContext->calculateFuelRequirements( + $startGalaxyPoint, + $targetGalaxyPoint, + $shipsTakingJourney, + )->willReturn($fuelRequirements); + + $resourcesLoad->add($fuelRequirements)->shouldBeCalledOnce(); + + $resourceAvailabilityChecker->check(new PlanetId($planetId), $resourcesLoad) + ->willReturn(true); + + $resourcesLoad->sum()->willReturn(600); + $stationingFleet->getLoadCapacity()->willReturn(300); + + $fleetId = "07db1a01-d585-4071-9ee0-2b72b0a471db"; + $stationingFleet->getId()->willReturn(new FleetId($fleetId)); + + $this->shouldThrow(NotEnoughFleetLoadCapacityException::class) + ->during( + 'resolveFromPlanet', + [ + new PlanetId($planetId), $shipsTakingJourney, $resourcesLoad, $targetGalaxyPoint] + ); + } +} diff --git a/spec/TheGame/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerSpec.php b/spec/TheGame/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerSpec.php index 97cd344..8a77704 100644 --- a/spec/TheGame/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerSpec.php +++ b/spec/TheGame/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerSpec.php @@ -10,7 +10,7 @@ use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\Domain\ResourceAmount; use TheGame\Application\SharedKernel\Domain\ResourceId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirements; +use TheGame\Application\SharedKernel\Domain\Resources; use TheGame\Application\SharedKernel\Exception\InconsistentModelException; final class ResourceAvailabilityCheckerSpec extends ObjectBehavior @@ -30,9 +30,9 @@ public function it_throws_exception_when_didnt_find_aggregate( $storagesRepository->findForPlanet(new PlanetId($planetId)) ->willReturn(null); - $requirements = new ResourceRequirements(); + $requirements = new Resources(); $resourceAmount = new ResourceAmount(new ResourceId($resourceId), 5); - $requirements->add($resourceAmount); + $requirements->addResource($resourceAmount); $this->shouldThrow(InconsistentModelException::class) ->during('check', [ @@ -51,9 +51,9 @@ public function it_returns_true_when_has_enough_resources( $storagesRepository->findForPlanet(new PlanetId($planetId)) ->willReturn($aggregate); - $requirements = new ResourceRequirements(); + $requirements = new Resources(); $resourceAmount = new ResourceAmount(new ResourceId($resourceId), 5); - $requirements->add($resourceAmount); + $requirements->addResource($resourceAmount); $aggregate->hasEnough($requirements)->willReturn(true); @@ -71,9 +71,9 @@ public function it_returns_false_when_hasnt_enough_resources( $storagesRepository->findForPlanet(new PlanetId($planetId)) ->willReturn($aggregate); - $requirements = new ResourceRequirements(); + $requirements = new Resources(); $resourceAmount = new ResourceAmount(new ResourceId($resourceId), 5); - $requirements->add($resourceAmount); + $requirements->addResource($resourceAmount); $aggregate->hasEnough($requirements)->willReturn(false); diff --git a/spec/TheGame/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandlerSpec.php b/spec/TheGame/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandlerSpec.php index 15a7512..99b10e1 100644 --- a/spec/TheGame/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandlerSpec.php @@ -14,7 +14,7 @@ use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\Domain\ResourceAmount; use TheGame\Application\SharedKernel\Domain\ResourceId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirements; +use TheGame\Application\SharedKernel\Domain\Resources; use TheGame\Application\SharedKernel\EventBusInterface; use TheGame\Application\SharedKernel\Exception\InconsistentModelException; @@ -40,8 +40,8 @@ public function it_uses_supported_resources( ->willReturn($storagesCollection); $resourceAmount = new ResourceAmount(new ResourceId($resourceId), $amount); - $resourceRequirements = new ResourceRequirements(); - $resourceRequirements->add($resourceAmount); + $resourceRequirements = new Resources(); + $resourceRequirements->addResource($resourceAmount); $storagesCollection->supports($resourceAmount) ->willReturn(true); @@ -74,8 +74,8 @@ public function it_throws_exception_when_has_not_enough_resources( ->willReturn($storagesCollection); $resourceAmount = new ResourceAmount(new ResourceId($resourceId), $amount); - $resourcesRequirements = new ResourceRequirements(); - $resourcesRequirements->add($resourceAmount); + $resourcesRequirements = new Resources(); + $resourcesRequirements->addResource($resourceAmount); $storagesCollection->hasEnough($resourcesRequirements) ->willReturn(false); diff --git a/spec/TheGame/Application/Component/ResourceStorage/Domain/Entity/StoragesCollectionSpec.php b/spec/TheGame/Application/Component/ResourceStorage/Domain/Entity/StoragesCollectionSpec.php index 65cf59c..9ceb551 100644 --- a/spec/TheGame/Application/Component/ResourceStorage/Domain/Entity/StoragesCollectionSpec.php +++ b/spec/TheGame/Application/Component/ResourceStorage/Domain/Entity/StoragesCollectionSpec.php @@ -15,7 +15,7 @@ use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\Domain\ResourceAmountInterface; use TheGame\Application\SharedKernel\Domain\ResourceId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; final class StoragesCollectionSpec extends ObjectBehavior { @@ -72,7 +72,7 @@ public function it_doesnt_support_resource_amount( public function it_has_enough_resources_for_supported_resources( Storage $storage, ResourceAmountInterface $resourceAmount, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $this->add($storage); @@ -93,7 +93,7 @@ public function it_has_enough_resources_for_supported_resources( public function it_hasnt_enough_resources_for_supported_resource( Storage $storage, ResourceAmountInterface $resourceAmount, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $this->add($storage); @@ -114,7 +114,7 @@ public function it_hasnt_enough_resources_for_supported_resource( public function it_hasnt_enough_resources_because_of_not_supported_resource( Storage $storage, ResourceAmountInterface $resourceAmount, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $this->add($storage); diff --git a/spec/TheGame/Application/Component/ResourceStorage/EventListener/TakeResourcesOnStartingJourneyEventListenerSpec.php b/spec/TheGame/Application/Component/ResourceStorage/EventListener/TakeResourcesOnStartingJourneyEventListenerSpec.php new file mode 100644 index 0000000..5d9611b --- /dev/null +++ b/spec/TheGame/Application/Component/ResourceStorage/EventListener/TakeResourcesOnStartingJourneyEventListenerSpec.php @@ -0,0 +1,45 @@ +beConstructedWith($commandBus); + + $planetId = "c64b2d71-b70c-428e-9395-ce7ffbf74945"; + $fleetId = "dc14be47-7c6a-4530-9327-1a6b3467ae23"; + $fromGalaxyPoint = "[1:2:3]"; + $toGalaxyPoint = "[2:3:4]"; + $fuelRequirements = [ + "00fb6403-ad85-4361-b4fe-785ba3075172" => 350, + ]; + $resourcesLoad = [ + "2f374b4e-6502-4fd2-addd-42c162d2b826" => 500, + "00fb6403-ad85-4361-b4fe-785ba3075172" => 250, + ]; + + $commandBus->dispatch(Argument::type(UseResourceCommand::class)) + ->shouldBeCalledTimes(2); + + $event = new FleetHasStartedJourneyEvent( + $planetId, + $fleetId, + $fromGalaxyPoint, + $toGalaxyPoint, + $fuelRequirements, + $resourcesLoad, + ); + $this->__invoke($event); + } +} diff --git a/spec/TheGame/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyReturnPointEventListenerSpec.php b/spec/TheGame/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyReturnPointEventListenerSpec.php new file mode 100644 index 0000000..cf47baa --- /dev/null +++ b/spec/TheGame/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyReturnPointEventListenerSpec.php @@ -0,0 +1,84 @@ +beConstructedWith( + $navigator, + $commandBus, + ); + } + + public function it_dispatches_resources_from_returning_fleet_to_the_storages( + NavigatorInterface $navigator, + CommandBusInterface $commandBus, + PlanetIdInterface $planetId, + ): void { + $startCoordinates = "[1:2:3]"; + $targetCoordinates = "[4:5:6]"; + $returnCoordinates = "[7:8:9]"; + $fleetId = "086a3343-22cb-4673-88c8-368e9e9709f3"; + $resourcesLoad = [ + "a85f9598-2a03-442b-9781-b55ed748628b" => 250, + "d704acf0-b89a-44f8-a87d-ba9e3c7c89e3" => 300, + ]; + + $navigator->getPlanetId(Argument::type(GalaxyPointInterface::class)) + ->willReturn($planetId); + + $planetId->getUuid()->willReturn("84a12209-d808-447d-8f97-2559ce60eb3e"); + + $commandBus->dispatch(Argument::type(DispatchResourcesCommand::class)) + ->shouldBeCalledTimes(2); + + $event = new FleetHasReachedJourneyReturnPointEvent( + $fleetId, + $startCoordinates, + $targetCoordinates, + $returnCoordinates, + $resourcesLoad + ); + $this->__invoke($event); + } + + public function it_throws_exception_when_dispatching_resources_on_non_existing_planet( + NavigatorInterface $navigator, + ): void { + $startCoordinates = "[1:2:3]"; + $targetCoordinates = "[4:5:6]"; + $returnCoordinates = "[7:8:9]"; + $fleetId = "086a3343-22cb-4673-88c8-368e9e9709f3"; + $resourcesLoad = [ + "a85f9598-2a03-442b-9781-b55ed748628b" => 250, + ]; + + $navigator->getPlanetId(Argument::type(GalaxyPointInterface::class)) + ->willReturn(null); + + $event = new FleetHasReachedJourneyReturnPointEvent( + $fleetId, + $startCoordinates, + $targetCoordinates, + $returnCoordinates, + $resourcesLoad + ); + $this->shouldThrow(InconsistentModelException::class)->during('__invoke', [$event]); + } +} diff --git a/spec/TheGame/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyTargetPointEventListenerSpec.php b/spec/TheGame/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyTargetPointEventListenerSpec.php new file mode 100644 index 0000000..191846f --- /dev/null +++ b/spec/TheGame/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyTargetPointEventListenerSpec.php @@ -0,0 +1,126 @@ +beConstructedWith( + $navigator, + $commandBus, + ); + } + + public function it_dispatches_resources_from_fleet_transporting_and_reaching_target_point_to_the_storages( + NavigatorInterface $navigator, + CommandBusInterface $commandBus, + PlanetIdInterface $planetId, + ): void { + $targetCoordinates = "[4:5:6]"; + $fleetId = "086a3343-22cb-4673-88c8-368e9e9709f3"; + $resourcesLoad = [ + "a85f9598-2a03-442b-9781-b55ed748628b" => 250, + "d704acf0-b89a-44f8-a87d-ba9e3c7c89e3" => 300, + ]; + + $navigator->getPlanetId(Argument::type(GalaxyPointInterface::class)) + ->willReturn($planetId); + + $planetId->getUuid()->willReturn("84a12209-d808-447d-8f97-2559ce60eb3e"); + + $commandBus->dispatch(Argument::type(DispatchResourcesCommand::class)) + ->shouldBeCalledTimes(2); + + $event = new FleetHasReachedJourneyTargetPointEvent( + MissionType::Transport->value, + $fleetId, + $targetCoordinates, + $resourcesLoad + ); + $this->__invoke($event); + } + + public function it_dispatches_resources_from_fleet_stationing_and_reaching_target_point_to_the_storages( + NavigatorInterface $navigator, + CommandBusInterface $commandBus, + PlanetIdInterface $planetId, + ): void { + $targetCoordinates = "[4:5:6]"; + $fleetId = "086a3343-22cb-4673-88c8-368e9e9709f3"; + $resourcesLoad = [ + "a85f9598-2a03-442b-9781-b55ed748628b" => 250, + "d704acf0-b89a-44f8-a87d-ba9e3c7c89e3" => 300, + ]; + + $navigator->getPlanetId(Argument::type(GalaxyPointInterface::class)) + ->willReturn($planetId); + + $planetId->getUuid()->willReturn("84a12209-d808-447d-8f97-2559ce60eb3e"); + + $commandBus->dispatch(Argument::type(DispatchResourcesCommand::class)) + ->shouldBeCalledTimes(2); + + $event = new FleetHasReachedJourneyTargetPointEvent( + MissionType::Stationing->value, + $fleetId, + $targetCoordinates, + $resourcesLoad + ); + $this->__invoke($event); + } + + public function it_does_nothing_when_mission_is_attack(): void + { + $targetCoordinates = "[4:5:6]"; + $fleetId = "086a3343-22cb-4673-88c8-368e9e9709f3"; + $resourcesLoad = [ + "a85f9598-2a03-442b-9781-b55ed748628b" => 250, + "d704acf0-b89a-44f8-a87d-ba9e3c7c89e3" => 300, + ]; + + $event = new FleetHasReachedJourneyTargetPointEvent( + MissionType::Attack->value, + $fleetId, + $targetCoordinates, + $resourcesLoad + ); + $this->__invoke($event); + } + + public function it_throws_exception_when_dispatching_resources_on_non_existing_planet( + NavigatorInterface $navigator, + ): void { + $targetCoordinates = "[4:5:6]"; + $fleetId = "086a3343-22cb-4673-88c8-368e9e9709f3"; + $resourcesLoad = [ + "a85f9598-2a03-442b-9781-b55ed748628b" => 250, + ]; + + $navigator->getPlanetId(Argument::type(GalaxyPointInterface::class)) + ->willReturn(null); + + $event = new FleetHasReachedJourneyTargetPointEvent( + MissionType::Transport->value, + $fleetId, + $targetCoordinates, + $resourcesLoad + ); + $this->shouldThrow(InconsistentModelException::class)->during('__invoke', [$event]); + } +} diff --git a/spec/TheGame/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandlerSpec.php b/spec/TheGame/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandlerSpec.php index e86cbbb..63a1121 100644 --- a/spec/TheGame/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandlerSpec.php @@ -14,7 +14,7 @@ use TheGame\Application\Component\Shipyard\Exception\ShipyardHasNotBeenFoundException; use TheGame\Application\Component\Shipyard\ShipyardRepositoryInterface; use TheGame\Application\SharedKernel\Domain\PlanetId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\EventBusInterface; final class CancelJobCommandHandlerSpec extends ObjectBehavior @@ -32,7 +32,7 @@ public function it_throws_exception_when_shipyard_has_not_been_found( $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; $jobId = "C4743117-1F59-449C-A023-7E0B00E670A4"; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn(null); $command = new CancelJobCommand($shipyardId, $jobId); @@ -43,13 +43,13 @@ public function it_throws_exception_when_shipyard_has_not_been_found( public function it_cancels_job( ShipyardRepositoryInterface $shipyardRepository, Shipyard $shipyard, - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, EventBusInterface $eventBus, ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; $jobId = "C4743117-1F59-449C-A023-7E0B00E670A4"; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn($shipyard); $shipyard->cancelJob(new JobId($jobId)) diff --git a/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandlerSpec.php b/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandlerSpec.php index 12a55a3..3b4597a 100644 --- a/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandlerSpec.php @@ -18,7 +18,7 @@ use TheGame\Application\Component\Shipyard\Exception\ShipyardHasNotBeenFoundException; use TheGame\Application\Component\Shipyard\ShipyardRepositoryInterface; use TheGame\Application\SharedKernel\Domain\PlanetId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\EventBusInterface; final class ConstructCannonsCommandHandlerSpec extends ObjectBehavior @@ -46,7 +46,7 @@ public function it_throws_exception_when_shipyard_has_not_been_found( $cannonType = 'laser'; $quantity = 500; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn(null); $command = new ConstructCannonsCommand($shipyardId, $cannonType, $quantity); @@ -61,14 +61,14 @@ public function it_throws_exception_when_planet_hasnt_sufficient_resources( ShipyardContextInterface $shipyardBalanceContext, Job $job, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $singleCannonResourceRequirements, - ResourceRequirementsInterface $jobResourceRequirements, + ResourcesInterface $singleCannonResourceRequirements, + ResourcesInterface $jobResourceRequirements, ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; $cannonType = 'laser'; $quantity = 500; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn($shipyard); $shipyard->getCurrentLevel()->willReturn(15); @@ -107,15 +107,15 @@ public function it_queues_cannons_in_shipyard( JobFactoryInterface $jobFactory, Job $job, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $singleCannonResourceRequirements, - ResourceRequirementsInterface $jobResourceRequirements, + ResourcesInterface $singleCannonResourceRequirements, + ResourcesInterface $jobResourceRequirements, EventBusInterface $eventBus, ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; $cannonType = 'laser'; $quantity = 500; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn($shipyard); $shipyard->getCurrentLevel()->willReturn(15); diff --git a/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandlerSpec.php b/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandlerSpec.php index b3bc0d9..fa626e1 100644 --- a/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandlerSpec.php @@ -18,7 +18,7 @@ use TheGame\Application\Component\Shipyard\Exception\ShipyardHasNotBeenFoundException; use TheGame\Application\Component\Shipyard\ShipyardRepositoryInterface; use TheGame\Application\SharedKernel\Domain\PlanetId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\EventBusInterface; final class ConstructShipsCommandHandlerSpec extends ObjectBehavior @@ -46,7 +46,7 @@ public function it_throws_exception_when_shipyard_has_not_been_found( $shipType = 'light-fighter'; $quantity = 500; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn(null); $command = new ConstructShipsCommand($shipyardId, $shipType, $quantity); @@ -61,14 +61,14 @@ public function it_throws_exception_when_planet_hasnt_sufficient_resources( JobFactoryInterface $jobFactory, Job $job, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $singleShipResourceRequirements, - ResourceRequirementsInterface $jobResourceRequirements, + ResourcesInterface $singleShipResourceRequirements, + ResourcesInterface $jobResourceRequirements, ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; $shipType = 'light-fighter'; $quantity = 500; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn($shipyard); $shipyard->getCurrentLevel()->willReturn(15); @@ -107,15 +107,15 @@ public function it_queues_ships_in_shipyard( JobFactoryInterface $jobFactory, Job $job, ResourceAvailabilityCheckerInterface $resourceAvailabilityChecker, - ResourceRequirementsInterface $singleShipResourceRequirements, - ResourceRequirementsInterface $jobResourceRequirements, + ResourcesInterface $singleShipResourceRequirements, + ResourcesInterface $jobResourceRequirements, EventBusInterface $eventBus, ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; $shipType = 'light-fighter'; $quantity = 500; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn($shipyard); $shipyard->getCurrentLevel()->willReturn(15); diff --git a/spec/TheGame/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandlerSpec.php b/spec/TheGame/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandlerSpec.php index a5a2de8..580ff10 100644 --- a/spec/TheGame/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandlerSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandlerSpec.php @@ -15,6 +15,7 @@ use TheGame\Application\Component\Shipyard\Domain\ShipyardId; use TheGame\Application\Component\Shipyard\Exception\ShipyardHasNotBeenFoundException; use TheGame\Application\Component\Shipyard\ShipyardRepositoryInterface; +use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\EventBusInterface; final class FinishJobsCommandHandlerSpec extends ObjectBehavior @@ -36,7 +37,7 @@ public function it_throws_exception_when_shipyard_has_not_been_found( ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn(null); $command = new FinishJobsCommand($shipyardId); @@ -55,9 +56,11 @@ public function it_finishes_currently_done_jobs( ): void { $shipyardId = "3E303BDF-976A-4509-8611-A30D33781085"; - $shipyardRepository->findAggregate(new ShipyardId($shipyardId)) + $shipyardRepository->find(new ShipyardId($shipyardId)) ->willReturn($shipyard); + $shipyardPlanetId = new PlanetId("fab9bf85-9eb7-49d6-9e97-0f1e1c0cef44"); + $shipyard->getPlanetId()->willReturn($shipyardPlanetId); $shipyard->finishJobs()->willReturn($summary); $summary->getSummary()->willReturn([ @@ -65,12 +68,12 @@ public function it_finishes_currently_done_jobs( $summaryEntry2->getWrappedObject(), ]); - $event1 = new NewCannonsHaveBeenConstructedEvent('laser', 12); - $finishedConstructionEventFactory->createEvent($summaryEntry1) + $event1 = new NewCannonsHaveBeenConstructedEvent($shipyardPlanetId->getUuid(), 'laser', 12); + $finishedConstructionEventFactory->createEvent($summaryEntry1, $shipyardPlanetId) ->willReturn($event1); - $event2 = new NewShipsHaveBeenConstructedEvent('light-fighter', 5); - $finishedConstructionEventFactory->createEvent($summaryEntry2) + $event2 = new NewShipsHaveBeenConstructedEvent($shipyardPlanetId->getUuid(), 'light-fighter', 5); + $finishedConstructionEventFactory->createEvent($summaryEntry2, $shipyardPlanetId) ->willReturn($event2); $eventBus->dispatch($event1)->shouldBeCalledOnce(); diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Entity/JobSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Entity/JobSpec.php index 7d9d650..b930078 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Entity/JobSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Entity/JobSpec.php @@ -10,7 +10,7 @@ use TheGame\Application\Component\Shipyard\Domain\ValueObject\Ship; use TheGame\Application\SharedKernel\Domain\ResourceAmount; use TheGame\Application\SharedKernel\Domain\ResourceId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirements; +use TheGame\Application\SharedKernel\Domain\Resources; final class JobSpec extends ObjectBehavior { @@ -22,12 +22,12 @@ public function let(): void $constructibleDuration = 500; $productionLoad = 75; - $requirements = new ResourceRequirements(); - $requirements->add(new ResourceAmount( + $requirements = new Resources(); + $requirements->addResource(new ResourceAmount( new ResourceId("4B8CCD4D-6940-43F5-BFF5-A5FB35836294"), 450, )); - $requirements->add(new ResourceAmount( + $requirements->addResource(new ResourceAmount( new ResourceId("A0F8E286-CA29-40FC-B33A-D1DCCEAA72D5"), 220, )); diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Entity/ShipyardSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Entity/ShipyardSpec.php index 5cf8013..ee7c616 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Entity/ShipyardSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Entity/ShipyardSpec.php @@ -15,7 +15,7 @@ use TheGame\Application\Component\Shipyard\Domain\JobIdInterface; use TheGame\Application\Component\Shipyard\Domain\ShipyardId; use TheGame\Application\SharedKernel\Domain\PlanetId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; final class ShipyardSpec extends ObjectBehavior { @@ -74,10 +74,10 @@ public function it_throws_exception_when_trying_to_queue_more_ships_than_product public function it_returns_job_resource_requirements( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -100,10 +100,10 @@ public function it_returns_job_resource_requirements( public function it_throws_exception_when_getting_resource_requirements_of_job_which_is_not_found( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, JobIdInterface $job3Id, ): void { $this->stubTwoJobs( @@ -129,10 +129,10 @@ public function it_throws_exception_when_getting_resource_requirements_of_job_wh public function it_finishes_all_jobs( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -172,10 +172,10 @@ public function it_finishes_jobs_when_having_no_job(): void public function it_finishes_only_first_job_fully( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -205,10 +205,10 @@ public function it_finishes_only_first_job_fully( public function it_finishes_only_first_job_partially( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -238,10 +238,10 @@ public function it_finishes_only_first_job_partially( public function it_finishes_first_job_fully_and_second_job_partially( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -272,10 +272,10 @@ public function it_finishes_first_job_fully_and_second_job_partially( public function it_finishes_both_first_and_second_jobs_fully( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -307,11 +307,11 @@ private function stubTwoJobs( Job $job1, JobIdInterface $job1Id, int $duration1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, Job $job2, JobIdInterface $job2Id, int $duration2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $job1Id->getUuid()->willReturn("CF224D29-FEAE-45C1-9C69-D6D8D92110BC"); $job1->getId()->willReturn($job1Id); @@ -344,10 +344,10 @@ public function it_throws_exception_when_trying_to_cancel_job_but_jobs_queue_is_ public function it_throws_exception_when_trying_to_cancel_already_taken_job( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, ): void { $this->stubTwoJobs( $job1, @@ -371,10 +371,10 @@ public function it_throws_exception_when_trying_to_cancel_already_taken_job( public function it_throws_exception_when_trying_to_cancel_not_queued_job( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, JobIdInterface $notQueuedJobId ): void { $this->stubTwoJobs( @@ -400,10 +400,10 @@ public function it_throws_exception_when_trying_to_cancel_not_queued_job( public function it_cancels_job( JobIdInterface $job1Id, Job $job1, - ResourceRequirementsInterface $job1ResourceRequirements, + ResourcesInterface $job1ResourceRequirements, JobIdInterface $job2Id, Job $job2, - ResourceRequirementsInterface $job2ResourceRequirements, + ResourcesInterface $job2ResourceRequirements, JobIdInterface $notQueuedJobId ): void { $this->stubTwoJobs( diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactorySpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactorySpec.php index 4e822ea..589ba70 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactorySpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactorySpec.php @@ -10,6 +10,7 @@ use TheGame\Application\Component\Shipyard\Domain\Event\NewCannonsHaveBeenConstructedEvent; use TheGame\Application\Component\Shipyard\Domain\Event\NewShipsHaveBeenConstructedEvent; use TheGame\Application\Component\Shipyard\Domain\FinishedJobsSummaryEntryInterface; +use TheGame\Application\SharedKernel\Domain\PlanetId; final class FinishedConstructionEventFactorySpec extends ObjectBehavior { @@ -20,7 +21,8 @@ public function it_returns_finish_construction_event_for_ship_unit( $summaryEntry->getType()->willReturn('light-fighter'); $summaryEntry->getQuantity()->willReturn(50); - $this->createEvent($summaryEntry) + $planetId = new PlanetId("acc9c2b5-9f50-46db-b9ba-c30707da2b3e"); + $this->createEvent($summaryEntry, $planetId) ->shouldReturnAnInstanceOf(NewShipsHaveBeenConstructedEvent::class); } @@ -31,7 +33,8 @@ public function it_returns_finish_construction_event_for_cannon_unit( $summaryEntry->getType()->willReturn('laser'); $summaryEntry->getQuantity()->willReturn(50); - $this->createEvent($summaryEntry) + $planetId = new PlanetId("acc9c2b5-9f50-46db-b9ba-c30707da2b3e"); + $this->createEvent($summaryEntry, $planetId) ->shouldReturnAnInstanceOf(NewCannonsHaveBeenConstructedEvent::class); } diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEventSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEventSpec.php index dd776b2..4805953 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEventSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEventSpec.php @@ -10,10 +10,16 @@ final class NewCannonsHaveBeenConstructedEventSpec extends ObjectBehavior { public function let(): void { + $planetId = "d7a1a33e-2669-485e-8867-6e129761359c"; $cannonType = 'laser'; $quantity = 500; - $this->beConstructedWith($cannonType, $quantity); + $this->beConstructedWith($planetId, $cannonType, $quantity); + } + + public function it_has_planet_id(): void + { + $this->getPlanetId()->shouldReturn("d7a1a33e-2669-485e-8867-6e129761359c"); } public function it_has_cannon_type(): void diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEventSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEventSpec.php index b198e1f..d7b93c6 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEventSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEventSpec.php @@ -10,10 +10,16 @@ final class NewShipsHaveBeenConstructedEventSpec extends ObjectBehavior { public function let(): void { - $cannonType = 'light-fighter'; + $planetId = "d7a1a33e-2669-485e-8867-6e129761359c"; + $shipType = 'light-fighter'; $quantity = 500; - $this->beConstructedWith($cannonType, $quantity); + $this->beConstructedWith($planetId, $shipType, $quantity); + } + + public function it_has_planet_id(): void + { + $this->getPlanetId()->shouldReturn("d7a1a33e-2669-485e-8867-6e129761359c"); } public function it_has_ship_type(): void diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEventSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEventSpec.php index c7d4033..5a23483 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEventSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEventSpec.php @@ -10,17 +10,24 @@ final class NewUnitsHaveBeenConstructedEventSpec extends ObjectBehavior { public function let(): void { + $planetId = "d7a1a33e-2669-485e-8867-6e129761359c"; $unit = 'unknown-unit'; $constructionType = 'unknown-type'; $quantity = 500; $this->beConstructedWith( + $planetId, $unit, $constructionType, $quantity ); } + public function it_has_planet_id(): void + { + $this->getPlanetId()->shouldReturn("d7a1a33e-2669-485e-8867-6e129761359c"); + } + public function it_has_construction_unit(): void { $this->getUnit()->shouldReturn('unknown-unit'); diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/Factory/JobFactorySpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/Factory/JobFactorySpec.php index bb79e45..1510d5e 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/Factory/JobFactorySpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/Factory/JobFactorySpec.php @@ -7,7 +7,7 @@ use PhpSpec\ObjectBehavior; use TheGame\Application\Component\Shipyard\Domain\ConstructibleUnit; use TheGame\Application\Component\Shipyard\Domain\JobIdInterface; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\UuidGeneratorInterface; final class JobFactorySpec extends ObjectBehavior @@ -19,7 +19,7 @@ public function let( } public function it_creates_cannons_job( - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, UuidGeneratorInterface $uuidGenerator, JobIdInterface $jobId, ): void { @@ -50,7 +50,7 @@ public function it_creates_cannons_job( } public function it_creates_ships_job( - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, UuidGeneratorInterface $uuidGenerator, JobIdInterface $jobId, ): void { diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/CannonSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/CannonSpec.php index 6e43185..c0ec263 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/CannonSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/CannonSpec.php @@ -6,12 +6,12 @@ use PhpSpec\ObjectBehavior; use TheGame\Application\Component\Shipyard\Domain\ConstructibleUnit; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; final class CannonSpec extends ObjectBehavior { public function let( - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $type = 'laser'; $duration = 500; diff --git a/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/ShipSpec.php b/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/ShipSpec.php index 7d4ac2c..9035655 100644 --- a/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/ShipSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/Domain/ValueObject/ShipSpec.php @@ -6,12 +6,12 @@ use PhpSpec\ObjectBehavior; use TheGame\Application\Component\Shipyard\Domain\ConstructibleUnit; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; final class ShipSpec extends ObjectBehavior { public function let( - ResourceRequirementsInterface $resourceRequirements, + ResourcesInterface $resourceRequirements, ): void { $type = 'light-fighter'; $duration = 500; diff --git a/spec/TheGame/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListenerSpec.php b/spec/TheGame/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListenerSpec.php index 697394d..a48fdb7 100644 --- a/spec/TheGame/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListenerSpec.php +++ b/spec/TheGame/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListenerSpec.php @@ -37,7 +37,7 @@ public function it_upgrades_shipyard_when_shipyard_building_has_been_upgraded( $buildingId = "576FAA85-E3BD-4095-8B76-C7C1769488A5"; $upgradedLevel = 5; - $shipyardRepository->findAggregateForBuilding( + $shipyardRepository->findForBuilding( new PlanetId($planetId), new BuildingId($buildingId), )->willReturn($shipyard); @@ -89,7 +89,7 @@ public function it_throws_exception_when_shipyard_has_level_greater_than_1_but_n $buildingId = "576FAA85-E3BD-4095-8B76-C7C1769488A5"; $upgradedLevel = 5; - $shipyardRepository->findAggregateForBuilding( + $shipyardRepository->findForBuilding( new PlanetId($planetId), new BuildingId($buildingId), )->willReturn(null); diff --git a/spec/TheGame/Application/SharedKernel/Domain/GalaxyPointSpec.php b/spec/TheGame/Application/SharedKernel/Domain/GalaxyPointSpec.php new file mode 100644 index 0000000..c519680 --- /dev/null +++ b/spec/TheGame/Application/SharedKernel/Domain/GalaxyPointSpec.php @@ -0,0 +1,49 @@ +beConstructedWith(1, 2, 3); + } + + public function it_has_galaxy_number(): void + { + $this->getGalaxy()->shouldReturn(1); + } + + public function it_has_solar_system_number(): void + { + $this->getSolarSystem()->shouldReturn(2); + } + + public function it_has_planet_number(): void + { + $this->getPlanet()->shouldReturn(3); + } + + public function it_formats_coordinates_to_string(): void + { + $this->format()->shouldReturn("[1:2:3]"); + } + + public function it_returns_array_of_coordinates(): void + { + $this->toArray()->shouldReturn([ + 1, 2, 3, + ]); + } + + public function it_resolves_galaxy_point_from_string(): void + { + $resolvedPoint = GalaxyPoint::fromString("[1:2:3]"); + $this->object->shouldBeLike($resolvedPoint); + } +} diff --git a/spec/TheGame/Application/SharedKernel/Domain/ResourcesSpec.php b/spec/TheGame/Application/SharedKernel/Domain/ResourcesSpec.php new file mode 100644 index 0000000..97ec2b1 --- /dev/null +++ b/spec/TheGame/Application/SharedKernel/Domain/ResourcesSpec.php @@ -0,0 +1,102 @@ +addResource($resourceAmount); + + $this->getAmount($resourceId)->shouldReturn(500); + } + + public function it_adds_resources_but_really_append_existing_ones(): void + { + $resourceId = new ResourceId("cbd7c53e-6e9c-426e-9298-6509316cdf2f"); + $resourceAmount = new ResourceAmount($resourceId, 500); + + $this->addResource($resourceAmount); + + $nextResourceAmount = new ResourceAmount($resourceId, 750); + $this->addResource($nextResourceAmount); + + $this->getAmount($resourceId)->shouldReturn(1250); + } + + public function it_returns_zero_amount_when_doesnt_have_resource_registered(): void + { + $resourceId = new ResourceId("cbd7c53e-6e9c-426e-9298-6509316cdf2f"); + + $this->getAmount($resourceId)->shouldReturn(0); + } + + public function it_returns_scalar_array(): void + { + [$resourceAmount1, $resourceAmount2] = $this->addTwoResources(); + + $this->toScalarArray()->shouldReturn([ + $resourceAmount1->getResourceId()->getUuid() => 500, + $resourceAmount2->getResourceId()->getUuid() => 300, + ]); + } + + public function it_returns_all_resource_amounts(): void + { + [$resourceAmount1, $resourceAmount2] = $this->addTwoResources(); + + $this->getAll()->shouldReturn([ + $resourceAmount1, $resourceAmount2, + ]); + } + + public function it_returns_resources_multiplied_by_the_number(): void + { + [$resourceAmount1, $resourceAmount2] = $this->addTwoResources(); + + $multiplied = $this->multipliedBy(2); + $multiplied->getAmount($resourceAmount1->getResourceId())->shouldReturn(1000); + $multiplied->getAmount($resourceAmount2->getResourceId())->shouldReturn(600); + } + + public function it_returns_sum_of_all_resources(): void + { + [$resourceAmount1, $resourceAmount2] = $this->addTwoResources(); + + $this->sum()->shouldReturn(800); + } + + private function addTwoResources(): array + { + $resourceId1 = new ResourceId("cbd7c53e-6e9c-426e-9298-6509316cdf2f"); + $resourceAmount1 = new ResourceAmount($resourceId1, 500); + + $resourceId2 = new ResourceId("f2732560-69d9-4b9f-91ec-68d0e0462ec6"); + $resourceAmount2 = new ResourceAmount($resourceId2, 300); + + $this->addResource($resourceAmount1); + $this->addResource($resourceAmount2); + + return [$resourceAmount1, $resourceAmount2]; + } + + public function it_clears_registered_resources(): void + { + $resourceId = new ResourceId("cbd7c53e-6e9c-426e-9298-6509316cdf2f"); + $resourceAmount = new ResourceAmount($resourceId, 500); + + $this->addResource($resourceAmount); + $this->clear(); + + $this->getAmount($resourceId)->shouldReturn(0); + } +} diff --git a/src/Application/Component/Balance/Bridge/BuildingContextInterface.php b/src/Application/Component/Balance/Bridge/BuildingContextInterface.php index fe6a93b..dd0a30b 100644 --- a/src/Application/Component/Balance/Bridge/BuildingContextInterface.php +++ b/src/Application/Component/Balance/Bridge/BuildingContextInterface.php @@ -5,7 +5,7 @@ namespace TheGame\Application\Component\Balance\Bridge; use TheGame\Application\SharedKernel\Domain\BuildingType; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; interface BuildingContextInterface { @@ -17,5 +17,5 @@ public function getBuildingDuration( public function getResourceRequirements( int $level, BuildingType $buildingType - ): ResourceRequirementsInterface; + ): ResourcesInterface; } diff --git a/src/Application/Component/Balance/Bridge/FleetJourneyContextInterface.php b/src/Application/Component/Balance/Bridge/FleetJourneyContextInterface.php new file mode 100644 index 0000000..73c6909 --- /dev/null +++ b/src/Application/Component/Balance/Bridge/FleetJourneyContextInterface.php @@ -0,0 +1,28 @@ + $shipsTakingJourney */ + public function calculateFuelRequirements( + GalaxyPointInterface $from, + GalaxyPointInterface $to, + array $shipsTakingJourney, + ): ResourcesInterface; +} diff --git a/src/Application/Component/Balance/Bridge/ShipyardContextInterface.php b/src/Application/Component/Balance/Bridge/ShipyardContextInterface.php index 1507350..d501473 100644 --- a/src/Application/Component/Balance/Bridge/ShipyardContextInterface.php +++ b/src/Application/Component/Balance/Bridge/ShipyardContextInterface.php @@ -4,7 +4,7 @@ namespace TheGame\Application\Component\Balance\Bridge; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; interface ShipyardContextInterface { @@ -14,11 +14,11 @@ public function getCannonConstructionTime(string $type, int $shipyardLevel): int public function getCannonProductionLoad(string $type): int; - public function getCannonResourceRequirements(string $type): ResourceRequirementsInterface; + public function getCannonResourceRequirements(string $type): ResourcesInterface; public function getShipConstructionTime(string $type, int $shipyardLevel): int; public function getShipProductionLoad(string $type): int; - public function getShipResourceRequirements(string $type): ResourceRequirementsInterface; + public function getShipResourceRequirements(string $type): ResourcesInterface; } diff --git a/src/Application/Component/FleetJourney/Command/CancelJourneyCommand.php b/src/Application/Component/FleetJourney/Command/CancelJourneyCommand.php new file mode 100644 index 0000000..45f13ae --- /dev/null +++ b/src/Application/Component/FleetJourney/Command/CancelJourneyCommand.php @@ -0,0 +1,20 @@ +fleetId; + } +} diff --git a/src/Application/Component/FleetJourney/Command/ReturnJourneysCommand.php b/src/Application/Component/FleetJourney/Command/ReturnJourneysCommand.php new file mode 100644 index 0000000..163ac8c --- /dev/null +++ b/src/Application/Component/FleetJourney/Command/ReturnJourneysCommand.php @@ -0,0 +1,20 @@ +userId; + } +} diff --git a/src/Application/Component/FleetJourney/Command/StartJourneyCommand.php b/src/Application/Component/FleetJourney/Command/StartJourneyCommand.php new file mode 100644 index 0000000..abe38f0 --- /dev/null +++ b/src/Application/Component/FleetJourney/Command/StartJourneyCommand.php @@ -0,0 +1,62 @@ + $shipsTakingJourney + * @param array $resourcesLoad + */ + public function __construct( + private readonly string $planetId, + private readonly string $targetGalaxyPoint, + private readonly string $missionType, + private readonly array $shipsTakingJourney, + private readonly array $resourcesLoad, + ) { + foreach ($this->shipsTakingJourney as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid ships taking journey key or value'); + } + } + + foreach ($this->resourcesLoad as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid resources load key or value'); + } + } + } + + public function getPlanetId(): string + { + return $this->planetId; + } + + public function getTargetGalaxyPoint(): string + { + return $this->targetGalaxyPoint; + } + + public function getMissionType(): string + { + return $this->missionType; + } + + /** @return array */ + public function getShipsTakingJourney(): array + { + return $this->shipsTakingJourney; + } + + /** @return array */ + public function getResourcesLoad(): array + { + return $this->resourcesLoad; + } +} diff --git a/src/Application/Component/FleetJourney/Command/TargetJourneysCommand.php b/src/Application/Component/FleetJourney/Command/TargetJourneysCommand.php new file mode 100644 index 0000000..2c4b78a --- /dev/null +++ b/src/Application/Component/FleetJourney/Command/TargetJourneysCommand.php @@ -0,0 +1,20 @@ +userId; + } +} diff --git a/src/Application/Component/FleetJourney/CommandHandler/CancelJourneyCommandHandler.php b/src/Application/Component/FleetJourney/CommandHandler/CancelJourneyCommandHandler.php new file mode 100644 index 0000000..4ff0487 --- /dev/null +++ b/src/Application/Component/FleetJourney/CommandHandler/CancelJourneyCommandHandler.php @@ -0,0 +1,41 @@ +getFleetId()); + $fleet = $this->fleetRepository->find($fleetId); + + if ($fleet === null) { + throw new InconsistentModelException(sprintf("Fleet %d doesn't exist", $command->getFleetId())); + } + + $fleet->cancelJourney(); + + $this->eventBus->dispatch( + new FleetHasCancelledJourneyEvent( + $command->getFleetId(), + $fleet->getJourneyTargetPoint()->format(), + $fleet->getResourcesLoad(), + ), + ); + } +} diff --git a/src/Application/Component/FleetJourney/CommandHandler/ReturnJourneysCommandHandler.php b/src/Application/Component/FleetJourney/CommandHandler/ReturnJourneysCommandHandler.php new file mode 100644 index 0000000..021b432 --- /dev/null +++ b/src/Application/Component/FleetJourney/CommandHandler/ReturnJourneysCommandHandler.php @@ -0,0 +1,42 @@ +getUserId()); + $fleets = $this->fleetRepository->findFlyingBackFromJourneyForUser($userId); + foreach ($fleets as $fleet) { + $fleet->tryToReachJourneyReturnPoint(); + if ($fleet->didReturnFromJourney() === false) { + continue; + } + + $this->eventBus->dispatch( + new FleetHasReachedJourneyReturnPointEvent( + $fleet->getId()->getUuid(), + $fleet->getJourneyStartPoint()->format(), + $fleet->getJourneyTargetPoint()->format(), + $fleet->getJourneyReturnPoint()->format(), + $fleet->getResourcesLoad(), + ), + ); + } + } +} diff --git a/src/Application/Component/FleetJourney/CommandHandler/StartJourneyCommandHandler.php b/src/Application/Component/FleetJourney/CommandHandler/StartJourneyCommandHandler.php new file mode 100644 index 0000000..2938cd4 --- /dev/null +++ b/src/Application/Component/FleetJourney/CommandHandler/StartJourneyCommandHandler.php @@ -0,0 +1,87 @@ +getTargetGalaxyPoint()); + if ($this->galaxyNavigator->isWithinBoundaries($targetGalaxyPoint) === false) { + throw new CannotTakeJourneyToOutOfBoundGalaxyPointException($targetGalaxyPoint); + } + + $fleetTakingJourney = $this->fleetResolver->resolveFromPlanet( + new PlanetId($command->getPlanetId()), + $command->getShipsTakingJourney(), + Resources::fromScalarArray($command->getResourcesLoad()), + $targetGalaxyPoint + ); + + $startGalaxyPoint = $fleetTakingJourney->getStationingGalaxyPoint(); + $missionType = MissionType::from($command->getMissionType()); + $isMissionEligible = $this->galaxyNavigator->isMissionEligible( + $missionType->value, + $startGalaxyPoint, + $targetGalaxyPoint, + ); + if ($isMissionEligible === false) { + throw new JourneyMissionIsNotEligibleException($missionType, $targetGalaxyPoint); + } + + $journey = $this->journeyFactory->createJourney( + $fleetTakingJourney->getId(), + $missionType, + $startGalaxyPoint, + $targetGalaxyPoint, + $this->journeyContext->calculateJourneyDuration( + $fleetTakingJourney->getSpeed(), + $startGalaxyPoint, + $targetGalaxyPoint, + ), + ); + $fleetTakingJourney->startJourney($journey); + + $fuelRequirements = $this->journeyContext->calculateFuelRequirements( + $startGalaxyPoint, + $targetGalaxyPoint, + $command->getShipsTakingJourney(), + ); + + $this->eventBus->dispatch( + new FleetHasStartedJourneyEvent( + $command->getPlanetId(), + $fleetTakingJourney->getId()->getUuid(), + $startGalaxyPoint->format(), + $command->getTargetGalaxyPoint(), + $fuelRequirements->toScalarArray(), + $command->getResourcesLoad(), + ) + ); + } +} diff --git a/src/Application/Component/FleetJourney/CommandHandler/TargetJourneysCommandHandler.php b/src/Application/Component/FleetJourney/CommandHandler/TargetJourneysCommandHandler.php new file mode 100644 index 0000000..4fe118e --- /dev/null +++ b/src/Application/Component/FleetJourney/CommandHandler/TargetJourneysCommandHandler.php @@ -0,0 +1,42 @@ +getUserId()); + $fleets = $this->fleetRepository->findInJourneyForUser($userId); + foreach ($fleets as $fleet) { + $fleet->tryToReachJourneyTargetPoint(); + if ($fleet->didReachJourneyTargetPoint() === false) { + continue; + } + + $mission = $fleet->getJourneyMissionType(); + $this->eventBus->dispatch( + new FleetHasReachedJourneyTargetPointEvent( + $mission->value, + $fleet->getId()->getUuid(), + $fleet->getJourneyTargetPoint()->format(), + $fleet->getResourcesLoad(), + ), + ); + } + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Entity/Fleet.php b/src/Application/Component/FleetJourney/Domain/Entity/Fleet.php new file mode 100644 index 0000000..9626315 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Entity/Fleet.php @@ -0,0 +1,359 @@ + $ships */ + public function __construct( + private readonly FleetIdInterface $fleetId, + private GalaxyPointInterface $stationingPoint, + private ResourcesInterface $resourcesLoad, + private array $ships = [], + ) { + } + + public function getId(): FleetIdInterface + { + return $this->fleetId; + } + + public function getStationingGalaxyPoint(): GalaxyPointInterface + { + return $this->stationingPoint; + } + + public function landOnPlanet(GalaxyPointInterface $newStationingPoint): void + { + $this->stationingPoint = $newStationingPoint; + } + + public function merge(Fleet $fleet): void + { + $this->addShips($fleet->ships); + } + + /** @param array $ships */ + public function addShips(array $ships): void + { + if ($this->currentJourney !== null) { + throw new FleetAlreadyInJourneyException($this->fleetId); + } + + foreach ($ships as $shipsToAdd) { + foreach ($this->ships as $currentGroup) { + if ($currentGroup->hasType($shipsToAdd->getType())) { + $currentGroup->merge($shipsToAdd); + + continue 2; + } + } + + $this->ships[] = $shipsToAdd; + } + } + + public function getSpeed(): int + { + if (count($this->ships) === 0) { + return 0; + } + + $lowestSpeed = $this->ships[0]->getSpeed(); + foreach ($this->ships as $shipGroup) { + if ($lowestSpeed > $shipGroup->getSpeed()) { + $lowestSpeed = $shipGroup->getSpeed(); + } + } + + return $lowestSpeed; + } + + /** @param array $shipsToCompare */ + public function hasEnoughShips( + array $shipsToCompare, + ): bool { + if (count($shipsToCompare) === 0) { + return false; + } + + foreach ($shipsToCompare as $shipType => $quantity) { + $shipTypeFound = false; + foreach ($this->ships as $shipGroup) { + if ($shipGroup->hasType($shipType) === false) { + continue; + } + + $shipTypeFound = true; + if ($shipGroup->hasEnoughShips($quantity) === false) { + return false; + } + } + + if ($shipTypeFound === false) { + return false; + } + } + + return true; + } + + /** @param array $shipsToCompare */ + public function hasMoreShipsThan( + array $shipsToCompare, + ): bool { + if (count($shipsToCompare) === 0 && count($this->ships) > 0) { + return true; + } + + if ($this->hasEnoughShips($shipsToCompare) === false) { + return false; + } + + foreach ($shipsToCompare as $shipType => $quantity) { + foreach ($this->ships as $shipGroup) { + if ($shipGroup->hasType($shipType) === false) { + continue; + } + + if ($shipGroup->hasMoreShipsThan($quantity) === true) { + return true; + } + } + } + + return false; + } + + /** + * @param array $shipsToSplit + * @return array + */ + public function split( + array $shipsToSplit, + ): array { + if ($this->hasEnoughShips($shipsToSplit) === false) { + throw new NotEnoughShipsException($this->fleetId); + } + + $splitShips = []; + foreach ($shipsToSplit as $shipType => $quantity) { + if ($quantity <= 0) { + continue; + } + + foreach ($this->ships as $shipGroup) { + if ($shipGroup->hasType($shipType) === false) { + continue; + } + + $splitShips[] = $shipGroup->split($quantity); + } + } + + return $splitShips; + } + + private function getCurrentJourney(): Journey + { + if ($this->currentJourney === null) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + return $this->currentJourney; + } + + public function isDuringJourney(): bool + { + if ($this->currentJourney === null) { + return false; + } + + if ($this->getCurrentJourney()->didReachTargetPoint() === false && $this->getCurrentJourney()->didReachReturnPoint() === false) { + return true; + } + + return false; + } + + public function startJourney(Journey $journey): void + { + if ($this->isDuringJourney()) { + throw new FleetAlreadyInJourneyException($this->fleetId); + } + + $this->currentJourney = $journey; + } + + public function getJourneyMissionType(): MissionType + { + if ($this->isDuringJourney() === false) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + return $this->getCurrentJourney()->getMissionType(); + } + + public function getJourneyStartPoint(): GalaxyPointInterface + { + if ($this->isDuringJourney() === false) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + return $this->getCurrentJourney()->getStartPoint(); + } + + public function getJourneyTargetPoint(): GalaxyPointInterface + { + if ($this->isDuringJourney() === false) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + return $this->getCurrentJourney()->getTargetPoint(); + } + + public function getJourneyReturnPoint(): GalaxyPointInterface + { + if ($this->isDuringJourney() === false) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + return $this->getCurrentJourney()->getReturnPoint(); + } + + public function didReachJourneyTargetPoint(): bool + { + return $this->currentJourney !== null + && $this->getCurrentJourney()->didReachTargetPoint(); + } + + public function tryToReachJourneyTargetPoint(): void + { + $hasStartedJourney = $this->currentJourney !== null; + if (! $hasStartedJourney) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + $hasFinishedJourney = $this->getCurrentJourney()->didReachReturnPoint() === true; + if ($hasFinishedJourney) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + if ($this->getCurrentJourney()->didReachTargetPoint() === false) { + return; + } + + if ($this->getCurrentJourney()->doesPlanToStationOnTarget()) { + $this->stationingPoint = $this->getCurrentJourney()->getTargetPoint(); + $this->getCurrentJourney()->reachTargetPoint(); + + return; + } elseif ($this->getCurrentJourney()->doesFlyBack()) { + return; + } + + $this->getCurrentJourney()->reachTargetPoint(); + } + + public function tryToReachJourneyReturnPoint(): void + { + $hasStartedJourney = $this->currentJourney !== null; + + if (! $hasStartedJourney) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + if ($this->getCurrentJourney()->didReachTargetPoint() === false) { + throw new FleetHasNotYetReachedTheTargetPointException($this->fleetId); + } + + if ($this->getCurrentJourney()->didReachReturnPoint() === false) { + return; + } + + $this->getCurrentJourney()->reachReturnPoint(); + } + + public function didReturnFromJourney(): bool + { + return $this->currentJourney !== null + && $this->getCurrentJourney()->didReachReturnPoint(); + } + + public function doesFlyBack(): bool + { + if ($this->currentJourney === null) { + return false; + } + + return $this->getCurrentJourney()->doesFlyBack(); + } + + public function cancelJourney(): void + { + if ($this->currentJourney !== null && $this->getCurrentJourney()->didReachTargetPoint()) { + throw new FleetNotInJourneyYetException($this->fleetId); + } + + $this->getCurrentJourney()->cancel(); + } + + public function getLoadCapacity(): int + { + $capacity = 0; + foreach ($this->ships as $shipGroup) { + $capacity += $shipGroup->getLoadCapacity(); + } + + return $capacity; + } + + /** @return array */ + public function getResourcesLoad(): array + { + return $this->resourcesLoad->toScalarArray(); + } + + public function load( + ResourcesInterface $resourcesLoad, + ): void { + $loadTotal = $resourcesLoad->sum(); + if ($loadTotal > $this->getLoadCapacity()) { + throw new NotEnoughFleetLoadCapacityException( + $this->fleetId, + $this->getLoadCapacity(), + $loadTotal, + ); + } + + if ($this->resourcesLoad->sum() > 0) { + throw new FleetAlreadyLoadedException($this->fleetId); + } + + $this->resourcesLoad = $resourcesLoad; + } + + public function unload(): ResourcesInterface + { + $load = clone $this->resourcesLoad; + $this->resourcesLoad->clear(); + + return $load; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Entity/Journey.php b/src/Application/Component/FleetJourney/Domain/Entity/Journey.php new file mode 100644 index 0000000..a2bbacb --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Entity/Journey.php @@ -0,0 +1,217 @@ +startedAt = new DateTimeImmutable(); + $this->plannedReachTargetAt = new DateTimeImmutable( + sprintf('+ %d seconds', $this->duration), + ); + $this->plannedReturnAt = new DateTimeImmutable( + sprintf('+ %d seconds', $this->duration * 2), + ); + $this->reachesTargetAt = $this->plannedReachTargetAt; + $this->returnsAt = $this->plannedReturnAt; + + $this->returnPoint = $this->startPoint; + } + + public function getId(): JourneyIdInterface + { + return $this->journeyId; + } + + public function getMissionType(): MissionType + { + return $this->missionType; + } + + public function getStartPoint(): GalaxyPointInterface + { + return $this->startPoint; + } + + public function getTargetPoint(): GalaxyPointInterface + { + return $this->targetPoint; + } + + public function getReturnPoint(): GalaxyPointInterface + { + return $this->returnPoint; + } + + public function getStartedAt(): DateTimeInterface + { + return $this->startedAt; + } + + public function getPlannedReachTargetAt(): DateTimeInterface + { + return $this->plannedReachTargetAt; + } + + public function getReachesTargetAt(): DateTimeInterface + { + return $this->reachesTargetAt; + } + + public function getPlannedReturnAt(): DateTimeInterface + { + return $this->plannedReturnAt; + } + + public function getReturnsAt(): DateTimeInterface + { + return $this->returnsAt; + } + + public function doesPlanToStationOnTarget(): bool + { + return $this->missionType === MissionType::Stationing; + } + + public function doesAttack(): bool + { + return $this->missionType === MissionType::Attack; + } + + public function doesTransportResources(): bool + { + return $this->missionType === MissionType::Transport; + } + + public function doesFlyBack(): bool + { + return $this->doesFlyBack; + } + + public function didReachTargetPoint(): bool + { + $now = new DateTimeImmutable(); + + return $now->getTimestamp() >= $this->reachesTargetAt->getTimestamp(); + } + + public function reachTargetPoint(): void + { + if ($this->doesFlyBack()) { + throw new FleetOnFlyBackException($this->fleetId); + } + + $now = new DateTimeImmutable(); + if ($this->didReachTargetPoint() === false) { + $timeLeft = $this->reachesTargetAt->getTimestamp() - $now->getTimestamp(); + + throw new FleetHasNotYetReachedTheTargetPointException($this->fleetId, $timeLeft); + } + + if ($this->doesPlanToStationOnTarget()) { + $this->returnPoint = $this->targetPoint; + $this->returnsAt = $now; + + return; + } + + $this->turnAround(); + } + + public function didReachReturnPoint(): bool + { + $now = new DateTimeImmutable(); + + return $now->getTimestamp() >= $this->returnsAt->getTimestamp(); + } + + public function reachReturnPoint(): void + { + if ($this->doesFlyBack() === false) { + throw new FleetNotOnFlyBackException($this->fleetId); + } + + $now = new DateTimeImmutable(); + if ($this->didReachReturnPoint() === false) { + $timeLeft = $this->returnsAt->getTimestamp() - $now->getTimestamp(); + + throw new FleetHasNotYetReachedTheReturnPointException($this->fleetId, $timeLeft); + } + + $this->returnsAt = $now; + $this->doesFlyBack = false; + } + + public function isCancelled(): bool + { + return $this->cancelled; + } + + public function cancel(): void + { + if ($this->doesFlyBack === true) { + throw new CannotCancelFleetJourneyOnFlyBackException($this->fleetId); + } + + if ($this->didReachTargetPoint() === true) { + throw new CannotCancelFleetJourneyOnReachingTargetPointException($this->fleetId); + } + + if ($this->didReachReturnPoint() === true) { + throw new CannotCancelFleetJourneyOnReachingReturnPointException($this->fleetId); + } + + $this->cancelled = true; + $this->turnAround(); + } + + private function turnAround(): void + { + $this->doesFlyBack = true; + + $now = new DateTimeImmutable(); + $timeFromStart = $now->getTimestamp() - $this->startedAt->getTimestamp(); + + $this->reachesTargetAt = $now; + $this->returnsAt = new DateTimeImmutable(sprintf('+ %d seconds', $timeFromStart)); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Event/FleetHasCancelledJourneyEvent.php b/src/Application/Component/FleetJourney/Domain/Event/FleetHasCancelledJourneyEvent.php new file mode 100644 index 0000000..71632f2 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Event/FleetHasCancelledJourneyEvent.php @@ -0,0 +1,40 @@ + $resourcesLoad */ + public function __construct( + private readonly string $fleetId, + private readonly string $targetGalaxyPoint, + private readonly array $resourcesLoad, + ) { + foreach ($this->resourcesLoad as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid resources load key or value'); + } + } + } + + public function getFleetId(): string + { + return $this->fleetId; + } + + public function getTargetGalaxyPoint(): string + { + return $this->targetGalaxyPoint; + } + + /** @return array */ + public function getResourcesLoad(): array + { + return $this->resourcesLoad; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyReturnPointEvent.php b/src/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyReturnPointEvent.php new file mode 100644 index 0000000..96a31e8 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyReturnPointEvent.php @@ -0,0 +1,52 @@ + $resourcesLoad */ + public function __construct( + private readonly string $fleetId, + private readonly string $startGalaxyPoint, + private readonly string $targetGalaxyPoint, + private readonly string $returnGalaxyPoint, + private readonly array $resourcesLoad, + ) { + foreach ($this->resourcesLoad as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid resources load key or value'); + } + } + } + + public function getFleetId(): string + { + return $this->fleetId; + } + + public function getStartGalaxyPoint(): string + { + return $this->startGalaxyPoint; + } + + public function getTargetGalaxyPoint(): string + { + return $this->targetGalaxyPoint; + } + + public function getReturnGalaxyPoint(): string + { + return $this->returnGalaxyPoint; + } + + /** @return array */ + public function getResourcesLoad(): array + { + return $this->resourcesLoad; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyTargetPointEvent.php b/src/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyTargetPointEvent.php new file mode 100644 index 0000000..26878e1 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Event/FleetHasReachedJourneyTargetPointEvent.php @@ -0,0 +1,46 @@ + $resourcesLoad */ + public function __construct( + private readonly string $mission, + private readonly string $fleetId, + private readonly string $targetGalaxyPoint, + private readonly array $resourcesLoad, + ) { + foreach ($this->resourcesLoad as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid resources load key or value'); + } + } + } + + public function getMission(): string + { + return $this->mission; + } + + public function getFleetId(): string + { + return $this->fleetId; + } + + public function getTargetGalaxyPoint(): string + { + return $this->targetGalaxyPoint; + } + + /** @return array */ + public function getResourcesLoad(): array + { + return $this->resourcesLoad; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Event/FleetHasStartedJourneyEvent.php b/src/Application/Component/FleetJourney/Domain/Event/FleetHasStartedJourneyEvent.php new file mode 100644 index 0000000..36578e2 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Event/FleetHasStartedJourneyEvent.php @@ -0,0 +1,68 @@ + $fuelRequirements + * @param array $resourcesLoad + */ + public function __construct( + private readonly string $planetId, + private readonly string $fleetId, + private readonly string $fromGalaxyPoint, + private readonly string $targetGalaxyPoint, + private readonly array $fuelRequirements, + private readonly array $resourcesLoad, + ) { + foreach ($this->fuelRequirements as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid fuel requirements key or value'); + } + } + + foreach ($this->resourcesLoad as $key => $value) { + if (is_string($key) === false || is_int($value) === false) { + throw new InvalidArgumentException('Invalid resources load key or value'); + } + } + } + + public function getPlanetId(): string + { + return $this->planetId; + } + + public function getFleetId(): string + { + return $this->fleetId; + } + + public function getFromGalaxyPoint(): string + { + return $this->fromGalaxyPoint; + } + + public function getTargetGalaxyPoint(): string + { + return $this->targetGalaxyPoint; + } + + /** @return array */ + public function getFuelRequirements(): array + { + return $this->fuelRequirements; + } + + /** @return array */ + public function getResourcesLoad(): array + { + return $this->resourcesLoad; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnFlyBackException.php b/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnFlyBackException.php new file mode 100644 index 0000000..73c7e52 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnFlyBackException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnReachingReturnPointException.php b/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnReachingReturnPointException.php new file mode 100644 index 0000000..7c054c3 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnReachingReturnPointException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnReachingTargetPointException.php b/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnReachingTargetPointException.php new file mode 100644 index 0000000..7370534 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/CannotCancelFleetJourneyOnReachingTargetPointException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/CannotMergeShipGroupsOfDifferentTypeException.php b/src/Application/Component/FleetJourney/Domain/Exception/CannotMergeShipGroupsOfDifferentTypeException.php new file mode 100644 index 0000000..64029d5 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/CannotMergeShipGroupsOfDifferentTypeException.php @@ -0,0 +1,21 @@ +format(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetAlreadyInJourneyException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetAlreadyInJourneyException.php new file mode 100644 index 0000000..73697bb --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetAlreadyInJourneyException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetAlreadyLoadedException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetAlreadyLoadedException.php new file mode 100644 index 0000000..6e97f19 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetAlreadyLoadedException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNoLoadException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNoLoadException.php new file mode 100644 index 0000000..bf1897f --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNoLoadException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNotYetReachedTheReturnPointException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNotYetReachedTheReturnPointException.php new file mode 100644 index 0000000..2472e28 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNotYetReachedTheReturnPointException.php @@ -0,0 +1,22 @@ +getUuid(), + $timeLeft, + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNotYetReachedTheTargetPointException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNotYetReachedTheTargetPointException.php new file mode 100644 index 0000000..81d0c4a --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetHasNotYetReachedTheTargetPointException.php @@ -0,0 +1,25 @@ +getUuid(), + ); + + if ($timeLeft !== null) { + $message .= sprintf(' (%d seconds left)', $timeLeft); + } + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetNotInJourneyYetException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetNotInJourneyYetException.php new file mode 100644 index 0000000..1e7be66 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetNotInJourneyYetException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetNotOnFlyBackException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetNotOnFlyBackException.php new file mode 100644 index 0000000..3d5177e --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetNotOnFlyBackException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/FleetOnFlyBackException.php b/src/Application/Component/FleetJourney/Domain/Exception/FleetOnFlyBackException.php new file mode 100644 index 0000000..fe6cfb3 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/FleetOnFlyBackException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/JourneyMissionIsNotEligibleException.php b/src/Application/Component/FleetJourney/Domain/Exception/JourneyMissionIsNotEligibleException.php new file mode 100644 index 0000000..4e32c16 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/JourneyMissionIsNotEligibleException.php @@ -0,0 +1,23 @@ +value, + $targetPoint->format(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/NoFleetStationingOnPlanetException.php b/src/Application/Component/FleetJourney/Domain/Exception/NoFleetStationingOnPlanetException.php new file mode 100644 index 0000000..203820d --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/NoFleetStationingOnPlanetException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughFleetLoadCapacityException.php b/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughFleetLoadCapacityException.php new file mode 100644 index 0000000..5406218 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughFleetLoadCapacityException.php @@ -0,0 +1,23 @@ +getUuid(), + $capacityNeeded, + $capacityAvailable + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughResourcesOnPlanetForFleetLoadException.php b/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughResourcesOnPlanetForFleetLoadException.php new file mode 100644 index 0000000..20b7c9a --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughResourcesOnPlanetForFleetLoadException.php @@ -0,0 +1,21 @@ +getUuid(), + ); + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughShipsException.php b/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughShipsException.php new file mode 100644 index 0000000..4344db4 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Exception/NotEnoughShipsException.php @@ -0,0 +1,35 @@ +getUuid(), + ); + } elseif ($argument instanceof FleetIdInterface) { + $message = sprintf( + 'Not enough ships on fleet %s', + $argument->getUuid(), + ); + } else { + $message = sprintf( + 'Not enough ships of type %s in fleet', + $argument, + ); + } + + parent::__construct($message); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Factory/FleetFactory.php b/src/Application/Component/FleetJourney/Domain/Factory/FleetFactory.php new file mode 100644 index 0000000..9cffe69 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Factory/FleetFactory.php @@ -0,0 +1,37 @@ + $shipsTakingJourney */ + public function create( + array $shipsTakingJourney, + GalaxyPointInterface $stationingPoint, + ResourcesInterface $resourcesLoad, + ): Fleet { + $id = $this->uuidGenerator->generateNewFleetId(); + + $fleet = new Fleet( + $id, + $stationingPoint, + $resourcesLoad + ); + $fleet->addShips($shipsTakingJourney); + + return $fleet; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Factory/FleetFactoryInterface.php b/src/Application/Component/FleetJourney/Domain/Factory/FleetFactoryInterface.php new file mode 100644 index 0000000..e455a62 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Factory/FleetFactoryInterface.php @@ -0,0 +1,20 @@ + $shipsTakingJourney */ + public function create( + array $shipsTakingJourney, + GalaxyPointInterface $stationingPoint, + ResourcesInterface $resourcesLoad, + ): Fleet; +} diff --git a/src/Application/Component/FleetJourney/Domain/Factory/JourneyFactory.php b/src/Application/Component/FleetJourney/Domain/Factory/JourneyFactory.php new file mode 100644 index 0000000..fbce211 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Factory/JourneyFactory.php @@ -0,0 +1,38 @@ +uuidGenerator->generateNewJourneyId(); + + return new Journey( + $id, + $fleetId, + $missionType, + $startGalaxyPoint, + $targetGalaxyPoint, + $journeyDuration, + ); + } +} diff --git a/src/Application/Component/FleetJourney/Domain/Factory/JourneyFactoryInterface.php b/src/Application/Component/FleetJourney/Domain/Factory/JourneyFactoryInterface.php new file mode 100644 index 0000000..21ba802 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/Factory/JourneyFactoryInterface.php @@ -0,0 +1,21 @@ +id; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/FleetIdInterface.php b/src/Application/Component/FleetJourney/Domain/FleetIdInterface.php new file mode 100644 index 0000000..ad1fef5 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/FleetIdInterface.php @@ -0,0 +1,11 @@ +id; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/JourneyIdInterface.php b/src/Application/Component/FleetJourney/Domain/JourneyIdInterface.php new file mode 100644 index 0000000..865259a --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/JourneyIdInterface.php @@ -0,0 +1,11 @@ +type; + } + + public function getQuantity(): int + { + return $this->quantity; + } + + public function hasType(string $type): bool + { + return $this->type === $type; + } + + public function hasMoreShipsThan(int $quantity): bool + { + return $this->quantity > $quantity; + } + + public function hasEnoughShips(int $quantity): bool + { + return $this->quantity >= $quantity; + } + + public function merge(ShipsGroupInterface $shipGroup): void + { + if ($this->type !== $shipGroup->getType()) { + throw new CannotMergeShipGroupsOfDifferentTypeException($this->type, $shipGroup->getType()); + } + + $this->quantity += $shipGroup->getQuantity(); + $shipGroup->setEmpty(); + } + + public function split(int $quantity): ShipsGroupInterface + { + if ($this->hasEnoughShips($quantity) === false) { + throw new NotEnoughShipsException($this->type); + } + + $this->quantity -= $quantity; + + return new ShipsGroup( + $this->type, + $quantity, + $this->speed, + $this->unitLoadCapacity, + ); + } + + public function getSpeed(): int + { + return $this->speed; + } + + public function getLoadCapacity(): int + { + return $this->getUnitLoadCapacity() * $this->getQuantity(); + } + + public function getUnitLoadCapacity(): int + { + return $this->unitLoadCapacity; + } + + public function setEmpty(): void + { + $this->quantity = 0; + } + + public function isEmpty(): bool + { + return $this->quantity === 0; + } +} diff --git a/src/Application/Component/FleetJourney/Domain/ShipsGroupInterface.php b/src/Application/Component/FleetJourney/Domain/ShipsGroupInterface.php new file mode 100644 index 0000000..1538758 --- /dev/null +++ b/src/Application/Component/FleetJourney/Domain/ShipsGroupInterface.php @@ -0,0 +1,32 @@ +getPlanetId()); + $fleetCurrentlyStationingOnPlanet = $this->fleetRepository->findStationingOnPlanet($planetId); + if ($fleetCurrentlyStationingOnPlanet === null) { + $fleetCurrentlyStationingOnPlanet = $this->fleetFactory->create( + [], + $this->navigator->getPlanetPoint($planetId), + new Resources(), + ); + } + + $shipType = $event->getType(); + $fleetCurrentlyStationingOnPlanet->addShips([ + new ShipsGroup( + $shipType, + $event->getQuantity(), + $this->fleetJourneyContext->getShipBaseSpeed($shipType), + $this->fleetJourneyContext->getShipLoadCapacity($shipType), + ), + ]); + } +} diff --git a/src/Application/Component/FleetJourney/EventListener/StationFleetOnReachingTargetPointEventListener.php b/src/Application/Component/FleetJourney/EventListener/StationFleetOnReachingTargetPointEventListener.php new file mode 100644 index 0000000..7edf564 --- /dev/null +++ b/src/Application/Component/FleetJourney/EventListener/StationFleetOnReachingTargetPointEventListener.php @@ -0,0 +1,51 @@ +getMission()); + if ($mission !== MissionType::Stationing) { + return; + } + + $joiningFleetId = new FleetId($event->getFleetId()); + $fleetJoiningPlanet = $this->fleetRepository->find($joiningFleetId); + if ($fleetJoiningPlanet === null) { + throw new InconsistentModelException(sprintf('Fleet %s doesn\'t exist', $event->getFleetId())); + } + + $stationingPoint = GalaxyPoint::fromString($event->getTargetGalaxyPoint()); + $planetId = $this->navigator->getPlanetId($stationingPoint); + if ($planetId === null) { + throw new InconsistentModelException(sprintf('Planet %s doesn\'t exist', $event->getTargetGalaxyPoint())); + } + + $fleetCurrentlyStationingOnPlanet = $this->fleetRepository->findStationingOnPlanet($planetId); + if ($fleetCurrentlyStationingOnPlanet === null) { + $fleetJoiningPlanet->landOnPlanet($stationingPoint); + + return; + } + + $fleetCurrentlyStationingOnPlanet->merge($fleetJoiningPlanet); + } +} diff --git a/src/Application/Component/FleetJourney/FleetRepositoryInterface.php b/src/Application/Component/FleetJourney/FleetRepositoryInterface.php new file mode 100644 index 0000000..9ea4fed --- /dev/null +++ b/src/Application/Component/FleetJourney/FleetRepositoryInterface.php @@ -0,0 +1,23 @@ + */ + public function findFlyingBackFromJourneyForUser(UserIdInterface $userId): array; + + /** @return array */ + public function findInJourneyForUser(UserIdInterface $userId): array; +} diff --git a/src/Application/Component/FleetJourney/FleetResolver.php b/src/Application/Component/FleetJourney/FleetResolver.php new file mode 100644 index 0000000..ffa8a68 --- /dev/null +++ b/src/Application/Component/FleetJourney/FleetResolver.php @@ -0,0 +1,90 @@ + $shipsTakingJourney + */ + public function resolveFromPlanet( + PlanetIdInterface $planetId, + array $shipsTakingJourney, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + ): Fleet { + $stationingFleet = $this->fleetRepository->findStationingOnPlanet($planetId); + if ($stationingFleet === null) { + throw new NoFleetStationingOnPlanetException($planetId); + } + + if ($stationingFleet->hasEnoughShips($shipsTakingJourney) === false) { + throw new NotEnoughShipsException($planetId); + } + + $resolvedFleet = $stationingFleet; + if ($stationingFleet->hasMoreShipsThan($shipsTakingJourney)) { + $shipGroupsTakingJourney = $stationingFleet->split($shipsTakingJourney); + $resolvedFleet = $this->fleetFactory->create( + $shipGroupsTakingJourney, + $stationingFleet->getStationingGalaxyPoint(), + $resourcesLoad, + ); + } + + $startGalaxyPoint = $stationingFleet->getStationingGalaxyPoint(); + $fuelRequirements = $this->journeyContext->calculateFuelRequirements( + $startGalaxyPoint, + $targetGalaxyPoint, + $shipsTakingJourney, + ); + + $resourcesLoad->add($fuelRequirements); + $hasEnoughResources = $this->resourceAvailabilityChecker->check($planetId, $resourcesLoad); + if ($hasEnoughResources === false) { + throw new NotEnoughResourcesOnPlanetForFleetLoadException($planetId); + } + $this->loadResources($resolvedFleet, $resourcesLoad); + + return $resolvedFleet; + } + + private function loadResources( + Fleet $resolvedFleet, + ResourcesInterface $resourcesLoad, + ): void { + $capacityNeeded = $resourcesLoad->sum(); + $currentCapacity = $resolvedFleet->getLoadCapacity(); + if ($capacityNeeded > $currentCapacity) { + throw new NotEnoughFleetLoadCapacityException( + $resolvedFleet->getId(), + $currentCapacity, + $capacityNeeded + ); + } + + $resolvedFleet->load($resourcesLoad); + } +} diff --git a/src/Application/Component/FleetJourney/FleetResolverInterface.php b/src/Application/Component/FleetJourney/FleetResolverInterface.php new file mode 100644 index 0000000..77989b7 --- /dev/null +++ b/src/Application/Component/FleetJourney/FleetResolverInterface.php @@ -0,0 +1,23 @@ + $shipsTakingJourney + */ + public function resolveFromPlanet( + PlanetIdInterface $planetId, + array $shipsTakingJourney, + ResourcesInterface $resourcesLoad, + GalaxyPointInterface $targetGalaxyPoint, + ): Fleet; +} diff --git a/src/Application/Component/Galaxy/Bridge/NavigatorInterface.php b/src/Application/Component/Galaxy/Bridge/NavigatorInterface.php new file mode 100644 index 0000000..2ca36ae --- /dev/null +++ b/src/Application/Component/Galaxy/Bridge/NavigatorInterface.php @@ -0,0 +1,25 @@ +storagesRepository->findForPlanet($planetId); if ($aggregate === null) { diff --git a/src/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerInterface.php b/src/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerInterface.php index 0fabe38..c614832 100644 --- a/src/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerInterface.php +++ b/src/Application/Component/ResourceStorage/Bridge/ResourceAvailabilityCheckerInterface.php @@ -5,12 +5,12 @@ namespace TheGame\Application\Component\ResourceStorage\Bridge; use TheGame\Application\SharedKernel\Domain\PlanetIdInterface; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; interface ResourceAvailabilityCheckerInterface { public function check( PlanetIdInterface $planetId, - ResourceRequirementsInterface $requirements, + ResourcesInterface $requirements, ): bool; } diff --git a/src/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandler.php b/src/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandler.php index a7d03d8..852462f 100644 --- a/src/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandler.php +++ b/src/Application/Component/ResourceStorage/CommandHandler/UseResourceCommandHandler.php @@ -11,7 +11,7 @@ use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\Domain\ResourceAmount; use TheGame\Application\SharedKernel\Domain\ResourceId; -use TheGame\Application\SharedKernel\Domain\ResourceRequirements; +use TheGame\Application\SharedKernel\Domain\Resources; use TheGame\Application\SharedKernel\EventBusInterface; use TheGame\Application\SharedKernel\Exception\InconsistentModelException; @@ -34,8 +34,8 @@ public function __invoke(UseResourceCommand $command): void $resourceId = new ResourceId($command->getResourceId()); $resourceAmount = new ResourceAmount($resourceId, $command->getAmount()); - $requirements = new ResourceRequirements(); - $requirements->add($resourceAmount); + $requirements = new Resources(); + $requirements->addResource($resourceAmount); if ($storages->hasEnough($requirements) === false) { throw new InsufficientResourcesException( diff --git a/src/Application/Component/ResourceStorage/Domain/Entity/StoragesCollection.php b/src/Application/Component/ResourceStorage/Domain/Entity/StoragesCollection.php index b8ccf52..c062c7e 100644 --- a/src/Application/Component/ResourceStorage/Domain/Entity/StoragesCollection.php +++ b/src/Application/Component/ResourceStorage/Domain/Entity/StoragesCollection.php @@ -14,7 +14,7 @@ use TheGame\Application\SharedKernel\Domain\PlanetId; use TheGame\Application\SharedKernel\Domain\ResourceAmountInterface; use TheGame\Application\SharedKernel\Domain\ResourceIdInterface; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; class StoragesCollection { @@ -50,7 +50,7 @@ public function supports(ResourceAmountInterface $resourceAmount): bool return false; } - public function hasEnough(ResourceRequirementsInterface $requirements): bool + public function hasEnough(ResourcesInterface $requirements): bool { $requirementsArray = $requirements->getAll(); foreach ($this->storages as $storage) { diff --git a/src/Application/Component/ResourceStorage/EventListener/TakeResourcesOnStartingJourneyEventListener.php b/src/Application/Component/ResourceStorage/EventListener/TakeResourcesOnStartingJourneyEventListener.php new file mode 100644 index 0000000..028396b --- /dev/null +++ b/src/Application/Component/ResourceStorage/EventListener/TakeResourcesOnStartingJourneyEventListener.php @@ -0,0 +1,34 @@ +getResourcesLoad() as $resourceId => $amount) { + $resourcesPivot[$resourceId] = ($resourcesPivot[$resourceId] ?? 0) + $amount; + } + + foreach ($resourcesPivot as $resourceId => $amount) { + $command = new UseResourceCommand( + $event->getPlanetId(), + $resourceId, + $amount, + ); + $this->commandBus->dispatch($command); + } + } +} diff --git a/src/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyReturnPointEventListener.php b/src/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyReturnPointEventListener.php new file mode 100644 index 0000000..5e97163 --- /dev/null +++ b/src/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyReturnPointEventListener.php @@ -0,0 +1,39 @@ +getReturnGalaxyPoint()); + $planetId = $this->navigator->getPlanetId($returnGalaxyPoint); + if ($planetId === null) { + throw new InconsistentModelException(sprintf("Planet %s has not been found", $planetId)); + } + + foreach ($event->getResourcesLoad() as $resourceId => $amount) { + $command = new DispatchResourcesCommand( + $planetId->getUuid(), + $resourceId, + $amount, + ); + $this->commandBus->dispatch($command); + } + } +} diff --git a/src/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyTargetPointEventListener.php b/src/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyTargetPointEventListener.php new file mode 100644 index 0000000..c4e43f3 --- /dev/null +++ b/src/Application/Component/ResourceStorage/EventListener/UnloadResourcesAfterReachingJourneyTargetPointEventListener.php @@ -0,0 +1,45 @@ +getMission()); + if ($mission !== MissionType::Stationing && $mission != MissionType::Transport) { + return; + } + + $targetGalaxyPoint = GalaxyPoint::fromString($event->getTargetGalaxyPoint()); + $planetId = $this->navigator->getPlanetId($targetGalaxyPoint); + if ($planetId === null) { + throw new InconsistentModelException(sprintf("Planet %s has not been found", $planetId)); + } + + foreach ($event->getResourcesLoad() as $resourceId => $amount) { + $command = new DispatchResourcesCommand( + $planetId->getUuid(), + $resourceId, + $amount, + ); + $this->commandBus->dispatch($command); + } + } +} diff --git a/src/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandler.php b/src/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandler.php index fb9dcfc..2cae12b 100644 --- a/src/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandler.php +++ b/src/Application/Component/Shipyard/CommandHandler/CancelJobCommandHandler.php @@ -23,7 +23,7 @@ public function __construct( public function __invoke(CancelJobCommand $command): void { $shipyardId = new ShipyardId($command->getShipyardId()); - $shipyard = $this->shipyardRepository->findAggregate($shipyardId); + $shipyard = $this->shipyardRepository->find($shipyardId); if ($shipyard === null) { throw new ShipyardHasNotBeenFoundException($shipyardId); } diff --git a/src/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandler.php b/src/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandler.php index 9c403c7..ae2cc2c 100644 --- a/src/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandler.php +++ b/src/Application/Component/Shipyard/CommandHandler/ConstructCannonsCommandHandler.php @@ -31,7 +31,7 @@ public function __construct( public function __invoke(ConstructCannonsCommand $command): void { $shipyardId = new ShipyardId($command->getShipyardId()); - $shipyard = $this->shipyardRepository->findAggregate($shipyardId); + $shipyard = $this->shipyardRepository->find($shipyardId); if ($shipyard === null) { throw new ShipyardHasNotBeenFoundException($shipyardId); } diff --git a/src/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandler.php b/src/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandler.php index 895f749..945f44b 100644 --- a/src/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandler.php +++ b/src/Application/Component/Shipyard/CommandHandler/ConstructShipsCommandHandler.php @@ -31,7 +31,7 @@ public function __construct( public function __invoke(ConstructShipsCommand $command): void { $shipyardId = new ShipyardId($command->getShipyardId()); - $shipyard = $this->shipyardRepository->findAggregate($shipyardId); + $shipyard = $this->shipyardRepository->find($shipyardId); if ($shipyard === null) { throw new ShipyardHasNotBeenFoundException($shipyardId); } diff --git a/src/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandler.php b/src/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandler.php index 2708aec..57980b5 100644 --- a/src/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandler.php +++ b/src/Application/Component/Shipyard/CommandHandler/FinishJobsCommandHandler.php @@ -23,14 +23,14 @@ public function __construct( public function __invoke(FinishJobsCommand $command): void { $shipyardId = new ShipyardId($command->getShipyardId()); - $shipyard = $this->shipyardRepository->findAggregate($shipyardId); + $shipyard = $this->shipyardRepository->find($shipyardId); if ($shipyard === null) { throw new ShipyardHasNotBeenFoundException($shipyardId); } $summary = $shipyard->finishJobs(); foreach ($summary->getSummary() as $entry) { - $event = $this->finishedConstructionEventFactory->createEvent($entry); + $event = $this->finishedConstructionEventFactory->createEvent($entry, $shipyard->getPlanetId()); $this->eventBus->dispatch($event); } } diff --git a/src/Application/Component/Shipyard/Domain/ConstructibleInterface.php b/src/Application/Component/Shipyard/Domain/ConstructibleInterface.php index c6e8741..63be51f 100644 --- a/src/Application/Component/Shipyard/Domain/ConstructibleInterface.php +++ b/src/Application/Component/Shipyard/Domain/ConstructibleInterface.php @@ -4,7 +4,7 @@ namespace TheGame\Application\Component\Shipyard\Domain; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; interface ConstructibleInterface { @@ -12,7 +12,7 @@ public function getConstructionUnit(): ConstructibleUnit; public function getType(): string; - public function getRequirements(): ResourceRequirementsInterface; + public function getRequirements(): ResourcesInterface; public function getDuration(): int; diff --git a/src/Application/Component/Shipyard/Domain/Entity/Job.php b/src/Application/Component/Shipyard/Domain/Entity/Job.php index a5fbbba..440f6ad 100644 --- a/src/Application/Component/Shipyard/Domain/Entity/Job.php +++ b/src/Application/Component/Shipyard/Domain/Entity/Job.php @@ -7,7 +7,7 @@ use TheGame\Application\Component\Shipyard\Domain\ConstructibleInterface; use TheGame\Application\Component\Shipyard\Domain\ConstructibleUnit; use TheGame\Application\Component\Shipyard\Domain\JobIdInterface; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; class Job implements ConstructibleInterface { @@ -41,7 +41,7 @@ public function getConstructionType(): string return $this->getType(); } - public function getRequirements(): ResourceRequirementsInterface + public function getRequirements(): ResourcesInterface { return $this->constructible->getRequirements()->multipliedBy($this->currentQuantity); } diff --git a/src/Application/Component/Shipyard/Domain/Entity/Shipyard.php b/src/Application/Component/Shipyard/Domain/Entity/Shipyard.php index 6e88aa0..5ad414b 100644 --- a/src/Application/Component/Shipyard/Domain/Entity/Shipyard.php +++ b/src/Application/Component/Shipyard/Domain/Entity/Shipyard.php @@ -14,7 +14,7 @@ use TheGame\Application\Component\Shipyard\Domain\JobIdInterface; use TheGame\Application\Component\Shipyard\Domain\ShipyardIdInterface; use TheGame\Application\SharedKernel\Domain\PlanetIdInterface; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; class Shipyard { @@ -49,7 +49,7 @@ public function getBuildingId(): BuildingIdInterface public function getResourceRequirements( JobIdInterface $jobId, - ): ResourceRequirementsInterface { + ): ResourcesInterface { foreach ($this->jobQueue as $job) { if ($job->getId()->getUuid() === $jobId->getUuid()) { return $job->getRequirements(); diff --git a/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactory.php b/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactory.php index b868ad1..9332b04 100644 --- a/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactory.php +++ b/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactory.php @@ -9,21 +9,26 @@ use TheGame\Application\Component\Shipyard\Domain\Event\NewShipsHaveBeenConstructedEvent; use TheGame\Application\Component\Shipyard\Domain\Event\NewUnitsHaveBeenConstructedEvent; use TheGame\Application\Component\Shipyard\Domain\FinishedJobsSummaryEntryInterface; +use TheGame\Application\SharedKernel\Domain\PlanetIdInterface; use TheGame\Application\SharedKernel\EventInterface; final class FinishedConstructionEventFactory implements FinishedConstructionEventFactoryInterface { - public function createEvent(FinishedJobsSummaryEntryInterface $summaryEntry): EventInterface - { + public function createEvent( + FinishedJobsSummaryEntryInterface $summaryEntry, + PlanetIdInterface $planetId, + ): EventInterface { switch ($summaryEntry->getUnit()) { case ConstructibleUnit::Cannon: { return new NewCannonsHaveBeenConstructedEvent( + $planetId->getUuid(), $summaryEntry->getType(), $summaryEntry->getQuantity(), ); } case ConstructibleUnit::Ship: { return new NewShipsHaveBeenConstructedEvent( + $planetId->getUuid(), $summaryEntry->getType(), $summaryEntry->getQuantity(), ); @@ -31,6 +36,7 @@ public function createEvent(FinishedJobsSummaryEntryInterface $summaryEntry): Ev } return new NewUnitsHaveBeenConstructedEvent( + $planetId->getUuid(), $summaryEntry->getUnit()->value, $summaryEntry->getType(), $summaryEntry->getQuantity(), diff --git a/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactoryInterface.php b/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactoryInterface.php index a6eea9d..fa58e73 100644 --- a/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactoryInterface.php +++ b/src/Application/Component/Shipyard/Domain/Event/Factory/FinishedConstructionEventFactoryInterface.php @@ -5,9 +5,13 @@ namespace TheGame\Application\Component\Shipyard\Domain\Event\Factory; use TheGame\Application\Component\Shipyard\Domain\FinishedJobsSummaryEntryInterface; +use TheGame\Application\SharedKernel\Domain\PlanetIdInterface; use TheGame\Application\SharedKernel\EventInterface; interface FinishedConstructionEventFactoryInterface { - public function createEvent(FinishedJobsSummaryEntryInterface $summaryEntry): EventInterface; + public function createEvent( + FinishedJobsSummaryEntryInterface $summaryEntry, + PlanetIdInterface $planetId, + ): EventInterface; } diff --git a/src/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEvent.php b/src/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEvent.php index 0b4d3aa..9c51061 100644 --- a/src/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEvent.php +++ b/src/Application/Component/Shipyard/Domain/Event/NewCannonsHaveBeenConstructedEvent.php @@ -9,11 +9,17 @@ final class NewCannonsHaveBeenConstructedEvent implements EventInterface { public function __construct( + private readonly string $planetId, private readonly string $type, private readonly int $quantity, ) { } + public function getPlanetId(): string + { + return $this->planetId; + } + public function getType(): string { return $this->type; diff --git a/src/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEvent.php b/src/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEvent.php index e347760..11d8c26 100644 --- a/src/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEvent.php +++ b/src/Application/Component/Shipyard/Domain/Event/NewShipsHaveBeenConstructedEvent.php @@ -9,11 +9,17 @@ final class NewShipsHaveBeenConstructedEvent implements EventInterface { public function __construct( + private readonly string $planetId, private readonly string $type, private readonly int $quantity, ) { } + public function getPlanetId(): string + { + return $this->planetId; + } + public function getType(): string { return $this->type; diff --git a/src/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEvent.php b/src/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEvent.php index 02041c8..40254e4 100644 --- a/src/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEvent.php +++ b/src/Application/Component/Shipyard/Domain/Event/NewUnitsHaveBeenConstructedEvent.php @@ -9,12 +9,18 @@ final class NewUnitsHaveBeenConstructedEvent implements EventInterface { public function __construct( + private readonly string $planetId, private readonly string $unit, private readonly string $type, private readonly int $quantity, ) { } + public function getPlanetId(): string + { + return $this->planetId; + } + public function getUnit(): string { return $this->unit; diff --git a/src/Application/Component/Shipyard/Domain/Factory/JobFactory.php b/src/Application/Component/Shipyard/Domain/Factory/JobFactory.php index 7980abf..51fbabd 100644 --- a/src/Application/Component/Shipyard/Domain/Factory/JobFactory.php +++ b/src/Application/Component/Shipyard/Domain/Factory/JobFactory.php @@ -8,7 +8,7 @@ use TheGame\Application\Component\Shipyard\Domain\Entity\Job; use TheGame\Application\Component\Shipyard\Domain\ValueObject\Cannon; use TheGame\Application\Component\Shipyard\Domain\ValueObject\Ship; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; use TheGame\Application\SharedKernel\UuidGeneratorInterface; final class JobFactory implements JobFactoryInterface @@ -24,7 +24,7 @@ public function createNewCannonsJob( int $shipyardLevel, int $cannonConstructionTime, int $cannonProductionLoad, - ResourceRequirementsInterface $cannonResourceRequirements, + ResourcesInterface $cannonResourceRequirements, ): Job { $cannon = new Cannon( $cannonType, @@ -42,7 +42,7 @@ public function createNewShipsJob( int $shipyardLevel, int $shipConstructionTime, int $shipProductionLoad, - ResourceRequirementsInterface $shipResourceRequirements, + ResourcesInterface $shipResourceRequirements, ): Job { $ship = new Ship( $shipType, diff --git a/src/Application/Component/Shipyard/Domain/Factory/JobFactoryInterface.php b/src/Application/Component/Shipyard/Domain/Factory/JobFactoryInterface.php index 100f7de..c39bfed 100644 --- a/src/Application/Component/Shipyard/Domain/Factory/JobFactoryInterface.php +++ b/src/Application/Component/Shipyard/Domain/Factory/JobFactoryInterface.php @@ -5,7 +5,7 @@ namespace TheGame\Application\Component\Shipyard\Domain\Factory; use TheGame\Application\Component\Shipyard\Domain\Entity\Job; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; interface JobFactoryInterface { @@ -15,7 +15,7 @@ public function createNewCannonsJob( int $shipyardLevel, int $cannonConstructionTime, int $cannonProductionLoad, - ResourceRequirementsInterface $cannonResourceRequirements, + ResourcesInterface $cannonResourceRequirements, ): Job; public function createNewShipsJob( @@ -24,6 +24,6 @@ public function createNewShipsJob( int $shipyardLevel, int $shipConstructionTime, int $shipProductionLoad, - ResourceRequirementsInterface $shipResourceRequirements, + ResourcesInterface $shipResourceRequirements, ): Job; } diff --git a/src/Application/Component/Shipyard/Domain/ValueObject/AbstractConstructible.php b/src/Application/Component/Shipyard/Domain/ValueObject/AbstractConstructible.php index 57f0c1c..1930759 100644 --- a/src/Application/Component/Shipyard/Domain/ValueObject/AbstractConstructible.php +++ b/src/Application/Component/Shipyard/Domain/ValueObject/AbstractConstructible.php @@ -6,13 +6,13 @@ use TheGame\Application\Component\Shipyard\Domain\ConstructibleInterface; use TheGame\Application\Component\Shipyard\Domain\ConstructibleUnit; -use TheGame\Application\SharedKernel\Domain\ResourceRequirementsInterface; +use TheGame\Application\SharedKernel\Domain\ResourcesInterface; abstract class AbstractConstructible implements ConstructibleInterface { public function __construct( private readonly string $type, - private readonly ResourceRequirementsInterface $requirements, + private readonly ResourcesInterface $requirements, private readonly int $duration, private readonly int $productionLoad, ) { @@ -25,7 +25,7 @@ public function getType(): string return $this->type; } - public function getRequirements(): ResourceRequirementsInterface + public function getRequirements(): ResourcesInterface { return $this->requirements; } diff --git a/src/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListener.php b/src/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListener.php index d6d1f1c..6c296fe 100644 --- a/src/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListener.php +++ b/src/Application/Component/Shipyard/EventListener/UpgradeShipyardEventListener.php @@ -28,7 +28,7 @@ public function __invoke(ShipyardConstructionHasBeenFinishedEvent $event): void $shipyard = $event->getUpgradedLevel() === 1 ? $this->shipyardFactory->create($planetId, $buildingId) - : $this->shipyardRepository->findAggregateForBuilding($planetId, $buildingId); + : $this->shipyardRepository->findForBuilding($planetId, $buildingId); if ($shipyard === null) { throw new ShipyardHasNotBeenFoundException($buildingId); diff --git a/src/Application/Component/Shipyard/ShipyardRepositoryInterface.php b/src/Application/Component/Shipyard/ShipyardRepositoryInterface.php index 23e28ed..b46a290 100644 --- a/src/Application/Component/Shipyard/ShipyardRepositoryInterface.php +++ b/src/Application/Component/Shipyard/ShipyardRepositoryInterface.php @@ -11,11 +11,11 @@ interface ShipyardRepositoryInterface { - public function findAggregate( + public function find( ShipyardIdInterface $shipyardId ): ?Shipyard; - public function findAggregateForBuilding( + public function findForBuilding( PlanetIdInterface $planetId, BuildingIdInterface $buildingId ): ?Shipyard; diff --git a/src/Application/SharedKernel/Domain/GalaxyPoint.php b/src/Application/SharedKernel/Domain/GalaxyPoint.php new file mode 100644 index 0000000..e897e19 --- /dev/null +++ b/src/Application/SharedKernel/Domain/GalaxyPoint.php @@ -0,0 +1,68 @@ +galaxy; + } + + public function getSolarSystem(): int + { + return $this->solarSystem; + } + + public function getPlanet(): int + { + return $this->planet; + } + + public function format(): string + { + return sprintf( + '[%d:%d:%d]', + $this->galaxy, + $this->solarSystem, + $this->planet + ); + } + + /** @return int[] */ + public function toArray(): array + { + return [ + $this->galaxy, + $this->solarSystem, + $this->planet, + ]; + } +} diff --git a/src/Application/SharedKernel/Domain/GalaxyPointInterface.php b/src/Application/SharedKernel/Domain/GalaxyPointInterface.php new file mode 100644 index 0000000..60b2344 --- /dev/null +++ b/src/Application/SharedKernel/Domain/GalaxyPointInterface.php @@ -0,0 +1,19 @@ + */ - private array $requirements = []; - - public function add(ResourceAmountInterface $resourceAmount): void - { - $resourceId = $resourceAmount->getResourceId(); - if ($this->hasResource($resourceId)) { - $this->appendResource($resourceAmount); - - return; - } - - $this->requirements[$resourceId->getUuid()] = $resourceAmount; - } - - public function getAmount(ResourceIdInterface $resourceId): int - { - if ($this->hasResource($resourceId) === false) { - return 0; - } - - return $this->requirements[$resourceId->getUuid()]->getAmount(); - } - - private function hasResource(ResourceIdInterface $resourceId): bool - { - return isset($this->requirements[$resourceId->getUuid()]); - } - - private function appendResource(ResourceAmountInterface $incomingResourceAmount): void - { - $resourceId = $incomingResourceAmount->getResourceId(); - $currentResourceAmount = $this->requirements[$resourceId->getUuid()]; - - $this->requirements[$resourceId->getUuid()] = new ResourceAmount( - $resourceId, - $currentResourceAmount->getAmount() + $incomingResourceAmount->getAmount(), - ); - } - - /** @return array */ - public function toScalarArray(): array - { - $retVal = []; - foreach ($this->requirements as $resourceUuid => $resourceAmount) { - $retVal[$resourceUuid] = $resourceAmount->getAmount(); - } - - return $retVal; - } - - /** @return array */ - public function getAll(): array - { - return array_values($this->requirements); - } - - public function multipliedBy(int $quantity): ResourceRequirementsInterface - { - $newRequirements = new self(); - foreach ($this->requirements as $resourceAmount) { - $newAmount = new ResourceAmount( - $resourceAmount->getResourceId(), - $resourceAmount->getAmount() * $quantity, - ); - $newRequirements->add($newAmount); - } - - return $newRequirements; - } -} diff --git a/src/Application/SharedKernel/Domain/ResourceRequirementsInterface.php b/src/Application/SharedKernel/Domain/ResourceRequirementsInterface.php deleted file mode 100644 index ade01ae..0000000 --- a/src/Application/SharedKernel/Domain/ResourceRequirementsInterface.php +++ /dev/null @@ -1,20 +0,0 @@ - */ - public function toScalarArray(): array; - - /** @return array */ - public function getAll(): array; - - public function multipliedBy(int $quantity): ResourceRequirementsInterface; -} diff --git a/src/Application/SharedKernel/Domain/Resources.php b/src/Application/SharedKernel/Domain/Resources.php new file mode 100644 index 0000000..b3aca28 --- /dev/null +++ b/src/Application/SharedKernel/Domain/Resources.php @@ -0,0 +1,120 @@ + */ + private array $resources = []; + + public function add(ResourcesInterface $resources): void + { + foreach ($resources->toScalarArray() as $resourceId => $amount) { + $this->addResource(new ResourceAmount( + new ResourceId($resourceId), + $amount + )); + } + } + + public function addResource(ResourceAmountInterface $resourceAmount): void + { + $resourceId = $resourceAmount->getResourceId(); + if ($this->hasResource($resourceId)) { + $this->appendResource($resourceAmount); + + return; + } + + $this->resources[$resourceId->getUuid()] = $resourceAmount; + } + + public function getAmount(ResourceIdInterface $resourceId): int + { + if ($this->hasResource($resourceId) === false) { + return 0; + } + + return $this->resources[$resourceId->getUuid()]->getAmount(); + } + + private function hasResource(ResourceIdInterface $resourceId): bool + { + return isset($this->resources[$resourceId->getUuid()]); + } + + private function appendResource(ResourceAmountInterface $incomingResourceAmount): void + { + $resourceId = $incomingResourceAmount->getResourceId(); + $currentResourceAmount = $this->resources[$resourceId->getUuid()]; + + $this->resources[$resourceId->getUuid()] = new ResourceAmount( + $resourceId, + $currentResourceAmount->getAmount() + $incomingResourceAmount->getAmount(), + ); + } + + /** @param array $scalarArray */ + public static function fromScalarArray(array $scalarArray): self + { + $resources = new self(); + + foreach ($scalarArray as $resourceId => $quantity) { + $resources->addResource(new ResourceAmount( + new ResourceId($resourceId), + $quantity, + )); + } + + return $resources; + } + + /** @return array */ + public function toScalarArray(): array + { + $retVal = []; + foreach ($this->resources as $resourceUuid => $resourceAmount) { + $retVal[$resourceUuid] = $resourceAmount->getAmount(); + } + + return $retVal; + } + + /** @return array */ + public function getAll(): array + { + return array_values($this->resources); + } + + public function multipliedBy(int $quantity): ResourcesInterface + { + $newResources = new self(); + foreach ($this->resources as $resourceAmount) { + $newAmount = new ResourceAmount( + $resourceAmount->getResourceId(), + $resourceAmount->getAmount() * $quantity, + ); + $newResources->addResource($newAmount); + } + + return $newResources; + } + + public function sum(): int + { + $sum = 0; + + foreach ($this->resources as $resourceAmount) { + $sum += $resourceAmount->getAmount(); + } + + return $sum; + } + + public function clear(): void + { + $this->resources = []; + } +} diff --git a/src/Application/SharedKernel/Domain/ResourcesInterface.php b/src/Application/SharedKernel/Domain/ResourcesInterface.php new file mode 100644 index 0000000..e21ae3e --- /dev/null +++ b/src/Application/SharedKernel/Domain/ResourcesInterface.php @@ -0,0 +1,29 @@ + $scalarArray */ + public static function fromScalarArray(array $scalarArray): self; + + /** @return array */ + public function toScalarArray(): array; + + /** @return array */ + public function getAll(): array; + + public function multipliedBy(int $quantity): ResourcesInterface; + + public function sum(): int; + + public function clear(): void; +} diff --git a/src/Application/SharedKernel/Domain/UserId.php b/src/Application/SharedKernel/Domain/UserId.php new file mode 100644 index 0000000..599ab36 --- /dev/null +++ b/src/Application/SharedKernel/Domain/UserId.php @@ -0,0 +1,18 @@ +id; + } +} diff --git a/src/Application/SharedKernel/Domain/UserIdInterface.php b/src/Application/SharedKernel/Domain/UserIdInterface.php new file mode 100644 index 0000000..8ece966 --- /dev/null +++ b/src/Application/SharedKernel/Domain/UserIdInterface.php @@ -0,0 +1,11 @@ +