Reliable - pierwszy commit

This commit is contained in:
Sasza Stanczew 2024-05-06 11:58:30 +02:00
parent 9d022ab235
commit 245cbec983
730 changed files with 51141 additions and 0 deletions

View file

@ -0,0 +1,19 @@
Copyright (C) 2020 Hakim El Hattab, http://hakim.se, and reveal.js contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,28 @@
<!-- .slide: data-background="#ccc" -->
### Agenda
* <!-- .element: class="fragment fade-in" --> Exceptions
* <!-- .element: class="fragment fade-in" --> How to throw and catch exceptions
* <!-- .element: class="fragment fade-in" --> Working with excpetions
* <!-- .element: class="fragment fade-in" --> Alternative ways to signal an error
* <!-- .element: class="fragment fade-in" --> Good practise:
* <!-- .element: class="fragment fade-in" --> SOLID
* <!-- .element: class="fragment fade-in" --> DRY
* <!-- .element: class="fragment fade-in" --> KISS
* <!-- .element: class="fragment fade-in" --> Code refactoring
* <!-- .element: class="fragment fade-in" --> Testing
* <!-- .element: class="fragment fade-in" --> GTEST
* <!-- .element: class="fragment fade-in" --> GMOCK
* <!-- .element: class="fragment fade-in" --> How to create simple thread safe logger
* <!-- .element: class="fragment fade-in" --> Design patterns
* <!-- .element: class="fragment fade-in" --> Observer
* <!-- .element: class="fragment fade-in" --> Strategy
* <!-- .element: class="fragment fade-in" --> Visitor
* <!-- .element: class="fragment fade-in" --> Template method
* <!-- .element: class="fragment fade-in" --> Command
* <!-- .element: class="fragment fade-in" --> Builder
* <!-- .element: class="fragment fade-in" --> Factory
* <!-- .element: class="fragment fade-in" --> Decorator
<!-- .slide: style="font-size: 0.82em" -->

View file

@ -0,0 +1,806 @@
## Builder
Whenever we need to create some complex object, which may have a lot of parameters, we end up with a constructor which takes plenty of arguments. In most cases we even don't want to set up all of this, so we create dozens of constructors which allow us to set up only needed parameters. And when we add another parameter, we need to add few more constructors. That's Horrible, isn't it? So how we can solve such a problem? How to create only one Constructor and allow user to build an object by injecting only needed parameters. In previous sentence I use a word `buid`, and this is exactly a design pattern that I want to present you.
<img data-src="images/builder_uml.png" alt="Builder UML">
___
## Ship
This is the constructor of class `Ship`. We can easily imagine that in the future we will add at least a few more parameters.
```C++
Ship::Ship(Time* time,
std::unique_ptr<DifficultLvlStrategy<Ship, bool>> strategy,
const std::string& name,
int capacity,
int maxCrew,
int crew,
std::unique_ptr<PrintVisitor>&& visitor,
int armor,
int maxArmor,
int canons,
int maxCanons,
int durability,
int maxDurability)
: _time(time),
_strategy(std::move(strategy)),
_name(name),
_capacity(capacity),
_maxCrew(maxCrew),
_crew(crew),
_visitor(std::move(visitor)),
_armor(armor),
_maxArmor(maxArmor),
_canons(canons),
_maxCanons(maxCanons)
_durability(durability),
_maxDurability(maxDurability) {
if (_capacity < 0) {
throw std::runtime_error("Capacity can't be negative value!");
}
assert(time);
_time->attach(this);
}
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.60em" -->
___
## Create a ship
Can you explain to me which number argument is an `armor` value, and which is a `cannon` value? Reading such code is quite hard. We can easily provide values in wrong order and instead of setting `armor` we will set a `maxCrew` or something else.
```C++
auto ship = Ship(
time,
std::move(strategy),
"Black Pearl",
200,
100,
50,
std:move(visitor),
100,
200,
20,
25,
1000,
1000);
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.80em" -->
___
## A new way
This is a new way of creating a ship. What do you think about this approach?
```C++
// We need to provide all mandatory parameters, the rest will have default values.
auto ship = Ship(time, name, capacity, maxCrew, maxArmor, maxCannons, maxDurability)
// Provide optional arguemnts
.setDifficultLvlStrategy(std::move(strategy))
.setCrew(100)
.setPrintVisitor(std::move(visitor))
.setArmor(100)
.setCannons(10)
.setDurability(1000)
.build();
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.92em" -->
___
## A better way
In previous example, we still need to provide few arguments in constructor before we will create an object. We can refactor this code and verify in `build` method if all mandatory parameters were set.
```C++
auto ship = ShipBuilder().setTime(time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.setDifficultLvlStrategy(std::move(strategy))
.setCrew(100)
.setPrintVisitor(std::move(visitor))
.setArmor(100)
.setCannons(10)
.setDurability(1000)
.build();
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.92em" -->
___
## Builder class
```C++
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique<Ship>()) {}
[[nodiscard]] std::unique_ptr<Ship> build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->time() ||
_ship->name().empty() ||
_ship->capacity() == -1 ||
_ship->maxCrew() == -1 ||
_ship->maxArmor() == -1 ||
_ship->maxCannons() == -1 ||
_ship->maxDurability() == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
private:
std::unique_ptr<Ship> _ship;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.67em" -->
___
## Setters
Now we need to add all setters to `ShipBuilder` class
```C++
ShipBuilder& setTime(Time* time) {
_ship->setTime(time);
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_ship->setStrategy(std::move(strategy));
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->setName(name);
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->setCapacity(capacity);
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->setMaxCrew(maxCrew);
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->setCrew(crew);
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
_ship->setVisitor(std::move(visitor));
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->setArmor(armor);
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->setMaxArmor(maxArmor);
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->setCannons(cannons);
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->setMaxCannons(maxCannons);
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->setDurability(durability);
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->setMaxDurability(maxDurability);
return *this;
}
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.62em" -->
___
## The ugly part
Unfortunately, if we want to follow the encapsulation rule we need to duplicate `setters` in class `Ship` we need also provide getters for all members (even if we don't use them by other classes).
```C++
class Ship {
public:
Ship& setTime(Time* time) {
_time = time;
return *this;
}
Ship& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_strategy = std::move(strategy);
return *this;
}
Ship& setName(const std::string& name) {
_name = name;
return *this;
}
Ship& setCapacity(int capacity) {
_capacity = capacity;
return *this;
}
Ship& setMaxCrew(int maxCrew) {
_maxCrew = maxCrew;
return *this;
}
Ship& setCrew(int crew) {
_crew = crew;
return *this;
}
Ship& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
visitor = std::move(_visitor);
return *this;
}
Ship& setArmor(int armor) {
_armor = armor;
return *this;
}
Ship& setMaxArmor(int maxArmor) {
_maxArmor = maxArmor;
return *this;
}
Ship& setCannons(int cannons) {
_canons = cannons;
return *this;
}
Ship& setMaxCannons(int maxCannons) {
_maxCannons = maxCannons;
return *this;
}
Ship& setDurability(int durability) {
_durability = durability;
return *this;
}
Ship& setMaxDurability(int maxDurability) {
_maxDurability = maxDurability;
return *this;
}
const Time* time() const { return _time; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int maxCrew() const { return _maxCrew; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int maxArmor() const { return _maxArmor; }
int canons() const { return _canons; }
int maxCannons() const { return _maxCannons; }
int durability() const { return _durability; }
int maxDurability() const { return _maxDurability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr<Ship> std::make_unique<Ship>();
Ship() = default;
Time* _time{};
std::unique_ptr<DifficultStrategy<Ship, bool>> _strategy{};
std::string _name{};
int _capacity{-1};
int _maxCrew{-1};
int _crew{10};
std::unique_ptr<PrintVisitor> _visitor{};
int _armor{0};
int _maxArmor{-1};
int _canons{0};
int _maxCannons{-1};
int _durability{100};
int _maxDurability{-1};
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.62em" -->
___
## Clean up
In my opinion, a better option is to declare friendship with class `ShipBuilder`. Generally, friend with other classes is not a good idea. But in builder pattern we have a lot of profits:
* <!-- .element: class="fragment fade-in" --> Don't need to duplicate setters
* <!-- .element: class="fragment fade-in" --> Some of the fields should not be changed after Ship will be built, but still we need to have a setter for this field
* <!-- .element: class="fragment fade-in" --> We don't need to return a reference to `Ship` for all setters.
* <!-- .element: class="fragment fade-in" --> Don't need to have getters for all members
___
```C++
class Ship {
public:
void setCrew(int crew) { _crew = crew; }
void setArmor(int armor) { _armor = armor; }
void setCannons(int cannons) { _canons = cannons; }
void setDurability(int durability) { _durability = durability; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int canons() const { return _canons; }
int durability() const { return _durability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr<Ship> std::make_unique<Ship>();
Ship() = default;
}
```
<!-- .slide: style="font-size: 0.82em" -->
___
```C++
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique<Ship>()) {}
[[nodiscard]] std::unique_ptr<Ship> build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->_time ||
_ship->_name.empty() ||
_ship->_capacity == -1 ||
_ship->_maxCrew == -1 ||
_ship->_maxArmor == -1 ||
_ship->_maxCannons == -1 ||
_ship->_maxDurability == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
ShipBuilder& setTime(Time* time) {
_ship->_time = time;
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_ship->_strategy = std::move(strategy);
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->_name = name;
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->_capacity = capacity;
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->_maxCrew = maxCrew;
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->_crew = crew;
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
_ship->_visitor = std::move(visitor);
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->_armor = armor;
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->_maxArmor = maxArmor;
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->_canons = cannons;
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->_maxCannons = maxCannons;
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->_durability = durability;
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->_maxDurability = maxDurability;
return *this;
}
private:
std::unique_ptr<Ship> _ship;
};
```
<!-- .slide: style="font-size: 0.62em" -->
___
## Usage
For both implementations, we use builder in the same way:
```C++
int main() {
auto ship = ShipBuilder().build();
if (ship) {
std::cout << "Ship name: " << ship->name() << '\n';
}
Time time;
auto ship2 = ShipBuilder()
.setTime(&time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.build();
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
}
```
<!-- .element: class="fragment fade-in" -->
```bash
Mandatory fields are not set!
Ship name: Black Pearl
```
<!-- .slide: style="font-size: 0.72em" -->
<!-- .element: class="fragment fade-in" -->
___
## gRPC
A perfect example of builder is an gRPC library. We use the builder pattern to create a server object, which we can customize. Based on provided arguments we can create synchronous or asynchronous server, with or without compression, with one or more services, with additional options like max response size etc...
```C++
grpc::Status status;
std::shared_ptr<grpc::AuthorizationPolicyProviderInterface> provider =
grpc::FileWatcherAuthorizationPolicyProvider::Create(
authz_policy_path, /*refresh_interval_sec=*/3600, &status);
auto option = std::make_unqiue<grpc::ServerBuilderOption>();
grpc::ChannelArguments args;
args.SetMaxReceiveMessageSize(4096);
args.SetMaxSendMessageSize(4096);
option->UpdateArguments(args);
std::unique_ptr<grpc::Server> server =
grpc::ServerBuilder().AddListeningPort("127.0.0.1:10000", InsecureServerCredentials())
.RegisterService(&service1)
.RegisterService("127.0.0.1:9999", &service2)
.SetAuthorizationPolicyProvider(provider)
.SetOption(std::move(option))
.SetDefaultCompressionLevel(grpc::GRPC_COMPRESS_LEVEL_HIGH)
.BuildAndStart();
```
<!-- .slide: style="font-size: 0.72em" -->
<!-- .element: class="fragment fade-in" -->
___
## DRY
At the beginning, we decided to have 3 types of ships. We always need to remember which value to pass to each parameter to create a specific type of ship. If we decide that something is not okay with balance, we need to change this parameter everywhere. If we forget to change it in some code, we end up with different parameters for the same ship type. We can of course decided to have some global variable, but this is an ugly solution. **We should wrap the whole logic in one place**. To make it more flexible, we will describe all parameters in a JSON file, so we will not be forced to recompile the whole binary every time we change the ship parameters. Because we don't want to modify existing code, we just create a new class that inherit `private` from the `ShipBuilder` so we can't use the interface `ShipBuilder` outside, but still `ShipJsonBuilder` may use this implementation.
```C++
class ShipJsonBuilder : private ShipBuilder {
public:
ShipJsonBuilder(const std::filesystem::path& path) { parseFile(path); }
std::unique_ptr<Ship> buildBrig(Time* time, const std::string& name) const {
return setTime(time)
.setName(name)
.setCapacity(_shipsData["brig"]["capacity"])
.setMaxCrew(_shipsData["brig"]["maxCrew"])
.setMaxArmor(_shipsData["brig"]["maxArmor"])
.setMaxCannons(_shipsData["brig"]["maxCannons"])
.setMaxDurability(_shipsData["brig"]["maxDurability"])
.build();
}
std::unique_ptr<Ship> buildFrigate(Time* time, const std::string& name) const;
std::unique_ptr<Ship> buildGaleon(Time* time, const std::string& name) const;
private:
json _shipsData;
void parseFile(const std::filesystem::path& path); // Parse file and store data in _shipsData
};
```
<!-- .slide: style="font-size: 0.67em" -->
<!-- .element: class="fragment fade-in" -->
___
## Usage
Now, we can easily create one of existing types of ship, without worry about which values to pass. What's more, we read the file from JSON, so we decouple values from the binary, so every time we make some changes in balance, we don't need to recompile any single file.
```C++
int main() {
Time time;
auto ship1 = ShipJsonBuilder().buildGaleon(&time, "Queens Anne Revenge");
if (ship1) {
std::cout << "Ship name: " << ship1->name() << '\n';
}
auto ship2 = ShipJsonBuilder().buildFrigate(&time, "Black Pearl");
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
auto ship3 = ShipJsonBuilder().buildBrig(&time, "Black Widow");
if (ship3) {
std::cout << "Ship name: " << ship3->name() << '\n';
}
}
```
<!-- .element: class="fragment fade-in" -->
```bash
Ship name: Queens Anne Revenge
Ship name: Black Pearl
Ship name: Black Widow
```
<!-- .slide: style="font-size: 0.74em" -->
<!-- .element: class="fragment fade-in" -->
___
## Different builder
But there is even more! Currently, we focus only on one builder, which always create a `Ship` class. We also create a `ShipJsonBuilder` which reuse implementation from `ShipBuilder` and allow to user read default parameters from the file, so we always create a `Ship` of the same type with equal parameters. We also saw that gRPC use the same logic to also user create various type of server. Furthermore, gRPC add plenty of methods which allow us to set parameters of this server. We can even go further and create a few types of builder, and then pass a pointer to the base class to `Driector` and create a different object.
```C++
class CrewBuilder {
public:
CrewBuilder& setName(const std::string& name) = 0;
CrewBuilder& setWeapon(std::unique_ptr<Weapon>&& weapon) = 0;
CrewBuilder& setHp(Hp hp) = 0;
std::unique_ptr<Crew> build() = 0;
};
```
<!-- .slide: style="font-size: 0.74em" -->
<!-- .element: class="fragment fade-in" -->
```C++
class PirateBuilder : public CrewBuilder {
public:
CrewBuilder& setName(const std::string& name) override { /* some implementation */ }
CrewBuilder& setWeapon(std::unique_ptr<Weapon>&& weapon) override { /* some implementation */ }
CrewBuilder& setHp(Hp hp) override { /* some implementation */ }
std::unique_ptr<Crew> build() { return std::make_unique<Pirate>(/* args */); }
};
```
<!-- .element: class="fragment fade-in" -->
___
## Tavern
Based on which tavern we are, we can recruit, for instance:
* <!-- .element: class="fragment fade-in" --> the marine,
* <!-- .element: class="fragment fade-in" --> the volunteer,
* <!-- .element: class="fragment fade-in" --> the pirate
Each kind of Sailor will act differently in combat and during sail. They also have a different pay. We don't want to hard-code a type of Tavern on every island, because during a game, we can for instance conquer an island and free it forms pirates, so we will no longer recruit their pirates, but maybe a marine or volunteer. That's why we should pass a builder class as an argument to tavern, so we can easily swap it during a game.
<!-- .element: class="fragment fade-in" -->
```C++
class Tawern {
public:
Tawern(std::unique_ptr<Builder>&& builder): _builder(std::move(builder)) {}
std::unique_ptr<Crew> recruit(const std::string& name, std::unique_ptr<Weapon>&& weapon) {
return _builder->setName(name).setWeapon(weapon).setHp(40).build();
}
private:
std::unique_ptr<Builder> _builder;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.74em" -->
___
## Mixing two design patterns
If you have a feeling that this looks similar to factory method, you are right! Both design patterns are similar. There are few main differences:
* <!-- .element: class="fragment fade-in" --> Builder focuses on constructing a complex object step by step. Abstract Factory emphasizes a family of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.
* <!-- .element: class="fragment fade-in" --> Builder often builds a Composite.
* <!-- .element: class="fragment fade-in" --> Often, designs start out using Factory Method (less complicated, more customizable, subclasses proliferate) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, more complex) as the designer discovers where more flexibility is needed.
* <!-- .element: class="fragment fade-in" --> Sometimes creational patterns are complementary: Builder can use one of the other patterns to implement which components get built. Abstract Factory, Builder, and Prototype can use Singleton in their implementations.
<!-- .slide: style="font-size: 0.84em" -->
___
## Exercise 1
* <!-- .element: class="fragment fade-in" --> Go to the directory <code>Ship</code> and create class <code>ShipBuilder</code>. The mandatory fields are:
* <!-- .element: class="fragment fade-in" --> name
* <!-- .element: class="fragment fade-in" --> capacity
* <!-- .element: class="fragment fade-in" --> time
* <!-- .element: class="fragment fade-in" --> The non-mandatory fields are:
* <!-- .element: class="fragment fade-in" --> difficultStrategy -> default value should be set as <b>Easy</b>
* <!-- .element: class="fragment fade-in" --> crew -> default value should be set as <b>10</b>
* <!-- .element: class="fragment fade-in" --> durability -> default value should be set as <b>1000</b>
* <!-- .element: class="fragment fade-in" --> armor -> default value should be set as <b>0</b>
* <!-- .element: class="fragment fade-in" --> PrintVisitor -> default value should be set as <b>Pretty Print</b>
* <!-- .element: class="fragment fade-in" --> Try to build your own <code>Ship</code>!
___
## Exercise 2
* <!-- .element: class="fragment fade-in" --> Go to the directory <code>Ship</code> and finish implementation of class <code>ShipJsonBuilder</code>. You should allow building 3 types of ship:
* <!-- .element: class="fragment fade-in" --> Brig
* <!-- .element: class="fragment fade-in" --> Frigate
* <!-- .element: class="fragment fade-in" --> Galoen
* <!-- .element: class="fragment fade-in" --> If you want to read a JSON filed, just use <b>operator[]</b> to get any of nested field, example: <code>_shipsData["brige"]["armor"]</code>
* <!-- .element: class="fragment fade-in" --> Try to build a ship using a new builder.
* <!-- .element: class="fragment fade-in" --> Try to change some values in <code>ships.json</code> and run binary without recompilation. You should see new values!
___
## Factory method
Because we saw in the previous example a factory method patter, I want to tell a little more about it. Whenever we want to hide implementation details about creation of new object (and also a final type) we should use factory method.
<img data-src="images/factory_method_uml.png" alt="Factory method UML">
___
Interface of factory:
```C++
class Factory {
public:
virtual std::unique_ptr<Crew> create(const std::string& name, std::unique_ptr<Weapon>&& weapon, int hp) = 0;
};
```
<!-- .element: class="fragment fade-in" -->
Implementations:
<!-- .element: class="fragment fade-in" -->
```C++
class PirateBuilder : public CrewBuilder {
public:
std::unique_ptr<Crew> create(const std::string& name, std::unique_ptr<Weapon>&& weapon, int hp) override {
return std::make_unique<Pirate>(name, std::move(weapon), hp);
}
};
class MarineBuilder : public CrewBuilder {
public:
std::unique_ptr<Crew> create(const std::string& name, std::unique_ptr<Weapon>&& weapon, int hp) override {
return std::make_unique<Pirate>(name, std::move(weapon), hp);
}
};
```
<!-- .element: class="fragment fade-in" -->
Class that use factory to create object:
<!-- .element: class="fragment fade-in" -->
```C++
class Tawern {
public:
Tawern(std::unique_ptr<Factory>&& factory): _factory(std::move(factory)) {}
std::unique_ptr<Crew> recruit(const std::string& name, std::unique_ptr<Weapon>&& weapon) {
return _factory->create(name, std::move(weapon), 40);
}
private:
std::unique_ptr<Builder> _factory_;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.60em" -->
___
## Exercise 3
* <!-- .element: class="fragment fade-in" --> Go to the directory <code>Ship</code> and create abstract class <code>FruitFactory</code>.
* <!-- .element: class="fragment fade-in" --> Create <code>FruitFactory</code>
* <!-- .element: class="fragment fade-in" --> Create <code>ItemFactory</code>
* <!-- .element: class="fragment fade-in" --> Create <code>AlcoholFactory</code>
* <!-- .element: class="fragment fade-in" --> Test in main.cpp if you can create any type of cargo
* <!-- .element: class="fragment fade-in" --> <b>How to handle scenario, when different cargo takes other arguments?</b>
___
## Different arguments
At the end, I want to talk about different arguments in constructors. We have three derived class from `Cargo`, each class takes different arguments.
```C++
struct Fruit : public Cargo {
int rottenCounter;
Fruit(size_t amount, int rottenCounter);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
struct Item : public Cargo {
enum class Type { Common, Rare, Epic, Legendary };
Type type;
Item(size_t amount, Type type);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
struct Alcohol : public Cargo {
enum class Type { White, Spiced, Dark};
int power;
Type type;
Alcohol(size_t amount, int power, Type type);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.60em" -->
___
## How to solve it?
**Of course by using template!**
<!-- .element: class="fragment fade-in" -->
```C++
template <typename... Args>
class CargoFactory {
public:
virtual std::unique_ptr<Cargo> create(size_t amount, Args... args) = 0;
};
```
<!-- .element: class="fragment fade-in" -->
```C++
class FruitFactory : public CargoFactory<int> {
public:
std::unique_ptr<Cargo> create(size_t amount, int rottenCounter) override {
return std::make_unique<Fruit>(amount, rottenCounter);
}
};
class AlcoholFactory : public CargoFactory<int, Alcohol::Type> {
public:
std::unique_ptr<Cargo> create(size_t amount, int power, Alcohol::Type type) override {
return std::make_unique<Alcohol>(amount, power, type);
}
};
class ItemFactory : public CargoFactory<Item::Type> {
public:
std::unique_ptr<Cargo> create(size_t amount, Item::Type type) override {
return std::make_unique<Item>(amount, type);
}
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.69em" -->

View file

@ -0,0 +1,43 @@
# Cloning and building example project
___
## Setup
* <!-- .element: class="fragment fade-in" --> Clone repository: https://github.com/nauka-programowania-MA/CreatingReliableSoftwareCpp
* <!-- .element: class="fragment fade-in" --> Go to exercises/exampleProject
* <!-- .element: class="fragment fade-in" --> Compile it and run:
* <!-- .element: class="fragment fade-in" --> <code>mkdir build</code>
* <!-- .element: class="fragment fade-in" --> <code>cd build</code>
* <!-- .element: class="fragment fade-in" --> cmake ..
* <!-- .element: class="fragment fade-in" --> make -j4 (where 4 is available threads)
* <!-- .element: class="fragment fade-in" --> ./ExampleProject
* <!-- .element: class="fragment fade-in" --> Should print <code>Hello World</code>
If you don't have linux, you can use <a href="https://replit.com/">replit</a>
<!-- .element: class="fragment fade-in" -->
* <!-- .element: class="fragment fade-in" --> Click Create C++
* <!-- .element: class="fragment fade-in" --> Name it and confirm with button Create Repl.
* <!-- .element: class="fragment fade-in" --> Click three dot on the rigt top screen and click upload folder
* <!-- .element: class="fragment fade-in" --> Now find your directory with repo and upload it
* <!-- .element: class="fragment fade-in" --> Congratulation, you can use linux shell wit sanitizers, cmake and valgridn support :)
<!-- .element: class="fragment fade-in" -->
___
## replit
If you decided to use replit please configure this file: `replit.nix`:
```bash
{ pkgs }: {
deps = [
pkgs.clang_12
pkgs.ccls
pkgs.gdb
pkgs.gnumake
pkgs.vim
pkgs.valgrind
pkgs.cmake
];
}
```

View file

@ -0,0 +1,577 @@
## Decorator
The last design pattern that I mention today is a `decorator`. Before we talk about it, let's extend `Cargo` class and handle a few new functionalities:
* The item may be damaged during a time (we have salty air) so it can lose his value during travelling
* The fruit may rotten, so we also want to lower its price every day
* The alcohol price should be based on it's power
* The value of item should be higher based on its type (common is cheapest but legendary has a big value)
___
## New implementation
Let's start with a new implementation of class `Fruit`. Now we inherit from `TimeObserver` to count days. We also take in account that the price will be lowered every day.
```C++
struct Fruit : public Cargo, public TimeObserver {
static constexpr int MAX_TIME = 20;
static constexpr int BASE_PRICE = 10;
int rottenCounter;
Time* time;
Fruit(Time* time, size_t amount, int rottenCounter)
: Cargo(amount), time(time), rottenCounter(rottenCounter) {
time->attach(this);
}
// Rule of 5
~Fruit() {
time->detach(this);
}
size_t getPrice() const override {
return (rottenCounter * BASE_PRICE) / MAX_TIME;
}
const std::string& name() const override {
static const std::string name = "Banana";
return name;
}
void nextDay() override {
rottenCounter = std::max(0, rottenCounter - 1);
}
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.65em" -->
___
```C++
struct Item : public Cargo, public TimeObserver {
enum class Type { Common = 100,
Rare = 300,
Epic = 1000,
Legendary = 2500 };
static constexpr int MAX_DURABILITY = 100;
Time* time;
Type type;
int durability{MAX_DURABILITY};
Item(Time* time, size_t amount, Type type)
: Cargo(amount), time(time), type(type) {
time->attach(this);
}
// Rule of 5
~Item() {
time->detach(this);
}
size_t getPrice() const override {
return (durability * static_cast<int>(type)) / MAX_DURABILITY;
}
const std::string& name() const override {
static const std::string name = "Item";
return name;
}
void nextDay() override {
durability = std::max(0, durability - 1);
}
};
```
<!-- .slide: style="font-size: 0.57em" -->
___
## Duplication of code
At the end, we also implement a `Alcohol` class. As you can see, we duplicate behavior few times. `Item` and `Fruit` may lose value each day. Price of `Item` and `Alcohol` depend on it's type etc…
```C++
struct Alcohol : public Cargo {
enum class Type { White = 100,
Spiced = 200,
Dark = 300,
Seasoned = 500 };
static constexpr int MAX_POWER = 96;
int power;
Type type;
Alcohol(size_t amount, int power, Type type)
: Cargo(amount), power(power), type(type) {}
size_t getPrice() const override {
return (power * static_cast<int>(type)) / MAX_POWER;
}
const std::string& name() const override {
static const std::string name = "Rum";
return name;
}
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.76em" -->
___
The following code works as we want to. However, whenever we want to change a logic, for instance instead of decrement the counter every day, we want to subtract `durability` and `rottenCounter` every second day. We need to do this in two places. And when we will add more cargo, we will need to do this everywhere. This is not a good example of easy to maintenance code.
```C++
int main() {
Time time;
std::unique_ptr<Cargo> cargo = std::make_unique<Item>(&time, 10, Item::Type::Epic);
for (int i = 1; i <= 15; ++i) {
std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice() << '\n';
++time;
}
}
```
<!-- .element: class="fragment fade-in" -->
```
DAY: 1 | Name: Item | Price: 1000
DAY: 2 | Name: Item | Price: 990
DAY: 3 | Name: Item | Price: 980
DAY: 4 | Name: Item | Price: 970
DAY: 5 | Name: Item | Price: 960
DAY: 6 | Name: Item | Price: 950
DAY: 7 | Name: Item | Price: 940
DAY: 8 | Name: Item | Price: 930
DAY: 9 | Name: Item | Price: 920
DAY: 10 | Name: Item | Price: 910
DAY: 11 | Name: Item | Price: 900
DAY: 12 | Name: Item | Price: 890
DAY: 13 | Name: Item | Price: 880
DAY: 14 | Name: Item | Price: 870
DAY: 15 | Name: Item | Price: 860
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.69em" -->
___
## Drawback
According to one of SOLID rule **O**pen-close, we should write an easy to extend code, which don't require from us modification of already implemented code. Unfortunately, we need to modify each class to append a new logic, like: `decrement durability each day` or `get price based on durability or type`. What's more, we don't want to simulate `rotten of fruit` or lose `durability` by items that are stored in `shops`. So we need to add some `bool` flag like `isInShop` to ignore time elapsing. When the code grows and grows, we need to add more such flags and duplicate more code. This is definitely not the way we should develop a code. A better solution is to make a `Fruit` or `Item` class unaware of time elapsing. We can also make class `item` and `Alcohol` unaware of different types, and it's prices. This will allow to decouple the code, and make it easier to extend.
___
## Decorator
Instead of implement the whole logic to every class, and then make some `bool` flags to omit a few of them. We can use a `decorator` pattern. Let's start from the interface. The `DecoratedCargo` inherit form `Cargo` class and take as an argument `unique_ptr` for class `Cargo`. This interface has also two protected function allowing to get underlying cargo.
```C++
class DecoratedCargo : public Cargo {
public:
explicit DecoratedCargo(std::unique_ptr<Cargo>&& cargo)
: _cargo(std::move(cargo)) {
assert(_cargo);
}
protected:
Cargo& cargo() { return *_cargo; }
const Cargo& cargo() const { return *_cargo; }
private:
std::unique_ptr<Cargo> _cargo;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.93em" -->
___
## Vulnerable
A `Vulnerable` class inherit from `TimeObserver` to simulate time elapsing and from `DecoratedCargo`. When we calculate the price, it will calculate percentage value based on `durability` and `maxDurability`. Price can be manipulated by other decorator, so we call `getPrice`.
```C++
class Vulnerable : public DecoratedCargo, public TimeObserver {
public:
Vulnerable(std::unique_ptr<Cargo>&& cargo, Time* time, int durability, int maxDurability)
: DecoratedCargo(std::move(cargo)), _time(time), _durability(durability), _maxDurability(maxDurability) {
assert(_time);
time->attach(this);
}
// Rule of 5
~Vulnerable() {
_time->detach(this);
}
void nextDay() override {
_durability = std::max(0, _durability - 1);
}
size_t getPrice() const override {
return (_durability * cargo().getPrice()) / _maxDurability;
}
const std::string& name() const override {
return cargo().name();
}
private:
Time* _time;
int _durability;
int _maxDurability;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.64em" -->
___
## Valuable
A `Valuable` class is a template class that take `enum` as a template argument. It will be used later when we calculate a price. We will treat it as a multiplier for already calculated price.
```C++
template <typename ValueType>
class Valuable : public DecoratedCargo {
public:
Valuable(std::unique_ptr<Cargo>&& cargo, ValueType value)
: DecoratedCargo(std::move(cargo)), _value(value) {
}
size_t getPrice() const override {
return cargo().getPrice() * static_cast<int>(_value);
}
const std::string& name() const override {
return cargo().name();
}
private:
ValueType _value;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.82em" -->
___
## Type
Because we want to make `Item` and `Alcohol` and any other type unaware about its type. We move it to the separate files. This allows us to develop new functionality without modification of existing code. For instance, when we decided later that we want to add also a `type` for `Fruit` we will create a new file and that's all! We don't need to recompile anything, and also don't need to modify anything. So even if `Fruit` is in a separate repository, and we can't modify it. We still can add new behavior.
```C++
enum class ItemType { Common = 1,
Rare = 3,
Epic = 10,
Legendary = 25 };
enum class AlcoholType { White = 1,
Spiced = 2,
Dark = 3,
Seasoned = 5 };
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.82em" -->
___
## Usage
Now we can concatenate any decorator with a `Cargo` type, so we can add new functionality `in fly`.
```C++
int main() {
Time time;
std::unique_ptr<Cargo> cargo = std::make_unique<Vulnerable>(
std::make_unique<Valuable<ItemType>>(
std::make_unique<Item>(10), ItemType::Epic),
&time, 100, 100);
for (int i = 1; i <= 15; ++i) {
std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice() << '\n';
++time;
}
}
```
<!-- .element: class="fragment fade-in" -->
```bash
DAY: 1 | Name: Item | Price: 1000
DAY: 2 | Name: Item | Price: 990
DAY: 3 | Name: Item | Price: 980
DAY: 4 | Name: Item | Price: 970
DAY: 5 | Name: Item | Price: 960
DAY: 6 | Name: Item | Price: 950
DAY: 7 | Name: Item | Price: 940
DAY: 8 | Name: Item | Price: 930
DAY: 9 | Name: Item | Price: 920
DAY: 10 | Name: Item | Price: 910
DAY: 11 | Name: Item | Price: 900
DAY: 12 | Name: Item | Price: 890
DAY: 13 | Name: Item | Price: 880
DAY: 14 | Name: Item | Price: 870
DAY: 15 | Name: Item | Price: 860
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.67em" -->
___
## Store
If cargo is inside the store, we just don't add a `Vulnerable` decorator!
```C++
Time time;
std::unique_ptr<Cargo> cargo = std::make_unique<Vulnerable>(
std::make_unique<Valuable<ItemType>>(
std::make_unique<Item>(10), ItemType::Epic),
&time, 100, 100);
std::unique_ptr<Cargo> cargo2 =
std::make_unique<Valuable<ItemType>>(
std::make_unique<Item>(10), ItemType::Epic);
for (int i = 1; i <= 15; ++i) {
std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice();
std::cout << " |-| Name: " << cargo2->name() << " | Price: " << cargo2->getPrice() << '\n';
++time;
}
```
<!-- .element: class="fragment fade-in" -->
```bash
DAY: 1 | Name: Item | Price: 1000 |-| Name: Item | Price: 1000
DAY: 2 | Name: Item | Price: 990 |-| Name: Item | Price: 1000
DAY: 3 | Name: Item | Price: 980 |-| Name: Item | Price: 1000
DAY: 4 | Name: Item | Price: 970 |-| Name: Item | Price: 1000
DAY: 5 | Name: Item | Price: 960 |-| Name: Item | Price: 1000
DAY: 6 | Name: Item | Price: 950 |-| Name: Item | Price: 1000
DAY: 7 | Name: Item | Price: 940 |-| Name: Item | Price: 1000
DAY: 8 | Name: Item | Price: 930 |-| Name: Item | Price: 1000
DAY: 9 | Name: Item | Price: 920 |-| Name: Item | Price: 1000
DAY: 10 | Name: Item | Price: 910 |-| Name: Item | Price: 1000
DAY: 11 | Name: Item | Price: 900 |-| Name: Item | Price: 1000
DAY: 12 | Name: Item | Price: 890 |-| Name: Item | Price: 1000
DAY: 13 | Name: Item | Price: 880 |-| Name: Item | Price: 1000
DAY: 14 | Name: Item | Price: 870 |-| Name: Item | Price: 1000
DAY: 15 | Name: Item | Price: 860 |-| Name: Item | Price: 1000
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.63em" -->
___
## Decorator
The decorator pattern can be used to extend (decorate) the functionality of a certain object statically, or in some cases at run-time, independently of other instances of the same class, provided some groundwork is done at design time. This is achieved by designing a new Decorator class that wraps the original class.
<img data-src="images/decorator_uml.png" alt="Decorator UML">
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.82em" -->
___
Decorator also helps with **D**ependency inversion. Because `decorator` classes are in low-lvl, so we can easily add a new decorator without changing anything in higher layers.
<img data-src="images/decorator_cargo__uml.png" alt="Decorator Cargo UML" height="600px">
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.76em" -->
___
## Drawbacks
Decorator is powerfull desing pattenr, but as everything it also has some drawbacks.
* <!-- .element: class="fragment fade-in" --> The main problem is that we need to use plenty of `unique_ptr` this will use more memory and also will be slower, because we need to call a few virtual functions.
* <!-- .element: class="fragment fade-in" --> If we add to many decorators, the code became hard to read
* <!-- .element: class="fragment fade-in" --> Debugging code containing decorators also may be more difficult, because we need to jump between a few of the same methods (but located in different decorators) before we reach the final result. So find a bug will be harder
* <!-- .element: class="fragment fade-in" --> If the result of one decorator has an impact on another, we may cause a hard to spot bug when we swap the order. So we shouldn't create a decorator, which should be stored in order, because a risk of concatenated them wrong is high.
___
## Different approach
As one popular proverb says: "Time is money". And if our code must be as fast as possible, and we can't afford a price, that few decorators means calling of few virtual functions, which also make the code much harder to optimize by compiler. We need to rising with decorator. I have good news for you: No, you don't need to! We have two types of polymorphism, so instead of using a `dynamic` one, let's use a `static`.
___
## Get rid of virtual
First, the class `Cargo` is no longer virtual. We don't need to implement rule of 5 and create a virtual destructor. We move constructors to `protected` section, to make it create only by derived classes (sth similar to abstract class, which we can't create).
```C++
struct Cargo {
size_t amount;
auto operator<=>(const Cargo&) const = default;
protected:
// Only derived class can create it
Cargo(size_t amount)
: amount(amount) {}
Cargo() = default;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.96em" -->
___
Next, we also remove all virtual functions from derived classes.
```C++
struct Fruit : public Cargo {
static constexpr int BASE_PRICE = 10;
Fruit(size_t amount)
: Cargo(amount) {}
size_t getPrice() const {
return BASE_PRICE;
}
const std::string& name() const {
static const std::string name = "Banana";
return name;
}
};
```
<!-- .element: class="fragment fade-in" -->
```C++
struct Item : public Cargo {
static constexpr int BASE_PRICE = 100;
Item(size_t amount)
: Cargo(amount) {}
size_t getPrice() const {
return BASE_PRICE;
}
const std::string& name() const {
static const std::string name = "Item";
return name;
}
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.66em" -->
___
We don't need to have a base class for decorator. We can move directly to implementation.
```C++
template <typename Neasted>
class Vulnerable : public TimeObserver {
public:
Vulnerable(Neasted neasted, Time* time, int durability, int maxDurability)
: _neasted(neasted), _time(time), _durability(durability), _maxDurability(maxDurability) {
assert(_time);
time->attach(this);
}
// Rule of 5
~Vulnerable() { _time->detach(this);}
void nextDay() override {
_durability = std::max(0, _durability - 1);
}
size_t getPrice() const {
return (_durability * _neasted.getPrice()) / _maxDurability;
}
const std::string& name() const {
return _neasted.name();
}
private:
Neasted _neasted;
Time* _time;
int _durability;
int _maxDurability;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.62em" -->
___
As you can see, we have even less code, and we avoid a lot of virtual functions. If we decide that an object is too heavy to copy, we can pass a pointer. The main bottleneck of decorator which slows down the code was calling a lot of virtual functions. Without virtual functions, our code is much faster.
```C++
template <typename Neasted, typename ValueType>
class Valuable {
public:
Valuable(Neasted neasted, ValueType value)
: _neasted(neasted), _value(value) {
}
size_t getPrice() const {
return _neasted.getPrice() * static_cast<int>(_value);
}
const std::string& name() const {
return _neasted.name();
}
private:
Neasted _neasted;
ValueType _value;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.72em" -->
___
Even usage seems more intuitive, because we don't need to create a lot of `unique_ptr` even for small objects.
```C++
int main() {
Time time;
auto cargo = Vulnerable(Valuable(Item(10), ItemType::Epic), &time, 100, 100);
auto cargo2 = Valuable(Item(10), ItemType::Epic);
for (int i = 1; i <= 15; ++i) {
std::cout << "DAY: " << i << " | Name: " << cargo.name() << " | Price: " << cargo.getPrice();
std::cout << " |-| Name: " << cargo2.name() << " | Price: " << cargo2.getPrice() << '\n';
++time;
}
}
```
<!-- .element: class="fragment fade-in" -->
```bash
DAY: 1 | Name: Item | Price: 1000 |-| Name: Item | Price: 1000
DAY: 2 | Name: Item | Price: 990 |-| Name: Item | Price: 1000
DAY: 3 | Name: Item | Price: 980 |-| Name: Item | Price: 1000
DAY: 4 | Name: Item | Price: 970 |-| Name: Item | Price: 1000
DAY: 5 | Name: Item | Price: 960 |-| Name: Item | Price: 1000
DAY: 6 | Name: Item | Price: 950 |-| Name: Item | Price: 1000
DAY: 7 | Name: Item | Price: 940 |-| Name: Item | Price: 1000
DAY: 8 | Name: Item | Price: 930 |-| Name: Item | Price: 1000
DAY: 9 | Name: Item | Price: 920 |-| Name: Item | Price: 1000
DAY: 10 | Name: Item | Price: 910 |-| Name: Item | Price: 1000
DAY: 11 | Name: Item | Price: 900 |-| Name: Item | Price: 1000
DAY: 12 | Name: Item | Price: 890 |-| Name: Item | Price: 1000
DAY: 13 | Name: Item | Price: 880 |-| Name: Item | Price: 1000
DAY: 14 | Name: Item | Price: 870 |-| Name: Item | Price: 1000
DAY: 15 | Name: Item | Price: 860 |-| Name: Item | Price: 1000
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.72em" -->
___
## Ecerice 1
* <!-- .element: class="fragment fade-in" --> Go into directory <code>Ship</code> and implement <code>DecoratedCargo</code>:
* <!-- .element: class="fragment fade-in" --> It should take a <code>std::unique_ptr&ltCargo&gt</code> and has protected getter to this field.
* <!-- .element: class="fragment fade-in" --> Implement <code>Vunreable</code> class:
* <!-- .element: class="fragment fade-in" --> It should inherit from <code>TimeObserver</code> and <code>DecoratedCargo</code>
* <!-- .element: class="fragment fade-in" --> It should take additional parameters in C'tor: <code>Time</code>, <code>durability</code> and <code>maxDurability</code>
* <!-- .element: class="fragment fade-in" --> It should simulate time elapsing. Every day should subtract durability
* <!-- .element: class="fragment fade-in" --> It should return price back on current durability of cargo
___
## Exercise 2
* <!-- .element: class="fragment fade-in" --> Implement <code>Valuable</code> class:
* <!-- .element: class="fragment fade-in" --> It should inherit from <code>DecoratedCargo</code>
* <!-- .element: class="fragment fade-in" --> it should be a template that takes <code>enum</code> type of value
* <!-- .element: class="fragment fade-in" --> It should return value based on type (cast it to enum and multiply by current value)
* <!-- .element: class="fragment fade-in" --> Try to compile code form <code>main.cpp</code>
* <!-- .element: class="fragment fade-in" --> You may have some trouble with running code if you don't write a code carefully :) think where is a problem.

View file

@ -0,0 +1 @@
# Design patterns

View file

@ -0,0 +1,204 @@
#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <set>
#include <vector>
struct Time {};
template <typename T, typename U>
struct DifficultStrategy {};
struct PrintVisitor {};
class Ship {
public:
Ship& setTime(Time* time) {
_time = time;
return *this;
}
Ship& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_strategy = std::move(strategy);
return *this;
}
Ship& setName(const std::string& name) {
_name = name;
return *this;
}
Ship& setCapacity(int capacity) {
_capacity = capacity;
return *this;
}
Ship& setMaxCrew(int maxCrew) {
_maxCrew = maxCrew;
return *this;
}
Ship& setCrew(int crew) {
_crew = crew;
return *this;
}
Ship& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
visitor = std::move(_visitor);
return *this;
}
Ship& setArmor(int armor) {
_armor = armor;
return *this;
}
Ship& setMaxArmor(int maxArmor) {
_maxArmor = maxArmor;
return *this;
}
Ship& setCannons(int cannons) {
_canons = cannons;
return *this;
}
Ship& setMaxCannons(int maxCannons) {
_maxCannons = maxCannons;
return *this;
}
Ship& setDurability(int durability) {
_durability = durability;
return *this;
}
Ship& setMaxDurability(int maxDurability) {
_maxDurability = maxDurability;
return *this;
}
const Time* time() const { return _time; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int maxCrew() const { return _maxCrew; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int maxArmor() const { return _maxArmor; }
int canons() const { return _canons; }
int maxCannons() const { return _maxCannons; }
int durability() const { return _durability; }
int maxDurability() const { return _maxDurability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr<Ship> std::make_unique<Ship>();
Ship() = default;
Time* _time{};
std::unique_ptr<DifficultStrategy<Ship, bool>> _strategy{};
std::string _name{};
int _capacity{-1};
int _maxCrew{-1};
int _crew{10};
std::unique_ptr<PrintVisitor> _visitor{};
int _armor{0};
int _maxArmor{-1};
int _canons{0};
int _maxCannons{-1};
int _durability{100};
int _maxDurability{-1};
};
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique<Ship>()) {}
[[nodiscard]] std::unique_ptr<Ship> build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->time() ||
_ship->name().empty() ||
_ship->capacity() == -1 ||
_ship->maxCrew() == -1 ||
_ship->maxArmor() == -1 ||
_ship->maxCannons() == -1 ||
_ship->maxDurability() == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
ShipBuilder& setTime(Time* time) {
_ship->setTime(time);
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_ship->setStrategy(std::move(strategy));
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->setName(name);
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->setCapacity(capacity);
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->setMaxCrew(maxCrew);
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->setCrew(crew);
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
_ship->setVisitor(std::move(visitor));
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->setArmor(armor);
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->setMaxArmor(maxArmor);
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->setCannons(cannons);
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->setMaxCannons(maxCannons);
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->setDurability(durability);
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->setMaxDurability(maxDurability);
return *this;
}
private:
std::unique_ptr<Ship> _ship;
};
int main() {
auto ship = ShipBuilder().build();
if (ship) {
std::cout << "Ship name: " << ship->name() << '\n';
}
Time time;
auto ship2 = ShipBuilder()
.setTime(&time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.build();
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
}

View file

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.16)
project(SHM LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
enable_testing()
add_subdirectory(src)
add_subdirectory(tests)
add_executable(${PROJECT_NAME} main.cpp)
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
target_link_libraries(${PROJECT_NAME}
Battle
Ship
Store
Core
Player
nlohmann_json::nlohmann_json
)

View file

@ -0,0 +1,65 @@
#include <functional>
#include <iostream>
#include <memory>
#include "Battle/BattleField.h"
#include "Core/BasicPrintVisitor.h"
#include "Core/PrettyPrintVisitor.h"
#include "Core/Time.h"
#include "Player/Enemy.h"
#include "Player/EnemyEasyDifficultLvlStrategy.h"
#include "Player/EnemyHardDifficultLvlStrategy.h"
#include "Player/RealPlayer.h"
#include "Ship/AlcoholFactory.h"
#include "Ship/Cargo.h"
#include "Ship/CargoDamageVisitor.h"
#include "Ship/FruitFactory.h"
#include "Ship/ItemFactory.h"
#include "Ship/Ship.h"
#include "Ship/ShipBuilder.h"
#include "Ship/ShipEasyDifficultLvlStrategy.h"
#include "Ship/ShipHardDifficultLvlStrategy.h"
#include "Ship/ShipJsonBuilder.h"
#include "Store/Store.h"
int main() {
Time time;
auto ship = ShipBuilder()
.setTime(&time)
.setName("Black Widow")
.setCapacity(1000)
.setCrew(40)
.setStrategy(std::make_unique<ShipHardDifficultLvlStrategy>())
.setVisitor(std::make_unique<PrettyPrintVisitor>())
.setArmor(100)
.setDurability(1000)
.build();
ship->load(AlcoholFactory().create(300, 40, Alcohol::Type::Spiced));
ship->load(FruitFactory().create(200, 15));
ship->load(FruitFactory().create(150, 15));
ship->load(ItemFactory().create(250, Item::Type::Epic));
ship->printCargo();
RealPlayer user("Mateusz", std::move(ship));
Enemy enemy("Enemy1",
ShipJsonBuilder("../ships.json").buildGaleon(&time, "Queens Anne revenge"),
std::make_unique<EnemyHardDifficultLvlStrategy>(),
CargoDamageVisitor{});
BattleField battefield{&user, &enemy};
while (true) {
for (auto* player : battefield.players()) {
std::cout << "-------------------------------------------------------------------\n";
std::cout << "Player HP: " << user.getShip().durability() << " ARMOR: " << user.getShip().armor() << " | ";
std::cout << "Enemy HP: " << enemy.getShip().durability() << " ARMOR: " << enemy.getShip().armor() << '\n';
std::cout << "-------------------------------------------------------------------\n";
for (size_t i = 0; i < 3; ++i) {
const auto status = player->makeAction(battefield);
if (status == Action::Status::Escaped || status == Action::Status::Defeated) {
return 0;
}
}
}
}
}

View file

@ -0,0 +1,20 @@
{
"brige":{
"capacity":1000,
"crew":40,
"durability":1000,
"armor":100
},
"frigate":{
"capacity":1500,
"crew":70,
"durability":2000,
"armor":500
},
"galeon":{
"capacity":2500,
"crew":120,
"durability":3000,
"armor":1000
}
}

View file

@ -0,0 +1,8 @@
add_library(Battle src/Defense.cpp src/BattleField.cpp src/Attack.cpp src/Escape.cpp)
target_include_directories(Battle PUBLIC include)
target_link_libraries(Battle
Ship
Player
)

View file

@ -0,0 +1,24 @@
#pragma once
class Player;
class Ship;
class Action {
public:
// As you can see keeping type here, require modify a base class whenever we add a new class, this volatile an open-close principle.
// We should be able to extend code without modification already implemented one
enum class Type {
Attack,
Defense,
Escape
};
enum class Status {
Escaped,
Defeated,
Nothing,
};
virtual Status operator()(Player* player, Ship* enemyShip) = 0;
virtual Type type() const = 0;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "Battle/Action.h"
class Player;
class Ship;
class Attack : public Action {
public:
Status operator()(Player* player, Ship* enemyShip) override;
Type type() const override { return Action::Type::Attack; }
};

View file

@ -0,0 +1,19 @@
#pragma once
#include <vector>
class Ship;
class Player;
class BattleField {
public:
BattleField(Player* player, Player* enemy);
Ship* getPlayerShip() const;
Ship* getEnemyShip() const;
std::vector<Player*> players() const;
private:
Player* _player;
Player* _enemy;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "Battle/Action.h"
class Player;
class Ship;
class Defense : public Action {
public:
Status operator()(Player* player, Ship* enemyShip) override;
Type type() const override { return Type::Defense; }
};

View file

@ -0,0 +1,18 @@
#pragma once
#include "Battle/Action.h"
#include <random>
class Player;
class Ship;
class Escape : public Action {
public:
Status operator()(Player* player, Ship* enemyShip) override;
Type type() const override { return Action::Type::Escape; }
private:
static std::random_device _rd;
std::mt19937 _seed{_rd()};
};

View file

@ -0,0 +1,22 @@
#include "Battle/Attack.h"
#include <Player/Player.h>
#include <Ship/Ship.h>
#include <iostream>
Action::Status Attack::operator()(Player* player, Ship* enemyShip) {
const int damage = player->attack(*enemyShip);
std::cout << "Player ship: " << player->getShip().name() << " attack ship: " << enemyShip->name() << '\n';
if (damage) {
std::cout << "Dealed damage: " << damage << '\n';
} else {
std::cout << "Missed\n";
}
if (enemyShip->durability() <= 0) {
return Status::Defeated;
}
return Status::Nothing;
}

View file

@ -0,0 +1,16 @@
#include "Battle/BattleField.h"
#include <Player/Player.h>
BattleField::BattleField(Player* player, Player* enemy)
: _player{player}, _enemy{enemy} {}
Ship* BattleField::getPlayerShip() const {
return &_player->getShip();
}
Ship* BattleField::getEnemyShip() const {
return &_enemy->getShip();
}
std::vector<Player*> BattleField::players() const {
return {_player, _enemy};
}

View file

@ -0,0 +1,13 @@
#include "Battle/Defense.h"
#include <Player/Player.h>
#include <Ship/Ship.h>
#include <iostream>
Action::Status Defense::operator()(Player* player, Ship*) {
player->getShip().increaseArmor(50);
std::cout << "Player ship: " << player->getShip().name() << " defense\n";
return Status::Nothing;
}

View file

@ -0,0 +1,22 @@
#include "Battle/Escape.h"
#include <Player/Player.h>
#include <Ship/Ship.h>
#include <iostream>
std::random_device Escape::_rd{};
Action::Status Escape::operator()(Player* player, Ship*) {
std::cout << "Player ship: " << player->getShip().name() << " try to escape!\n";
std::uniform_int_distribution<int> dice(0, 20);
const auto res = dice(_seed);
if (res > 12) {
std::cout << "Player escaped!\n";
return Status::Escaped;
}
std::cout << "Escape maneuver failed!\n";
return Status::Nothing;
}

View file

@ -0,0 +1,5 @@
add_subdirectory(Battle)
add_subdirectory(Core)
add_subdirectory(Player)
add_subdirectory(Ship)
add_subdirectory(Store)

View file

@ -0,0 +1,8 @@
add_library(Core src/Time.cpp src/BasicPrintVisitor.cpp src/PrettyPrintVisitor.cpp)
target_include_directories(Core PUBLIC include)
target_link_libraries(Core
Ship
Store
)

View file

@ -0,0 +1,12 @@
#pragma once
#include "Core/PrintVisitor.h"
class Store;
class Ship;
class BasicPrintVisitor : public PrintVisitor {
public:
void visit(const Store&) const override;
void visit(const Ship&) const override;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include <memory>
#include <string>
#include <set>
#include <utility>
template <typename T, typename Res>
class DifficultLvlStrategy {
public:
virtual Res handle(T&) const = 0;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "Core/PrintVisitor.h"
class Store;
class Ship;
class PrettyPrintVisitor : public PrintVisitor {
public:
void visit(const Store&) const override;
void visit(const Ship&) const override;
};

View file

@ -0,0 +1,11 @@
#pragma once
class Store;
class Ship;
class PrintVisitor {
public:
virtual ~PrintVisitor() = default;
virtual void visit(const Store&) const = 0;
virtual void visit(const Ship&) const = 0;
};

View file

@ -0,0 +1,27 @@
#pragma once
class TimeObserver;
#include <memory>
#include <string>
#include <set>
#include <utility>
class Time {
public:
enum class State {
Closing,
FocusChanged
};
void attach(TimeObserver* observer);
void detach(TimeObserver* observer);
Time& operator++();
size_t day() const { return _day; }
private:
void notify() const;
size_t _day{0};
std::set<TimeObserver*> _observers;
};

View file

@ -0,0 +1,6 @@
#pragma once
struct TimeObserver {
virtual ~TimeObserver() = default;
virtual void nextDay() = 0;
};

View file

@ -0,0 +1,22 @@
#include <Core/BasicPrintVisitor.h>
#include <Ship/Ship.h>
#include <Store/Store.h>
#include <iomanip>
#include <iostream>
void BasicPrintVisitor::visit(const Store& store) const {
const auto& cargoes = store.cargoes();
for (const auto& cargo : cargoes) {
std::cout << std::setw(15) << std::left << cargo->name() << std::setw(15) << cargo->amount << std::setw(15) << cargo->getPrice() << "\n";
}
}
void BasicPrintVisitor::visit(const Ship& ship) const {
const auto& cargoes = ship.cargoes();
for (const auto& cargo : cargoes) {
std::cout << std::setw(15) << std::left << cargo->name() << std::setw(15) << cargo->amount << "\n";
}
}

View file

@ -0,0 +1,33 @@
#include <Core/PrettyPrintVisitor.h>
#include <Ship/Ship.h>
#include <Store/Store.h>
#include <iomanip>
#include <iostream>
void PrettyPrintVisitor::visit(const Store& store) const {
const auto& cargoes = store.cargoes();
std::cout << "|----------------|----------------|----------------|\n";
std::cout << "| NAME "
<< "| AMOUNT "
<< "| PRICE |" << '\n';
std::cout << "|----------------|----------------|----------------|\n";
for (const auto& cargo : cargoes) {
std::cout << "|" << std::setw(15) << std::left << cargo->name() << " | " << std::setw(15) << cargo->amount << "| " << std::setw(15) << cargo->getPrice() << "|\n";
}
std::cout << "|----------------|----------------|----------------|\n";
}
void PrettyPrintVisitor::visit(const Ship& ship) const {
const auto& cargoes = ship.cargoes();
std::cout << "|----------------|----------------|\n";
std::cout << "| NAME "
<< "| AMOUNT |" << '\n';
std::cout << "|----------------|----------------|\n";
for (const auto& cargo : cargoes) {
std::cout << "|" << std::setw(15) << std::left << cargo->name() << " | " << std::setw(15) << cargo->amount << "|\n";
}
std::cout << "|----------------|----------------|\n";
}

View file

@ -0,0 +1,24 @@
#include "Core/Time.h"
#include "Core/TimeObserver.h"
#include <algorithm>
#include <functional>
void Time::attach(TimeObserver* observer) {
_observers.emplace(observer);
}
void Time::detach(TimeObserver* observer) {
_observers.erase(observer);
}
Time& Time::operator++() {
++_day;
notify();
return *this;
}
void Time::notify() const {
std::ranges::for_each(_observers, std::mem_fn(&TimeObserver::nextDay));
}

View file

@ -0,0 +1,9 @@
add_library(Player src/EnemyEasyDifficultLvlStrategy.cpp src/EnemyHardDifficultLvlStrategy.cpp src/Player.cpp src/RealPlayer.cpp)
target_include_directories(Player PUBLIC include)
target_link_libraries(Player
Battle
Core
Ship
)

View file

@ -0,0 +1,66 @@
#pragma once
#include "Player/Player.h"
#include <Battle/Attack.h>
#include <Battle/BattleField.h>
#include <Battle/Defense.h>
#include <Core/DifficultLvlStrategy.h>
#include <Ship/Ship.h>
#include <memory>
#include <string>
template <typename DamageVisitor>
class Enemy : public Player {
public:
Enemy(const std::string& name, std::unique_ptr<Ship>&& ship, std::unique_ptr<DifficultLvlStrategy<Ship, int>>&& strategy, DamageVisitor visitor);
int attack(Ship& playerShip) override;
Ship& getShip() { return *_ship; }
const Ship& getShip() const { return *_ship; }
protected:
Ship* chooseEnemyShip(const BattleField& battleField) const override final;
std::unique_ptr<Action> chooseAction(const BattleField& battleField) const override final;
private:
std::string _name;
std::unique_ptr<Ship> _ship;
std::unique_ptr<DifficultLvlStrategy<Ship, int>> _strategy;
DamageVisitor _damageVisitor;
};
template <typename DamageVisitor>
Enemy<DamageVisitor>::Enemy(const std::string& name, std::unique_ptr<Ship>&& ship, std::unique_ptr<DifficultLvlStrategy<Ship, int>>&& strategy, DamageVisitor visitor)
: _name(name), _ship(std::move(ship)), _strategy(std::move(strategy)), _damageVisitor(visitor) {}
template <typename DamageVisitor>
int Enemy<DamageVisitor>::attack(Ship& playerShip) {
if (const auto damage = _strategy->handle(playerShip)) {
for (auto& cargo : playerShip.cargoes()) {
cargo->accept(_damageVisitor);
}
return damage;
}
return 0;
}
template <typename DamageVisitor>
Ship* Enemy<DamageVisitor>::chooseEnemyShip(const BattleField& battleField) const {
// To simplify palyer and enemy has one ship
return battleField.getPlayerShip();
}
template <typename DamageVisitor>
std::unique_ptr<Action> Enemy<DamageVisitor>::chooseAction(const BattleField& battleField) const {
static bool flag = true;
if (flag) {
flag = !flag;
return std::make_unique<Attack>();
}
flag = !flag;
return std::make_unique<Defense>();
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
#include <random>
class Ship;
class EnemyEasyDifficultLvlStrategy : public DifficultLvlStrategy<Ship, int> {
public:
EnemyEasyDifficultLvlStrategy();
int handle(Ship& ship) const override;
private:
static std::random_device _rd;
mutable std::mt19937 _seed;
};

View file

@ -0,0 +1,17 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
#include <random>
class Ship;
class EnemyHardDifficultLvlStrategy : public DifficultLvlStrategy<Ship, int> {
public:
EnemyHardDifficultLvlStrategy();
int handle(Ship& ship) const override;
private:
static std::random_device _rd;
mutable std::mt19937 _seed;
};

View file

@ -0,0 +1,22 @@
#pragma once
#include <memory>
#include <Battle/Action.h>
class BattleField;
class Ship;
class Player {
public:
virtual ~Player() = default;
virtual int attack(Ship& playerShip) = 0;
virtual Ship& getShip() = 0;
virtual const Ship& getShip() const = 0;
Action::Status makeAction(const BattleField& battleField);
protected:
virtual Ship* chooseEnemyShip(const BattleField& battleField) const = 0;
virtual std::unique_ptr<Action> chooseAction(const BattleField& battleField) const = 0;
};

View file

@ -0,0 +1,26 @@
#pragma once
#include "Player/Player.h"
#include <memory>
#include <string>
#include <random>
class RealPlayer : public Player {
public:
RealPlayer(const std::string& name, std::unique_ptr<Ship>&& ship);
int attack(Ship& playerShip) override;
Ship& getShip() { return *_ship; }
const Ship& getShip() const { return *_ship; }
protected:
Ship* chooseEnemyShip(const BattleField& battleField) const override final;
std::unique_ptr<Action> chooseAction(const BattleField& battleField) const override final;
private:
std::string _name;
std::unique_ptr<Ship> _ship;
static std::random_device _rd;
std::mt19937 _seed{_rd()};
};

View file

@ -0,0 +1,28 @@
#include "Player/EnemyEasyDifficultLvlStrategy.h"
#include "Ship/Ship.h"
std::random_device EnemyEasyDifficultLvlStrategy::_rd{};
EnemyEasyDifficultLvlStrategy::EnemyEasyDifficultLvlStrategy()
: _seed(_rd()) {}
int EnemyEasyDifficultLvlStrategy::handle(Ship& ship) const {
std::uniform_int_distribution<int> dice(1, 20);
int multiplier = 1;
const int res = dice(_seed);
if (res < 5) {
// miss
return 0;
} else if (res > 19) {
// criticall
multiplier = 2;
}
std::uniform_int_distribution<int> damage(25, 50);
const int total = damage(_seed) * multiplier;
ship.takeDamage(total);
return total;
}

View file

@ -0,0 +1,31 @@
#include "Player/EnemyHardDifficultLvlStrategy.h"
#include "Ship/Ship.h"
std::random_device EnemyHardDifficultLvlStrategy::_rd{};
EnemyHardDifficultLvlStrategy::EnemyHardDifficultLvlStrategy()
: _seed(_rd()) {}
int EnemyHardDifficultLvlStrategy::handle(Ship& ship) const {
std::uniform_int_distribution<int> dice(1, 20);
int multiplier = 1;
const int res = dice(_seed);
if (res < 3) {
// miss
return 0;
} else if (res == 20) {
// turbo critical
multiplier = 3;
} else if (res > 15) {
// criticall
multiplier = 2;
}
std::uniform_int_distribution<int> damage(30, 60);
const int total = damage(_seed) * multiplier;
ship.takeDamage(total);
return total;
}

View file

@ -0,0 +1,11 @@
#include "Player/Player.h"
Action::Status Player::makeAction(const BattleField& battleField) {
std::unique_ptr<Action> action = chooseAction(battleField);
if (action->type() == Action::Type::Attack) {
Ship* enemyShip = chooseEnemyShip(battleField);
return (*action)(this, enemyShip);
}
return (*action)(this, nullptr);
}

View file

@ -0,0 +1,56 @@
#include "Player/RealPlayer.h"
#include <Battle/Attack.h>
#include <Battle/BattleField.h>
#include <Battle/Defense.h>
#include <Battle/Escape.h>
#include <Ship/Ship.h>
#include <iostream>
std::random_device RealPlayer::_rd{};
RealPlayer::RealPlayer(const std::string& name, std::unique_ptr<Ship>&& ship)
: _name(name), _ship(std::move(ship)) {}
int RealPlayer::attack(Ship& playerShip) {
std::uniform_int_distribution<int> dice(0, 20);
const int res = dice(_seed);
int input = 0;
int damage = 0;
std::cout << "Choose Ammo: 1) Round Shot 2) Chain Shot 3) Grape Shot: ";
std::cin >> input;
if (input == 1 && res > 4) {
damage = 50;
} else if (input == 2 && res > 7) {
damage = 80;
} else if (input == 3 && res > 10) {
damage = 100;
} else {
return 0;
}
playerShip.takeDamage(damage);
return damage;
}
Ship* RealPlayer::chooseEnemyShip(const BattleField& battleField) const {
// To simplify palyer and enemy has one ship
return battleField.getEnemyShip();
}
std::unique_ptr<Action> RealPlayer::chooseAction(const BattleField& battleField) const {
int input = 0;
std::cout << "Choose Action: 1) Attack 2) Defense 3) Escape: ";
std::cin >> input;
if (input == 1) {
return std::make_unique<Attack>();
}
if (input == 3) {
return std::make_unique<Escape>();
}
return std::make_unique<Defense>();
}

View file

@ -0,0 +1,15 @@
add_library(Ship
src/Ship.cpp
src/Cargo.cpp
src/ShipEasyDifficultLvlStrategy.cpp
src/ShipHardDifficultLvlStrategy.cpp
src/CargoDamageVisitor.cpp
src/ShipBuilder.cpp
src/ShipJsonBuilder.cpp)
target_include_directories(Ship PUBLIC include)
target_link_libraries(Ship
Core
nlohmann_json::nlohmann_json
)

View file

@ -0,0 +1,14 @@
#pragma once
#include <cstddef>
#include <memory>
#include <Ship/Cargo.h>
#include <Ship/CargoFactory.h>
class AlcoholFactory : public CargoFactory<int, Alcohol::Type> {
public:
std::unique_ptr<Cargo> create(size_t amount, int power, Alcohol::Type type) override {
return std::make_unique<Alcohol>(amount, power, type);
}
};

View file

@ -0,0 +1,59 @@
#pragma once
#include <cstddef>
#include <string>
#include "Ship/CargoDamageVisitor.h"
#include <Core/DifficultLvlStrategy.h>
struct Cargo {
size_t amount;
Cargo(size_t amount);
virtual ~Cargo() = default;
Cargo(const Cargo&) = default;
Cargo(Cargo&&) = default;
Cargo& operator=(const Cargo&) = default;
Cargo& operator=(Cargo&&) = default;
auto operator<=>(const Cargo&) const = default;
virtual size_t getPrice() const = 0;
virtual const std::string& name() const = 0;
virtual void accept(const CargoDamageVisitor& visitor) = 0;
};
struct Fruit : public Cargo {
int rottenCounter;
Fruit(size_t amount, int rottenCounter);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
struct Item : public Cargo {
enum class Type { Common, Rare, Epic, Legendary };
Type type;
Item(size_t amount, Type type);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
struct Alcohol : public Cargo {
enum class Type { White, Spiced, Dark};
int power;
Type type;
Alcohol(size_t amount, int power, Type type);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};

View file

@ -0,0 +1,25 @@
#pragma once
#include <random>
class Fruit;
class Alcohol;
class Item;
class CargoDamageVisitor {
public:
CargoDamageVisitor() = default;
virtual ~CargoDamageVisitor() = default;
CargoDamageVisitor(const CargoDamageVisitor&) = default;
CargoDamageVisitor(CargoDamageVisitor&&) = default;
CargoDamageVisitor& operator=(const CargoDamageVisitor&) = default;
CargoDamageVisitor& operator=(CargoDamageVisitor&&) = default;
virtual void operator()(Fruit& fruit) const;
virtual void operator()(Alcohol& alcohol) const;
virtual void operator()(Item& item) const;
private:
static std::random_device _rd;
mutable std::mt19937 _seed{_rd()};
};

View file

@ -0,0 +1,12 @@
#pragma once
#include <cstddef>
#include <memory>
class Cargo;
template <typename... Args>
class CargoFactory {
public:
virtual std::unique_ptr<Cargo> create(size_t amount, Args... args) = 0;
};

View file

@ -0,0 +1,14 @@
#pragma once
#include <cstddef>
#include <memory>
#include <Ship/CargoFactory.h>
#include <Ship/Cargo.h>
class FruitFactory : public CargoFactory<int> {
public:
std::unique_ptr<Cargo> create(size_t amount, int rottenCounter) override {
return std::make_unique<Fruit>(amount, rottenCounter);
}
};

View file

@ -0,0 +1,14 @@
#pragma once
#include <cstddef>
#include <memory>
#include <Ship/Cargo.h>
#include <Ship/CargoFactory.h>
class ItemFactory : public CargoFactory<Item::Type> {
public:
std::unique_ptr<Cargo> create(size_t amount, Item::Type type) override {
return std::make_unique<Item>(amount, type);
}
};

View file

@ -0,0 +1,70 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <Core/DifficultLvlStrategy.h>
#include <Core/TimeObserver.h>
#include "Ship/Cargo.h"
class PrintVisitor;
class Time;
class Ship : public TimeObserver {
public:
enum class StatusCode {
OK,
MissingCargo,
LackOfSpace
};
~Ship();
Ship(const Ship&) = default;
Ship(Ship&&) = default;
Ship& operator=(const Ship&) = default;
Ship& operator=(Ship&&) = default;
// Override from TimeObserver
void nextDay() override;
std::string& name() { return _name; }
const std::string& name() const { return _name; }
int crew() const { return _crew; }
void printCargo() const;
Cargo* load(std::unique_ptr<Cargo>&& cargo, StatusCode& code) noexcept;
void unload(const std::string& name, int amount, StatusCode& code) noexcept;
// May throw
Cargo* load(std::unique_ptr<Cargo>&& cargo);
void unload(const std::string& name, int amount);
void takeDamage(int damage);
const std::vector<std::unique_ptr<Cargo>>& cargoes() const;
void increaseArmor(int armor) { _armor += armor; }
int durability() const { return _durability; }
int armor() const { return _armor; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr<Ship> std::make_unique<Ship>();
friend class ShipBuilder;
Ship() = default;
void initialize();
bool unloadCargo(const std::string& cargoName, int amount);
void rebel();
Time* _time;
std::unique_ptr<DifficultLvlStrategy<Ship, bool>> _strategy;
std::string _name;
int _capacity;
int _crew;
int _durability{1000};
int _armor{0};
std::vector<std::unique_ptr<Cargo>> _cargoes;
std::unique_ptr<PrintVisitor> _visitor;
};

View file

@ -0,0 +1,28 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
#include <Ship/Ship.h>
#include <memory>
class PrintVisitor;
class Time;
class ShipBuilder {
public:
ShipBuilder();
[[nodiscard]] std::unique_ptr<Ship> build();
ShipBuilder& setTime(Time* time);
ShipBuilder& setStrategy(std::unique_ptr<DifficultLvlStrategy<Ship, bool>> strategy);
ShipBuilder& setName(const std::string& name);
ShipBuilder& setCapacity(int capacity);
ShipBuilder& setCrew(int crew);
ShipBuilder& setVisitor(std::unique_ptr<PrintVisitor> visitor);
ShipBuilder& setArmor(int armor);
ShipBuilder& setDurability(int durability);
private:
std::unique_ptr<Ship> _ship;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
class Ship;
class ShipEasyDifficultLvlStrategy : public DifficultLvlStrategy<Ship, bool> {
public:
bool handle(Ship& ship) const override;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
class Ship;
class ShipHardDifficultLvlStrategy : public DifficultLvlStrategy<Ship, bool> {
public:
bool handle(Ship& ship) const override;
};

View file

@ -0,0 +1,26 @@
#pragma once
#include <nlohmann/json.hpp>
#include <filesystem>
#include "Ship/ShipBuilder.h"
using json = nlohmann::json;
class Time;
class Ship;
class ShipJsonBuilder : private ShipBuilder {
public:
ShipJsonBuilder(const std::filesystem::path& path);
std::unique_ptr<Ship> buildBrig(Time* time, const std::string& name);
std::unique_ptr<Ship> buildFrigate(Time* time, const std::string& name);
std::unique_ptr<Ship> buildGaleon(Time* time, const std::string& name);
private:
void parseFile(const std::filesystem::path& path);
json _shipsData;
};

View file

@ -0,0 +1,54 @@
#include "Ship/Cargo.h"
Cargo::Cargo(size_t amount)
: amount(amount) {}
size_t Fruit::getPrice() const {
return 20;
}
const std::string& Fruit::name() const {
static std::string name{"Banana"};
return name;
}
Fruit::Fruit(size_t amount, int rottenCounter)
: Cargo(amount), rottenCounter(rottenCounter) {
}
void Fruit::accept(const CargoDamageVisitor& visitor) {
visitor(*this);
}
Item::Item(size_t amount, Type type)
: Cargo(amount), type(type) {
}
size_t Item::getPrice() const {
return 30;
}
const std::string& Item::name() const {
static std::string name{"Item"};
return name;
}
void Item::accept(const CargoDamageVisitor& visitor) {
visitor(*this);
}
Alcohol::Alcohol(size_t amount, int power, Type type)
: Cargo(amount), power(power), type(type) {}
size_t Alcohol::getPrice() const {
return 40;
}
const std::string& Alcohol::name() const {
static std::string name{"Rum"};
return name;
}
void Alcohol::accept(const CargoDamageVisitor& visitor) {
visitor(*this);
}

View file

@ -0,0 +1,20 @@
#include <Ship/CargoDamageVisitor.h>
#include <Ship/Cargo.h>
std::random_device CargoDamageVisitor::_rd{};
void CargoDamageVisitor::operator()(Fruit& fruit) const {
std::uniform_int_distribution<int> dice(0, 5);
fruit.amount -= dice(_seed);
}
void CargoDamageVisitor::operator()(Alcohol& alcohol) const {
std::uniform_int_distribution<int> dice(0, 10);
alcohol.amount -= (dice(_seed) / 2);
}
void CargoDamageVisitor::operator()(Item& item) const {
std::uniform_int_distribution<int> dice(0, 20);
item.amount -= (dice(_seed) / 4);
}

View file

@ -0,0 +1,107 @@
#include "Ship/Ship.h"
#include <Core/PrintVisitor.h>
#include <Core/Time.h>
#include <algorithm>
#include <cassert>
#include <exception>
#include <iostream>
void Ship::initialize() {
assert(time);
_time->attach(this);
}
Ship::~Ship() {
_time->detach(this);
}
void Ship::nextDay() {
if (!_strategy->handle(*this)) {
rebel();
}
}
void Ship::printCargo() const {
_visitor->visit(*this);
}
Cargo* Ship::load(std::unique_ptr<Cargo>&& cargo, StatusCode& code) noexcept {
if (_capacity > cargo->amount) {
_cargoes.push_back(std::move(cargo));
code = StatusCode::LackOfSpace;
return _cargoes.back().get();
}
code = StatusCode::OK;
return nullptr;
}
void Ship::unload(const std::string& cargoName, int amount, StatusCode& code) noexcept {
if (!unloadCargo(cargoName, amount)) {
code = StatusCode::MissingCargo;
return;
}
code = StatusCode::OK;
}
Cargo* Ship::load(std::unique_ptr<Cargo>&& cargo) {
if (_capacity > cargo->amount) {
_cargoes.push_back(std::move(cargo));
return _cargoes.back().get();
}
throw std::runtime_error("Lack of space");
}
void Ship::unload(const std::string& cargoName, int amount) {
if (!unloadCargo(cargoName, amount)) {
throw std::runtime_error("Missing cargo");
}
}
void Ship::takeDamage(int damage) {
_armor -= damage;
if (_armor >= 0) {
return;
}
_durability += _armor;
_armor = 0;
if (_durability <= 0) {
std::cout << "Ship: " << _name << " was destroyed!\n";
}
}
const std::vector<std::unique_ptr<Cargo>>& Ship::cargoes() const {
return _cargoes;
}
bool Ship::unloadCargo(const std::string& cargoName, int amount) {
while (amount != 0) {
auto it = std::ranges::find_if(_cargoes, [&cargoName](const auto& cargo) { return cargo->name() == cargoName; });
if (it != _cargoes.end()) {
if ((*it)->amount > amount) {
(*it)->amount -= amount;
return true;
}
amount -= (*it)->amount;
_cargoes.erase(it);
} else {
return false;
}
}
return true;
}
void Ship::rebel() {
std::cout << "\n********** Crew rebel **********\n";
_crew -= 5;
if (_crew < 5) {
throw std::runtime_error("There is not enought crew! Game over!");
}
}

View file

@ -0,0 +1,68 @@
#include "Ship/ShipBuilder.h"
#include "Ship/ShipEasyDifficultLvlStrategy.h"
#include <Core/PrettyPrintVisitor.h>
#include <iostream>
ShipBuilder::ShipBuilder()
: _ship(std::make_unique<Ship>()) {
_ship->_strategy = std::make_unique<ShipEasyDifficultLvlStrategy>();
_ship->_visitor = std::make_unique<PrettyPrintVisitor>();
}
std::unique_ptr<Ship> ShipBuilder::build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->_time || _ship->_name.empty() || _ship->_capacity == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
_ship->initialize();
return std::move(_ship);
}
ShipBuilder& ShipBuilder::setTime(Time* time) {
_ship->_time = time;
return *this;
}
ShipBuilder& ShipBuilder::setStrategy(std::unique_ptr<DifficultLvlStrategy<Ship, bool>> strategy) {
_ship->_strategy = std::move(strategy);
return *this;
}
ShipBuilder& ShipBuilder::setName(const std::string& name) {
_ship->_name = name;
return *this;
}
ShipBuilder& ShipBuilder::setCapacity(int capacity) {
_ship->_capacity = capacity;
return *this;
}
ShipBuilder& ShipBuilder::setCrew(int crew) {
_ship->_crew = crew;
return *this;
}
ShipBuilder& ShipBuilder::setVisitor(std::unique_ptr<PrintVisitor> visitor) {
_ship->_visitor = std::move(visitor);
return *this;
}
ShipBuilder& ShipBuilder::setArmor(int armor) {
_ship->_armor = armor;
return *this;
}
ShipBuilder& ShipBuilder::setDurability(int durability) {
_ship->_durability = durability;
return *this;
}

View file

@ -0,0 +1,12 @@
#include "Ship/ShipEasyDifficultLvlStrategy.h"
#include "Ship/Ship.h"
bool ShipEasyDifficultLvlStrategy::handle(Ship& ship) const {
Ship::StatusCode status;
ship.unload("Rum", ship.crew() / 2, status);
Ship::StatusCode status2;
ship.unload("Banana", ship.crew() / 2, status2);
return status == Ship::StatusCode::OK && status2 == Ship::StatusCode::OK;
}

View file

@ -0,0 +1,12 @@
#include "Ship/ShipHardDifficultLvlStrategy.h"
#include "Ship/Ship.h"
bool ShipHardDifficultLvlStrategy::handle(Ship& ship) const {
Ship::StatusCode status;
ship.unload("Rum", ship.crew(), status);
Ship::StatusCode status2;
ship.unload("Banana", ship.crew(), status2);
return status == Ship::StatusCode::OK && status2 == Ship::StatusCode::OK;
}

View file

@ -0,0 +1,48 @@
#include "Ship/ShipJsonBuilder.h"
#include "Ship/Ship.h"
#include <errno.h>
#include <fstream>
#include <iostream>
ShipJsonBuilder::ShipJsonBuilder(const std::filesystem::path& path) {
parseFile(path);
}
std::unique_ptr<Ship> ShipJsonBuilder::buildBrig(Time* time, const std::string& name) {
return setTime(time)
.setName(name)
.setCapacity(_shipsData["brige"]["capacity"])
.setCrew(_shipsData["brige"]["crew"])
.setArmor(_shipsData["brige"]["armor"])
.setDurability(_shipsData["brige"]["durability"])
.build();
}
std::unique_ptr<Ship> ShipJsonBuilder::buildFrigate(Time* time, const std::string& name) {
return setTime(time)
.setName(name)
.setCapacity(_shipsData["frigate"]["capacity"])
.setCrew(_shipsData["frigate"]["crew"])
.setArmor(_shipsData["frigate"]["armor"])
.setDurability(_shipsData["frigate"]["durability"])
.build();
}
std::unique_ptr<Ship> ShipJsonBuilder::buildGaleon(Time* time, const std::string& name) {
return setTime(time)
.setName(name)
.setCapacity(_shipsData["galeon"]["capacity"])
.setCrew(_shipsData["galeon"]["crew"])
.setArmor(_shipsData["galeon"]["armor"])
.setDurability(_shipsData["galeon"]["durability"])
.build();
}
void ShipJsonBuilder::parseFile(const std::filesystem::path& path) {
std::ifstream file(path);
if (!file.is_open()) {
std::cout << "Can't open a file! erno: " << strerror(errno) << '\n';
abort();
}
_shipsData = json::parse(file);
}

View file

@ -0,0 +1,7 @@
add_library(Store src/Store.cpp)
target_include_directories(Store PUBLIC include)
target_link_libraries(Store
Ship
)

View file

@ -0,0 +1,30 @@
#pragma once
#include "Ship/Cargo.h"
#include <Core/TimeObserver.h>
#include <memory>
#include <string>
#include <random>
#include <vector>
class PrintVisitor;
class Store : public TimeObserver {
public:
explicit Store(std::vector<std::unique_ptr<Cargo>>&& cargos, std::unique_ptr<PrintVisitor>&& visitor);
// Override from TimeObserver
void nextDay() override;
size_t getTotalPrice() const;
std::vector<std::string> getCargosName() const;
const std::vector<std::unique_ptr<Cargo>>& cargoes() const;
void printCargo() const;
private:
static std::random_device _rd;
std::mt19937 _seed;
std::vector<std::unique_ptr<Cargo>> _cargoes;
std::unique_ptr<PrintVisitor> _visitor;
};

View file

@ -0,0 +1,41 @@
#include "Store/Store.h"
#include <Core/PrintVisitor.h>
Store::Store(std::vector<std::unique_ptr<Cargo>>&& cargos, std::unique_ptr<PrintVisitor>&& visitor)
: _seed(_rd()), _cargoes(std::move(cargos)), _visitor(std::move(visitor)) {}
std::random_device Store::_rd{};
void Store::nextDay() {
std::uniform_int_distribution<int> dist(-50, 50);
for (auto& cargo : _cargoes) {
cargo->amount += dist(_seed);
}
}
size_t Store::getTotalPrice() const {
size_t value{0};
for (const auto& cargo : _cargoes) {
value += cargo->getPrice();
}
return value;
}
const std::vector<std::unique_ptr<Cargo>>& Store::cargoes() const {
return _cargoes;
}
std::vector<std::string> Store::getCargosName() const {
std::vector<std::string> names;
for (const auto& cargo : _cargoes) {
names.push_back(cargo->name());
}
return names;
}
void Store::printCargo() const {
_visitor->visit(*this);
}

View file

@ -0,0 +1,21 @@
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.11.0
)
FetchContent_MakeAvailable(googletest)
add_library(GTest::GTest INTERFACE IMPORTED)
target_link_libraries(GTest::GTest INTERFACE gtest_main)
target_link_libraries(GTest::GTest INTERFACE gmock_main)
add_executable(SHM_Tests ShipUnitTests.cpp StoreUnitTests.cpp)
target_link_libraries(SHM_Tests
PRIVATE
GTest::GTest
Ship
Store)
add_test(shm_gtests SHM_Tests)

View file

@ -0,0 +1,3 @@
#pragma once
// Write mock here

View file

@ -0,0 +1,9 @@
#include "Ship/Cargo.h"
#include "Ship/Ship.h"
#include <gtest/gtest.h>
TEST(ShipTests, ShouldCreate)
{
}

View file

@ -0,0 +1,11 @@
#include "CargoMock.h"
#include "Ship/Cargo.h"
#include "Store/Store.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
TEST(StoreTests, ShouldCreate)
{
}

View file

@ -0,0 +1,152 @@
#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <set>
#include <vector>
struct Time {};
template <typename T, typename U>
struct DifficultStrategy {};
struct PrintVisitor {};
class Ship {
public:
void setCrew(int crew) { _crew = crew; }
void setArmor(int armor) { _armor = armor; }
void setCannons(int cannons) { _canons = cannons; }
void setDurability(int durability) { _durability = durability; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int canons() const { return _canons; }
int durability() const { return _durability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr<Ship> std::make_unique<Ship>();
friend class ShipBuilder;
Ship() = default;
Time* _time{};
std::unique_ptr<DifficultStrategy<Ship, bool>> _strategy{};
std::string _name{};
int _capacity{-1};
int _maxCrew{-1};
int _crew{10};
std::unique_ptr<PrintVisitor> _visitor{};
int _armor{0};
int _maxArmor{-1};
int _canons{0};
int _maxCannons{-1};
int _durability{100};
int _maxDurability{-1};
};
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique<Ship>()) {}
[[nodiscard]] std::unique_ptr<Ship> build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->_time ||
_ship->_name.empty() ||
_ship->_capacity == -1 ||
_ship->_maxCrew == -1 ||
_ship->_maxArmor == -1 ||
_ship->_maxCannons == -1 ||
_ship->_maxDurability == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
ShipBuilder& setTime(Time* time) {
_ship->_time = time;
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_ship->_strategy = std::move(strategy);
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->_name = name;
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->_capacity = capacity;
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->_maxCrew = maxCrew;
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->_crew = crew;
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
_ship->_visitor = std::move(visitor);
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->_armor = armor;
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->_maxArmor = maxArmor;
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->_canons = cannons;
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->_maxCannons = maxCannons;
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->_durability = durability;
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->_maxDurability = maxDurability;
return *this;
}
private:
std::unique_ptr<Ship> _ship;
};
int main() {
auto ship = ShipBuilder().build();
if (ship) {
std::cout << "Ship name: " << ship->name() << '\n';
}
Time time;
auto ship2 = ShipBuilder()
.setTime(&time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.build();
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
}

View file

@ -0,0 +1,173 @@
#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <set>
#include <vector>
struct Time {};
template <typename T, typename U>
struct DifficultStrategy {};
struct PrintVisitor {};
class Ship {
public:
void setCrew(int crew) { _crew = crew; }
void setArmor(int armor) { _armor = armor; }
void setCannons(int cannons) { _canons = cannons; }
void setDurability(int durability) { _durability = durability; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int canons() const { return _canons; }
int durability() const { return _durability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr<Ship> std::make_unique<Ship>();
friend class ShipBuilder;
Ship() = default;
Time* _time{};
std::unique_ptr<DifficultStrategy<Ship, bool>> _strategy{};
std::string _name{};
int _capacity{-1};
int _maxCrew{-1};
int _crew{10};
std::unique_ptr<PrintVisitor> _visitor{};
int _armor{0};
int _maxArmor{-1};
int _canons{0};
int _maxCannons{-1};
int _durability{100};
int _maxDurability{-1};
};
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique<Ship>()) {}
[[nodiscard]] std::unique_ptr<Ship> build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->_time ||
_ship->_name.empty() ||
_ship->_capacity == -1 ||
_ship->_maxCrew == -1 ||
_ship->_maxArmor == -1 ||
_ship->_maxCannons == -1 ||
_ship->_maxDurability == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
ShipBuilder& setTime(Time* time) {
_ship->_time = time;
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr<DifficultStrategy<Ship, bool>> strategy) {
_ship->_strategy = std::move(strategy);
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->_name = name;
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->_capacity = capacity;
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->_maxCrew = maxCrew;
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->_crew = crew;
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr<PrintVisitor> visitor) {
_ship->_visitor = std::move(visitor);
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->_armor = armor;
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->_maxArmor = maxArmor;
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->_canons = cannons;
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->_maxCannons = maxCannons;
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->_durability = durability;
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->_maxDurability = maxDurability;
return *this;
}
private:
std::unique_ptr<Ship> _ship;
};
class ShipJsonBuilder : private ShipBuilder {
public:
std::unique_ptr<Ship> buildBrig(Time* time, const std::string& name) {
return setTime(time)
.setName(name)
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.build();
}
std::unique_ptr<Ship> buildFrigate();
std::unique_ptr<Ship> buildGaleon();
};
int main() {
auto ship = ShipBuilder().build();
if (ship) {
std::cout << "Ship name: " << ship->name() << '\n';
}
Time time;
auto ship2 = ShipBuilder()
.setTime(&time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.build();
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
auto ship3 = ShipJsonBuilder().buildBrig(&time, "Black Widow");
if (ship3) {
std::cout << "Ship name: " << ship3->name() << '\n';
}
}

View file

@ -0,0 +1,162 @@
#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <set>
#include <vector>
struct TimeObserver {
virtual ~TimeObserver() = default;
virtual void nextDay() = 0;
};
class Time {
public:
enum class State {
Closing,
FocusChanged
};
void attach(TimeObserver* observer);
void detach(TimeObserver* observer);
Time& operator++();
size_t day() const { return _day; }
private:
void notify() const;
size_t _day{0};
std::set<TimeObserver*> _observers;
};
void Time::attach(TimeObserver* observer) {
_observers.emplace(observer);
}
void Time::detach(TimeObserver* observer) {
_observers.erase(observer);
}
Time& Time::operator++() {
++_day;
notify();
return *this;
}
void Time::notify() const {
std::ranges::for_each(_observers, std::mem_fn(&TimeObserver::nextDay));
}
struct Cargo {
size_t amount;
Cargo(size_t amount)
: amount(amount) {}
virtual ~Cargo() = default;
Cargo(const Cargo&) = default;
Cargo(Cargo&&) = default;
Cargo& operator=(const Cargo&) = default;
Cargo& operator=(Cargo&&) = default;
auto operator<=>(const Cargo&) const = default;
virtual size_t getPrice() const = 0;
virtual const std::string& name() const = 0;
};
struct Fruit : public Cargo, public TimeObserver {
static constexpr int MAX_TIME = 20;
static constexpr int BASE_PRICE = 10;
int rottenCounter;
Time* time;
Fruit(Time* time, size_t amount, int rottenCounter)
: Cargo(amount), time(time), rottenCounter(rottenCounter) {
time->attach(this);
}
// Rule of 5
~Fruit() {
time->detach(this);
}
size_t getPrice() const override {
return (rottenCounter * BASE_PRICE) / MAX_TIME;
}
const std::string& name() const override {
static const std::string name = "Banana";
return name;
}
void nextDay() override {
rottenCounter = std::max(0, rottenCounter - 1);
}
};
struct Item : public Cargo, public TimeObserver {
enum class Type { Common = 100,
Rare = 300,
Epic = 1000,
Legendary = 2500 };
static constexpr int MAX_DURABILITY = 100;
Time* time;
Type type;
int durability{MAX_DURABILITY};
Item(Time* time, size_t amount, Type type)
: Cargo(amount), time(time), type(type) {
time->attach(this);
}
// Rule of 5
~Item() {
time->detach(this);
}
size_t getPrice() const override {
return (durability * static_cast<int>(type)) / MAX_DURABILITY;
}
const std::string& name() const override {
static const std::string name = "Item";
return name;
}
void nextDay() override {
durability = std::max(0, durability - 1);
}
};
struct Alcohol : public Cargo {
enum class Type { White = 100,
Spiced = 200,
Dark = 300,
Seasoned = 500 };
static constexpr int MAX_POWER = 96;
int power;
Type type;
Alcohol(size_t amount, int power, Type type)
: Cargo(amount), power(power), type(type) {}
size_t getPrice() const override {
return (power * static_cast<int>(type)) / MAX_POWER;
}
const std::string& name() const override {
static const std::string name = "Rum";
return name;
}
};
int main() {
Time time;
std::unique_ptr<Cargo> cargo = std::make_unique<Item>(&time, 10, Item::Type::Epic);
for (int i = 1; i <= 15; ++i) {
std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice() << '\n';
++time;
}
}

View file

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.16)
project(SHM LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
enable_testing()
add_subdirectory(src)
add_subdirectory(tests)
add_executable(${PROJECT_NAME} main.cpp)
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
target_link_libraries(${PROJECT_NAME}
Battle
Ship
Store
Core
Player
nlohmann_json::nlohmann_json
)

View file

@ -0,0 +1,53 @@
#include <functional>
#include <iostream>
#include <memory>
#include "Battle/BattleField.h"
#include "Core/BasicPrintVisitor.h"
#include "Core/FullInfoPrintVisitor.h"
#include "Core/PrettyPrintVisitor.h"
#include "Core/Time.h"
#include "Player/Enemy.h"
#include "Player/EnemyEasyDifficultLvlStrategy.h"
#include "Player/EnemyHardDifficultLvlStrategy.h"
#include "Player/RealPlayer.h"
#include "Ship/AlcoholFactory.h"
#include "Ship/AlcoholType.h"
#include "Ship/Cargo.h"
#include "Ship/CargoDamageVisitor.h"
#include "Ship/FruitFactory.h"
#include "Ship/ItemFactory.h"
#include "Ship/ItemType.h"
#include "Ship/Ship.h"
#include "Ship/ShipBuilder.h"
#include "Ship/ShipEasyDifficultLvlStrategy.h"
#include "Ship/ShipHardDifficultLvlStrategy.h"
#include "Ship/ShipJsonBuilder.h"
#include "Ship/Valuable.h"
#include "Ship/Vulnerable.h"
#include "Store/Store.h"
int main() {
Time time;
auto ship = ShipBuilder()
.setTime(&time)
.setName("Black Widow")
.setCapacity(2500)
.setCrew(40)
.setStrategy(std::make_unique<ShipHardDifficultLvlStrategy>())
.setVisitor(std::make_unique<FullInfoPrintVisitor>())
.setArmor(100)
.setDurability(1000)
.build();
ship->load(std::make_unique<Valuable<AlcoholType>>(AlcoholFactory().create(800, 40), AlcoholType::Seasoned));
ship->load(std::make_unique<Vulnerable>(FruitFactory().create(500), &time, 15, 15));
ship->load(std::make_unique<Vulnerable>(FruitFactory().create(700), &time, 20, 20));
ship->load(std::make_unique<Vulnerable>(std::make_unique<Valuable<ItemType>>(ItemFactory().create(250), ItemType::Epic), &time, 100, 100));
for (size_t i = 1; i <= 15; ++i) {
std::cout << "day: " << i << '\n';
ship->printCargo();
++time;
}
}

View file

@ -0,0 +1,20 @@
{
"brige":{
"capacity":1000,
"crew":40,
"durability":1000,
"armor":100
},
"frigate":{
"capacity":1500,
"crew":70,
"durability":2000,
"armor":500
},
"galeon":{
"capacity":2500,
"crew":120,
"durability":3000,
"armor":1000
}
}

View file

@ -0,0 +1,8 @@
add_library(Battle src/Defense.cpp src/BattleField.cpp src/Attack.cpp src/Escape.cpp)
target_include_directories(Battle PUBLIC include)
target_link_libraries(Battle
Ship
Player
)

View file

@ -0,0 +1,24 @@
#pragma once
class Player;
class Ship;
class Action {
public:
// As you can see keeping type here, require modify a base class whenever we add a new class, this volatile an open-close principle.
// We should be able to extend code without modification already implemented one
enum class Type {
Attack,
Defense,
Escape
};
enum class Status {
Escaped,
Defeated,
Nothing,
};
virtual Status operator()(Player* player, Ship* enemyShip) = 0;
virtual Type type() const = 0;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "Battle/Action.h"
class Player;
class Ship;
class Attack : public Action {
public:
Status operator()(Player* player, Ship* enemyShip) override;
Type type() const override { return Action::Type::Attack; }
};

View file

@ -0,0 +1,19 @@
#pragma once
#include <vector>
class Ship;
class Player;
class BattleField {
public:
BattleField(Player* player, Player* enemy);
Ship* getPlayerShip() const;
Ship* getEnemyShip() const;
std::vector<Player*> players() const;
private:
Player* _player;
Player* _enemy;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "Battle/Action.h"
class Player;
class Ship;
class Defense : public Action {
public:
Status operator()(Player* player, Ship* enemyShip) override;
Type type() const override { return Type::Defense; }
};

View file

@ -0,0 +1,18 @@
#pragma once
#include "Battle/Action.h"
#include <random>
class Player;
class Ship;
class Escape : public Action {
public:
Status operator()(Player* player, Ship* enemyShip) override;
Type type() const override { return Action::Type::Escape; }
private:
static std::random_device _rd;
std::mt19937 _seed{_rd()};
};

View file

@ -0,0 +1,22 @@
#include "Battle/Attack.h"
#include <Player/Player.h>
#include <Ship/Ship.h>
#include <iostream>
Action::Status Attack::operator()(Player* player, Ship* enemyShip) {
const int damage = player->attack(*enemyShip);
std::cout << "Player ship: " << player->getShip().name() << " attack ship: " << enemyShip->name() << '\n';
if (damage) {
std::cout << "Dealed damage: " << damage << '\n';
} else {
std::cout << "Missed\n";
}
if (enemyShip->durability() <= 0) {
return Status::Defeated;
}
return Status::Nothing;
}

View file

@ -0,0 +1,16 @@
#include "Battle/BattleField.h"
#include <Player/Player.h>
BattleField::BattleField(Player* player, Player* enemy)
: _player{player}, _enemy{enemy} {}
Ship* BattleField::getPlayerShip() const {
return &_player->getShip();
}
Ship* BattleField::getEnemyShip() const {
return &_enemy->getShip();
}
std::vector<Player*> BattleField::players() const {
return {_player, _enemy};
}

View file

@ -0,0 +1,13 @@
#include "Battle/Defense.h"
#include <Player/Player.h>
#include <Ship/Ship.h>
#include <iostream>
Action::Status Defense::operator()(Player* player, Ship*) {
player->getShip().increaseArmor(50);
std::cout << "Player ship: " << player->getShip().name() << " defense\n";
return Status::Nothing;
}

View file

@ -0,0 +1,22 @@
#include "Battle/Escape.h"
#include <Player/Player.h>
#include <Ship/Ship.h>
#include <iostream>
std::random_device Escape::_rd{};
Action::Status Escape::operator()(Player* player, Ship*) {
std::cout << "Player ship: " << player->getShip().name() << " try to escape!\n";
std::uniform_int_distribution<int> dice(0, 20);
const auto res = dice(_seed);
if (res > 12) {
std::cout << "Player escaped!\n";
return Status::Escaped;
}
std::cout << "Escape maneuver failed!\n";
return Status::Nothing;
}

View file

@ -0,0 +1,5 @@
add_subdirectory(Battle)
add_subdirectory(Core)
add_subdirectory(Player)
add_subdirectory(Ship)
add_subdirectory(Store)

View file

@ -0,0 +1,8 @@
add_library(Core src/Time.cpp src/BasicPrintVisitor.cpp src/PrettyPrintVisitor.cpp src/FullInfoPrintVisitor.cpp)
target_include_directories(Core PUBLIC include)
target_link_libraries(Core
Ship
Store
)

View file

@ -0,0 +1,12 @@
#pragma once
#include "Core/PrintVisitor.h"
class Store;
class Ship;
class BasicPrintVisitor : public PrintVisitor {
public:
void visit(const Store&) const override;
void visit(const Ship&) const override;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include <memory>
#include <string>
#include <set>
#include <utility>
template <typename T, typename Res>
class DifficultLvlStrategy {
public:
virtual Res handle(T&) const = 0;
};

View file

@ -0,0 +1,15 @@
#pragma once
#include "Core/PrintVisitor.h"
class Store;
class Ship;
class FullInfoPrintVisitor : public PrintVisitor {
public:
void visit(const Store&) const override;
void visit(const Ship&) const override;
private:
void print() const;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "Core/PrintVisitor.h"
class Store;
class Ship;
class PrettyPrintVisitor : public PrintVisitor {
public:
void visit(const Store&) const override;
void visit(const Ship&) const override;
};

View file

@ -0,0 +1,11 @@
#pragma once
class Store;
class Ship;
class PrintVisitor {
public:
virtual ~PrintVisitor() = default;
virtual void visit(const Store&) const = 0;
virtual void visit(const Ship&) const = 0;
};

View file

@ -0,0 +1,27 @@
#pragma once
class TimeObserver;
#include <memory>
#include <string>
#include <set>
#include <utility>
class Time {
public:
enum class State {
Closing,
FocusChanged
};
void attach(TimeObserver* observer);
void detach(TimeObserver* observer);
Time& operator++();
size_t day() const { return _day; }
private:
void notify() const;
size_t _day{0};
std::set<TimeObserver*> _observers;
};

View file

@ -0,0 +1,6 @@
#pragma once
struct TimeObserver {
virtual ~TimeObserver() = default;
virtual void nextDay() = 0;
};

View file

@ -0,0 +1,22 @@
#include <Core/BasicPrintVisitor.h>
#include <Ship/Ship.h>
#include <Store/Store.h>
#include <iomanip>
#include <iostream>
void BasicPrintVisitor::visit(const Store& store) const {
const auto& cargoes = store.cargoes();
for (const auto& cargo : cargoes) {
std::cout << std::setw(15) << std::left << cargo->name() << std::setw(15) << cargo->amount() << std::setw(15) << cargo->getPrice() << "\n";
}
}
void BasicPrintVisitor::visit(const Ship& ship) const {
const auto& cargoes = ship.cargoes();
for (const auto& cargo : cargoes) {
std::cout << std::setw(15) << std::left << cargo->name() << std::setw(15) << cargo->amount() << "\n";
}
}

View file

@ -0,0 +1,24 @@
#include <Core/FullInfoPrintVisitor.h>
#include <Ship/Ship.h>
#include <Store/Store.h>
#include <iomanip>
#include <iostream>
void FullInfoPrintVisitor::visit(const Store& store) const {
std::cout << "Don't have time to implement this, sorry xD\n";
}
void FullInfoPrintVisitor::visit(const Ship& ship) const {
const auto& cargoes = ship.cargoes();
std::cout << "|----------------|----------------|----------------|\n";
std::cout << "| NAME "
<< "| AMOUNT "
<< "| PRICE |" << '\n';
std::cout << "|----------------|----------------|----------------|\n";
for (const auto& cargo : cargoes) {
std::cout << "|" << std::setw(15) << std::left << cargo->name() << " | " << std::setw(15) << cargo->amount() << "| " << std::setw(15) << cargo->getPrice() << "|\n";
}
std::cout << "|----------------|----------------|----------------|\n";
}

View file

@ -0,0 +1,33 @@
#include <Core/PrettyPrintVisitor.h>
#include <Ship/Ship.h>
#include <Store/Store.h>
#include <iomanip>
#include <iostream>
void PrettyPrintVisitor::visit(const Store& store) const {
const auto& cargoes = store.cargoes();
std::cout << "|----------------|----------------|----------------|\n";
std::cout << "| NAME "
<< "| AMOUNT "
<< "| PRICE |" << '\n';
std::cout << "|----------------|----------------|----------------|\n";
for (const auto& cargo : cargoes) {
std::cout << "|" << std::setw(15) << std::left << cargo->name() << " | " << std::setw(15) << cargo->amount() << "| " << std::setw(15) << cargo->getPrice() << "|\n";
}
std::cout << "|----------------|----------------|----------------|\n";
}
void PrettyPrintVisitor::visit(const Ship& ship) const {
const auto& cargoes = ship.cargoes();
std::cout << "|----------------|----------------|\n";
std::cout << "| NAME "
<< "| AMOUNT |" << '\n';
std::cout << "|----------------|----------------|\n";
for (const auto& cargo : cargoes) {
std::cout << "|" << std::setw(15) << std::left << cargo->name() << " | " << std::setw(15) << cargo->amount() << "|\n";
}
std::cout << "|----------------|----------------|\n";
}

View file

@ -0,0 +1,24 @@
#include "Core/Time.h"
#include "Core/TimeObserver.h"
#include <algorithm>
#include <functional>
void Time::attach(TimeObserver* observer) {
_observers.emplace(observer);
}
void Time::detach(TimeObserver* observer) {
_observers.erase(observer);
}
Time& Time::operator++() {
++_day;
notify();
return *this;
}
void Time::notify() const {
std::ranges::for_each(_observers, std::mem_fn(&TimeObserver::nextDay));
}

View file

@ -0,0 +1,9 @@
add_library(Player src/EnemyEasyDifficultLvlStrategy.cpp src/EnemyHardDifficultLvlStrategy.cpp src/Player.cpp src/RealPlayer.cpp)
target_include_directories(Player PUBLIC include)
target_link_libraries(Player
Battle
Core
Ship
)

View file

@ -0,0 +1,66 @@
#pragma once
#include "Player/Player.h"
#include <Battle/Attack.h>
#include <Battle/BattleField.h>
#include <Battle/Defense.h>
#include <Core/DifficultLvlStrategy.h>
#include <Ship/Ship.h>
#include <memory>
#include <string>
template <typename DamageVisitor>
class Enemy : public Player {
public:
Enemy(const std::string& name, std::unique_ptr<Ship>&& ship, std::unique_ptr<DifficultLvlStrategy<Ship, int>>&& strategy, DamageVisitor visitor);
int attack(Ship& playerShip) override;
Ship& getShip() { return *_ship; }
const Ship& getShip() const { return *_ship; }
protected:
Ship* chooseEnemyShip(const BattleField& battleField) const override final;
std::unique_ptr<Action> chooseAction(const BattleField& battleField) const override final;
private:
std::string _name;
std::unique_ptr<Ship> _ship;
std::unique_ptr<DifficultLvlStrategy<Ship, int>> _strategy;
DamageVisitor _damageVisitor;
};
template <typename DamageVisitor>
Enemy<DamageVisitor>::Enemy(const std::string& name, std::unique_ptr<Ship>&& ship, std::unique_ptr<DifficultLvlStrategy<Ship, int>>&& strategy, DamageVisitor visitor)
: _name(name), _ship(std::move(ship)), _strategy(std::move(strategy)), _damageVisitor(visitor) {}
template <typename DamageVisitor>
int Enemy<DamageVisitor>::attack(Ship& playerShip) {
if (const auto damage = _strategy->handle(playerShip)) {
for (auto& cargo : playerShip.cargoes()) {
cargo->accept(_damageVisitor);
}
return damage;
}
return 0;
}
template <typename DamageVisitor>
Ship* Enemy<DamageVisitor>::chooseEnemyShip(const BattleField& battleField) const {
// To simplify palyer and enemy has one ship
return battleField.getPlayerShip();
}
template <typename DamageVisitor>
std::unique_ptr<Action> Enemy<DamageVisitor>::chooseAction(const BattleField& battleField) const {
static bool flag = true;
if (flag) {
flag = !flag;
return std::make_unique<Attack>();
}
flag = !flag;
return std::make_unique<Defense>();
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
#include <random>
class Ship;
class EnemyEasyDifficultLvlStrategy : public DifficultLvlStrategy<Ship, int> {
public:
EnemyEasyDifficultLvlStrategy();
int handle(Ship& ship) const override;
private:
static std::random_device _rd;
mutable std::mt19937 _seed;
};

View file

@ -0,0 +1,17 @@
#pragma once
#include <Core/DifficultLvlStrategy.h>
#include <random>
class Ship;
class EnemyHardDifficultLvlStrategy : public DifficultLvlStrategy<Ship, int> {
public:
EnemyHardDifficultLvlStrategy();
int handle(Ship& ship) const override;
private:
static std::random_device _rd;
mutable std::mt19937 _seed;
};

Some files were not shown because too many files have changed in this diff Show more