Reliable - pierwszy commit
This commit is contained in:
parent
9d022ab235
commit
245cbec983
730 changed files with 51141 additions and 0 deletions
19
CreatingReliableSoftwareCpp/LICENSE
Normal file
19
CreatingReliableSoftwareCpp/LICENSE
Normal 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.
|
||||
28
CreatingReliableSoftwareCpp/Presentation/agenda.md
Normal file
28
CreatingReliableSoftwareCpp/Presentation/agenda.md
Normal 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" -->
|
||||
806
CreatingReliableSoftwareCpp/Presentation/builder.md
Normal file
806
CreatingReliableSoftwareCpp/Presentation/builder.md
Normal 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" -->
|
||||
43
CreatingReliableSoftwareCpp/Presentation/compilation.md
Normal file
43
CreatingReliableSoftwareCpp/Presentation/compilation.md
Normal 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
|
||||
];
|
||||
}
|
||||
```
|
||||
577
CreatingReliableSoftwareCpp/Presentation/decorator.md
Normal file
577
CreatingReliableSoftwareCpp/Presentation/decorator.md
Normal 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<Cargo></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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
# Design patterns
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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; }
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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; }
|
||||
};
|
||||
|
|
@ -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()};
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
add_subdirectory(Battle)
|
||||
add_subdirectory(Core)
|
||||
add_subdirectory(Player)
|
||||
add_subdirectory(Ship)
|
||||
add_subdirectory(Store)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
struct TimeObserver {
|
||||
virtual ~TimeObserver() = default;
|
||||
virtual void nextDay() = 0;
|
||||
};
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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()};
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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()};
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
add_library(Store src/Store.cpp)
|
||||
|
||||
target_include_directories(Store PUBLIC include)
|
||||
|
||||
target_link_libraries(Store
|
||||
Ship
|
||||
)
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
// Write mock here
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#include "Ship/Cargo.h"
|
||||
#include "Ship/Ship.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(ShipTests, ShouldCreate)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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; }
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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; }
|
||||
};
|
||||
|
|
@ -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()};
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
add_subdirectory(Battle)
|
||||
add_subdirectory(Core)
|
||||
add_subdirectory(Player)
|
||||
add_subdirectory(Ship)
|
||||
add_subdirectory(Store)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
struct TimeObserver {
|
||||
virtual ~TimeObserver() = default;
|
||||
virtual void nextDay() = 0;
|
||||
};
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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
Loading…
Reference in a new issue