diff --git a/.github/issues.json b/.github/issues.json index 32960f8..566c73b 100644 --- a/.github/issues.json +++ b/.github/issues.json @@ -1,2 +1,7 @@ [ + { + "title": "logic to check if the item is durable, because an item that has quantity > 1 cannot be stackable", + "description": "An item that has quantity > 1 cannot be stackable", + "labels": ["bug"] + } ] \ No newline at end of file diff --git a/src/Application/UseCase/Dice/RollDice/RollDiceUseCase.php b/src/Application/UseCase/Dice/RollDice/RollDiceUseCase.php index a38c5fe..00d054c 100644 --- a/src/Application/UseCase/Dice/RollDice/RollDiceUseCase.php +++ b/src/Application/UseCase/Dice/RollDice/RollDiceUseCase.php @@ -7,7 +7,6 @@ use Eco\Result; use RPGPlayground\Application\UseCase\Dice\RollDice\RollDiceInput; use RPGPlayground\Application\UseCase\Dice\RollDice\RollDiceOutput; -use RPGPlayground\Domain\Actions\Dice\RollDiceAction; use RPGPlayground\Domain\Enums\Roll\RollAttribute; final class RollDiceUseCase @@ -46,11 +45,11 @@ private static function roll(RollDiceInput $input): array $rolls = []; for ($i = 0; $i < $input->multiplier; $i++) { - $rolls[] = RollDiceAction::roll($input->dice); + $rolls[] = $input->dice->roll(); } if ($input->attribute !== null) { - $rolls[] = RollDiceAction::roll($input->dice); + $rolls[] = $input->dice->roll(); } return $rolls; diff --git a/src/Core/Handler/LogHandler.php b/src/Core/Handler/LogHandler.php index 234e28a..db0bdc5 100644 --- a/src/Core/Handler/LogHandler.php +++ b/src/Core/Handler/LogHandler.php @@ -1,5 +1,7 @@ sides); - } -} diff --git a/src/Domain/Entities/Character.php b/src/Domain/Entities/Character.php new file mode 100644 index 0000000..9ee04ae --- /dev/null +++ b/src/Domain/Entities/Character.php @@ -0,0 +1,72 @@ +inventory = $inventory; + } + + public function getInventory(): Inventory + { + return $this->inventory; + } +} diff --git a/src/Domain/Entities/Check.php b/src/Domain/Entities/Check.php index f200f9a..9f5dc51 100644 --- a/src/Domain/Entities/Check.php +++ b/src/Domain/Entities/Check.php @@ -4,6 +4,8 @@ namespace RPGPlayground\Domain\Entities; +use RPGPlayground\Core\Handler\StrHandler; + final class Check { private function __construct( @@ -23,6 +25,8 @@ public static function create(string $title, int $threshold): self throw new \InvalidArgumentException('Title cannot be empty.'); } + $title = StrHandler::sanitize($title); + if ($threshold < 0) { throw new \InvalidArgumentException('Threshold must be greater than or equal to 0.'); } diff --git a/src/Domain/Entities/Inventory.php b/src/Domain/Entities/Inventory.php new file mode 100644 index 0000000..20dd84f --- /dev/null +++ b/src/Domain/Entities/Inventory.php @@ -0,0 +1,146 @@ +checkItemsWeight($items); + } + + /** + * @param InventoryItem[] $items + * @return static + * @throws \InvalidArgumentException + */ + private function checkItemsWeight(array $items): void + { + $totalWeight = 0; + + foreach ($items as $item) { + $totalWeight += $item->getItem()->getWeight(); + } + + if ($totalWeight > $this->maxWeight) { + throw new \InvalidArgumentException('Total weight of items must be less than or equal to max weight'); + } + } + + public function add(InventoryItem $item): void + { + if ($this->has($item->getItem()->getId())) { + throw new \InvalidArgumentException('Item already exists in inventory'); + } + + if ((count($this->items) + $item->getQuantity()) > $this->slots) { + throw new \InvalidArgumentException('Inventory is full'); + } + + // TODO: checks if the inventory will fit the items if the new item is added + $this->checkItemsWeight([...$this->items, $item]); + + $this->items[$item->getItem()->getId()->value] = $item; + } + + /** + * @param Identifier $id + * @return void + */ + public function remove(Identifier $id): void + { + if (!$this->has($id)) { + throw new \InvalidArgumentException('Item not found in inventory'); + } + + unset($this->items[$id->value]); + } + + /** + * @param Identifier $id + * @param int $quantity + * @return self + * @throws \InvalidArgumentException + */ + public function drop(Identifier $id, int $quantity): self + { + if (!$this->has($id)) { + throw new \InvalidArgumentException('Item not found in inventory'); + } + + $item = $this->get($id); + + if ($item->getQuantity() == $quantity) { + $this->remove($id); + return $this; + } + + if ($quantity > $item->getQuantity()) { + throw new \InvalidArgumentException("Not enough quantity of {$item->getItem()->getName()} to drop"); + } + + $item->setQuantity($item->getQuantity() - $quantity); + + $this->items[$id->value] = $item; + + return $this; + } + + /** + * @param Identifier $id + * @return InventoryItem + * @throws \InvalidArgumentException + */ + public function get(Identifier $id): InventoryItem + { + if (!$this->has($id)) { + throw new \InvalidArgumentException('Item not found in inventory'); + } + + return $this->items[$id->value]; + } + + /** + * @param Identifier $id + * @return bool + */ + public function has(Identifier $id): bool + { + if (empty($id)) { + throw new \InvalidArgumentException('Id cannot be empty'); + } + + return array_key_exists($id->value, $this->items); + } + + /** + * @return InventoryItem[] + */ + public function show(): array + { + return $this->items; + } +} diff --git a/src/Domain/Entities/Item.php b/src/Domain/Entities/Item.php new file mode 100644 index 0000000..74ba6f0 --- /dev/null +++ b/src/Domain/Entities/Item.php @@ -0,0 +1,81 @@ +name = $name; + $this->description = $description; + } + + /** + * @return Identifier + */ + public function getId(): Identifier + { + return $this->id; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @return float + */ + public function getWeight(): float + { + return $this->weight; + } + + /** + * @return Attributes + */ + public function getAttributes(): Attributes + { + return $this->attributes; + } +} diff --git a/src/Domain/Entities/Item/DurableItem.php b/src/Domain/Entities/Item/DurableItem.php new file mode 100644 index 0000000..196c254 --- /dev/null +++ b/src/Domain/Entities/Item/DurableItem.php @@ -0,0 +1,42 @@ +item; + } + + /** + * @return Durability + */ + public function getDurability(): Durability + { + return $this->durability; + } + + /** + * @param Item $item + * @param Durability $durability + * @return self + */ + public static function fromItem(Item $item, Durability $durability): self + { + return new self($item, $durability); + } +} diff --git a/src/Domain/Entities/Item/InventoryItem.php b/src/Domain/Entities/Item/InventoryItem.php new file mode 100644 index 0000000..295535e --- /dev/null +++ b/src/Domain/Entities/Item/InventoryItem.php @@ -0,0 +1,58 @@ +item; + } + + /** + * @return int + */ + public function getQuantity(): int + { + return $this->quantity; + } + + /** + * @param int $quantity + * @return void + * @throws \InvalidArgumentException + */ + public function setQuantity(int $quantity): void + { + if ($quantity < 1) { + throw new \InvalidArgumentException('Quantity cannot be less than 1'); + } + $this->quantity = $quantity; + } + + /** + * @param Item $item + * @param int $quantity + * @return self + */ + public static function fromItem(Item $item, int $quantity = 1): self + { + return new self($item, $quantity); + } +} diff --git a/src/Domain/ValueObjects/Attributes.php b/src/Domain/ValueObjects/Attributes.php new file mode 100644 index 0000000..bc9ba92 --- /dev/null +++ b/src/Domain/ValueObjects/Attributes.php @@ -0,0 +1,43 @@ +key()] = $attribute; + } + + parent::__construct($bundleData); + } +} + +abstract class Attribute +{ + /** + * @return string + */ + abstract public function key(): string; + + /** + * @return string + */ + abstract public function description(): string; + + /** + * @return int + */ + abstract public function value(): int; +} diff --git a/src/Domain/ValueObjects/Attributes/GenericAttribute.php b/src/Domain/ValueObjects/Attributes/GenericAttribute.php new file mode 100644 index 0000000..7115a7b --- /dev/null +++ b/src/Domain/ValueObjects/Attributes/GenericAttribute.php @@ -0,0 +1,43 @@ +key; + } + + /** + * @return string + */ + #[\Override] + public function description(): string + { + return $this->description; + } + + /** + * @return int + */ + #[\Override] + public function value(): int + { + return $this->value; + } +} diff --git a/src/Domain/ValueObjects/Bundle.php b/src/Domain/ValueObjects/Bundle.php new file mode 100644 index 0000000..8652f46 --- /dev/null +++ b/src/Domain/ValueObjects/Bundle.php @@ -0,0 +1,60 @@ + $bundleData + */ + public function __construct( + private array $bundleData = [], + ) {} + + /** + * @param array $bundleData + * @return static + */ + public function add(array $bundleData): static + { + return new static(array_merge($this->bundleData, $bundleData)); + } + + /** + * @param string $key + * @return static + */ + public function remove(string $key): static + { + return new static(array_diff_key($this->bundleData, [$key => true])); + } + + /** + * @param string $key + * @return TData|null + */ + public function get(string $key): mixed + { + return $this->has($key) ? $this->bundleData[$key] : null; + } + + /** @return array */ + public function show(): array + { + return $this->bundleData; + } + + /** + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + return array_key_exists($key, $this->bundleData); + } +} diff --git a/src/Domain/ValueObjects/Character/Identity.php b/src/Domain/ValueObjects/Character/Identity.php new file mode 100644 index 0000000..18d5ac7 --- /dev/null +++ b/src/Domain/ValueObjects/Character/Identity.php @@ -0,0 +1,18 @@ + $identityData + */ + public function __construct(array $identityData = []) + { + parent::__construct($identityData); + } +} diff --git a/src/Domain/ValueObjects/Character/Statistics.php b/src/Domain/ValueObjects/Character/Statistics.php new file mode 100644 index 0000000..50c5a83 --- /dev/null +++ b/src/Domain/ValueObjects/Character/Statistics.php @@ -0,0 +1,48 @@ +key()] = $statistic; + } + + parent::__construct($bundleData); + } +} + +abstract class Statistic +{ + /** + * @return string + */ + abstract public function key(): string; + + /** + * @return int + */ + abstract public function current(): int; + + /** + * @param int $value + */ + abstract public function increase(int $value): static; + + /** + * @param int $value + */ + abstract public function decrease(int $value): static; +} diff --git a/src/Domain/ValueObjects/Character/Statistics/GenericStatistic.php b/src/Domain/ValueObjects/Character/Statistics/GenericStatistic.php new file mode 100644 index 0000000..9eff19d --- /dev/null +++ b/src/Domain/ValueObjects/Character/Statistics/GenericStatistic.php @@ -0,0 +1,68 @@ +current = 0; + } + + /** + * @return string + */ + #[\Override] + public function key(): string + { + return $this->key; + } + + /** + * @return int + */ + #[\Override] + public function current(): int + { + return $this->current; + } + + /** + * @param int $value + * @return static + */ + #[\Override] + public function increase(int $value): static + { + if ($this->current >= $this->max) { + return $this; + } + + $clone = clone $this; + $clone->current = $this->current + $value; + return $clone; + } + + /** + * @param int $value + * @return static + */ + #[\Override] + public function decrease(int $value): static + { + if ($this->current <= 0) { + return $this; + } + $clone = clone $this; + $clone->current = $this->current - $value; + return $clone; + } +} diff --git a/src/Domain/ValueObjects/Character/Statistics/Health.php b/src/Domain/ValueObjects/Character/Statistics/Health.php new file mode 100644 index 0000000..b964ca7 --- /dev/null +++ b/src/Domain/ValueObjects/Character/Statistics/Health.php @@ -0,0 +1,71 @@ +current = $max; + } + + /** + * @return string + */ + #[\Override] + public function key(): string + { + return self::KEY; + } + + /** + * @return int + */ + #[\Override] + public function current(): int + { + return $this->current; + } + + /** + * @param int $value + * @return static + */ + #[\Override] + public function increase(int $value): static + { + if ($this->current >= $this->max) { + return $this; + } + $clone = clone $this; + $clone->current = $this->current + $value; + return $clone; + } + + /** + * @param int $value + * @return static + */ + #[\Override] + public function decrease(int $value): static + { + if ($this->current <= 0) { + return $this; + } + $clone = clone $this; + $clone->current = $this->current - $value; + return $clone; + } +} diff --git a/src/Domain/ValueObjects/Dice.php b/src/Domain/ValueObjects/Dice.php index 7f9a928..a45adbe 100644 --- a/src/Domain/ValueObjects/Dice.php +++ b/src/Domain/ValueObjects/Dice.php @@ -19,4 +19,14 @@ public function __construct( throw new \InvalidArgumentException('Invalid number of sides for a dice'); } } + + /** + * Returns the result of rolling a dice with the given number of sides. + * @return int The result of the roll. + * @throws \Random\RandomException if the system entropy source fails. + */ + public function roll(): int + { + return random_int(self::MINIMUM_VALUE, $this->sides); + } } diff --git a/src/Domain/ValueObjects/Durability.php b/src/Domain/ValueObjects/Durability.php new file mode 100644 index 0000000..6f018df --- /dev/null +++ b/src/Domain/ValueObjects/Durability.php @@ -0,0 +1,35 @@ + $max) { + throw new \InvalidArgumentException('Durability cannot be less than 1 or greater than max'); + } + } + + /** + * @param int $value + * @return self + */ + public function increase(int $value): self + { + return new self(min($this->current + $value, $this->max), $this->max); + } + + /** + * @param int $value + * @return self + */ + public function decrease(int $value): self + { + return new self(max($this->current - $value, 0), $this->max); + } +} diff --git a/tests/Domain/Actions/Dice/RollDiceActionTest.php b/tests/Domain/Actions/Dice/RollDiceActionTest.php deleted file mode 100644 index 5253507..0000000 --- a/tests/Domain/Actions/Dice/RollDiceActionTest.php +++ /dev/null @@ -1,87 +0,0 @@ -assertGreaterThanOrEqual(Dice::MINIMUM_VALUE, $result); - $this->assertLessThanOrEqual(20, $result); - } - - public function test_roll_never_returns_zero(): void - { - $dice = new Dice(6); - - for ($i = 0; $i < 100; $i++) { - $this->assertGreaterThanOrEqual(Dice::MINIMUM_VALUE, RollDiceAction::roll($dice)); - } - } - - public function test_roll_never_exceeds_sides(): void - { - $dice = new Dice(6); - - for ($i = 0; $i < 100; $i++) { - $this->assertLessThanOrEqual(6, RollDiceAction::roll($dice)); - } - } - - // ------------------------------------------------------------------------- - // D1 edge case - // ------------------------------------------------------------------------- - - public function test_d1_always_returns_one(): void - { - $dice = new Dice(1); - - for ($i = 0; $i < 10; $i++) { - $this->assertSame(1, RollDiceAction::roll($dice)); - } - } - - // ------------------------------------------------------------------------- - // Common dice types - // ------------------------------------------------------------------------- - - #[\PHPUnit\Framework\Attributes\DataProvider('commonDiceProvider')] - public function test_roll_stays_within_range_for_common_dice(int $sides): void - { - $dice = new Dice($sides); - $result = RollDiceAction::roll($dice); - - $this->assertGreaterThanOrEqual(Dice::MINIMUM_VALUE, $result); - $this->assertLessThanOrEqual($sides, $result); - } - - /** - * @return array - */ - public static function commonDiceProvider(): array - { - return [ - 'D4' => [4], - 'D6' => [6], - 'D8' => [8], - 'D10' => [10], - 'D12' => [12], - 'D20' => [20], - 'D100' => [100], - ]; - } -}