diff --git a/solutions/lab7/main.cpp b/solutions/lab7/main.cpp index e69de29..e1db9fa 100644 --- a/solutions/lab7/main.cpp +++ b/solutions/lab7/main.cpp @@ -0,0 +1,328 @@ +#include "src/dragon.h" +#include "src/elf.h" +#include "src/knight_errant.h" +#include "src/npc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace std::chrono_literals; +std::mutex print_mutex; +std::atomic stop_game{false}; +std::shared_mutex npc_array_mutex; + +class TextObserver : public IFightObserver { +private: + TextObserver(){}; + +public: + static std::shared_ptr get() { + static TextObserver instance; + return std::shared_ptr(&instance, [](IFightObserver *) {}); + } + + void on_fight(const std::shared_ptr attacker, int attack_roll, + const std::shared_ptr defender, int defense_roll, + bool win) override { + std::lock_guard lck(print_mutex); + std::cout << "\nFight Details --------\n"; + std::cout << "Attacker:\n"; + attacker->print(); + std::cout << "Defender:\n"; + defender->print(); + std::cout << "Attack roll: " << attack_roll + << ", Defense roll: " << defense_roll << '\n'; + std::cout << (win ? "Defender died!" : "Defender survived.") << "\n\n"; + } +}; + +std::shared_ptr factory(std::istream &is) { + std::shared_ptr result; + int type{0}; + if (is >> type) { + switch (type) { + case DragonType: + result = std::make_shared(is); + break; + case ElfType: + result = std::make_shared(is); + break; + case KnightErrantType: + result = std::make_shared(is); + break; + } + } else + std::cerr << "unexpected NPC type:" << type << '\n'; + + if (result) + result->subscribe(TextObserver::get()); + + return result; +} + +std::shared_ptr factory(NpcType type, int x, int y) { + std::shared_ptr result; + switch (type) { + case DragonType: + result = std::make_shared(x, y); + break; + case ElfType: + result = std::make_shared(x, y); + break; + case KnightErrantType: + result = std::make_shared(x, y); + break; + default: + break; + } + if (result) + result->subscribe(TextObserver::get()); + + return result; +} + +void save(const set_t &array, const std::string &filename) { + std::ofstream fs(filename); + fs << array.size() << '\n'; + for (auto &n : array) + n->save(fs); + fs.flush(); + fs.close(); +} + +set_t load(const std::string &filename) { + set_t result; + std::ifstream is(filename); + if (is.good() && is.is_open()) { + int count; + is >> count; + for (int i = 0; i < count; ++i) + result.insert(factory(is)); + is.close(); + } else + std::cerr << "Error: " << std::strerror(errno) << '\n'; + return result; +} + +std::ostream &operator<<(std::ostream &os, const set_t &array) { + for (auto &n : array) + n->print(); + return os; +} + +set_t fight(const set_t &array, size_t distance) { + set_t dead_list; + + for (const auto &attacker : array) + for (const auto &defender : array) + if ((attacker != defender) && attacker->is_close(defender, distance) && + defender->accept(attacker)) + dead_list.insert(defender); + + return dead_list; +} + +struct print : std::stringstream { + ~print() { + static std::mutex mtx; + std::lock_guard lck(print_mutex); + std::cout << this->str(); + std::cout.flush(); + } +}; + +struct FightEvent { + std::shared_ptr attacker; + std::shared_ptr defender; +}; + +class FightManager { +private: + std::queue events; + FightManager() {} + std::mutex mtx; + +public: + static FightManager &get() { + static FightManager instance; + return instance; + } + + void add_event(FightEvent &&event) { + std::lock_guard lck(mtx); + events.push(event); + } + + void operator()() { + while (!stop_game) { + std::optional event; + + { + std::lock_guard lck(mtx); + if (!events.empty()) { + event = events.front(); + events.pop(); + } + } + + if (event) { + auto attacker = event->attacker; + auto defender = event->defender; + + if (attacker->is_alive() && defender->is_alive() && + defender->accept(attacker)) { + int attack_roll = std::rand() % 6 + 1; + int defense_roll = std::rand() % 6 + 1; + + bool win = attack_roll > defense_roll; + + if (win) { + defender->must_die(); + } + + TextObserver::get()->on_fight(attacker, attack_roll, defender, + defense_roll, win); + } + } + + std::this_thread::sleep_for(100ms); + } + } +}; + +int main() { + const int MAX_X{100}, MAX_Y{100}, DISTANCE{50}, GAME_DURATION{30}; + const int GRID{20}, STEP_X{MAX_X / GRID}, STEP_Y{MAX_Y / GRID}; + set_t array; + + std::cout << "Generating initial NPCs...\n"; + for (size_t i = 0; i < 50; ++i) + array.insert(factory(NpcType(std::rand() % 3 + 1), std::rand() % MAX_X, + std::rand() % MAX_Y)); + + std::cout << "Initial list of NPCs:\n" << array; + + std::thread fight_thread(std::ref(FightManager::get())); + + std::thread move_thread([&]() { + while (!stop_game) { + { + std::shared_lock lock(npc_array_mutex); + for (const auto &npc : array) { + if (npc->is_alive()) { + int move_distance = 0; + switch (npc->get_type()) { + case KnightErrantType: + move_distance = 30; + break; + case ElfType: + move_distance = 10; + break; + case DragonType: + move_distance = 50; + break; + default: + break; + } + + int dx = std::rand() % (2 * move_distance + 1) - move_distance; + int dy = std::rand() % (2 * move_distance + 1) - move_distance; + + npc->move(dx, dy, MAX_X, MAX_Y); + } + } + + for (const auto &npc : array) { + for (const auto &other : array) { + if (npc != other && npc->is_alive() && other->is_alive()) { + int kill_distance = 0; + switch (npc->get_type()) { + case KnightErrantType: + kill_distance = 10; + break; + case ElfType: + kill_distance = 50; + break; + case DragonType: + kill_distance = 30; + break; + default: + break; + } + + if (npc->is_close(other, kill_distance)) { + FightManager::get().add_event({npc, other}); + } + } + } + } + } + std::this_thread::sleep_for(10ms); + } + }); + + auto start_time = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start_time < + std::chrono::seconds(GAME_DURATION)) { + std::array fields{}; + fields.fill(0); + + for (const auto &npc : array) { + const auto [x, y] = npc->position(); + int i = x / STEP_X; + int j = y / STEP_Y; + + if (npc->is_alive()) { + switch (npc->get_type()) { + case DragonType: + fields[i + GRID * j] = 'D'; + break; + case ElfType: + fields[i + GRID * j] = 'E'; + break; + case KnightErrantType: + fields[i + GRID * j] = 'K'; + break; + default: + break; + } + } else { + fields[i + GRID * j] = '.'; + } + } + + { + std::lock_guard lck(print_mutex); + for (int j = 0; j < GRID; ++j) { + for (int i = 0; i < GRID; ++i) { + char c = fields[i + j * GRID]; + std::cout << "[" << (c ? c : ' ') << "]"; + } + std::cout << '\n'; + } + std::cout << "\n"; + } + std::this_thread::sleep_for(1s); + } + + stop_game = true; + fight_thread.join(); + move_thread.join(); + + std::cout << "Survivors:\n"; + for (const auto &npc : array) { + if (npc->is_alive()) { + npc->print(); + } + } + std::cout << "\n\n"; + + return 0; +} diff --git a/solutions/lab7/src/dragon.cpp b/solutions/lab7/src/dragon.cpp index e69de29..61f63ba 100644 --- a/solutions/lab7/src/dragon.cpp +++ b/solutions/lab7/src/dragon.cpp @@ -0,0 +1,32 @@ +#include "dragon.h" +#include "elf.h" +#include "knight_errant.h" + +Dragon::Dragon(int x, int y) : NPC(DragonType, x, y) {} +Dragon::Dragon(std::istream &is) : NPC(DragonType, is) {} + +bool Dragon::accept(std::shared_ptr other) { + if (std::dynamic_pointer_cast(other)) { + return true; + } + if (std::dynamic_pointer_cast(other)) { + return true; + } + if (std::dynamic_pointer_cast(other)) { + return true; + } + + return false; +} + +void Dragon::print() { std::cout << *this; } + +void Dragon::save(std::ostream &os) { + os << DragonType << std::endl; + NPC::save(os); +} + +std::ostream &operator<<(std::ostream &os, Dragon &dragon) { + os << "dragon: " << *static_cast(&dragon) << std::endl; + return os; +} diff --git a/solutions/lab7/src/dragon.h b/solutions/lab7/src/dragon.h index e69de29..affd83f 100644 --- a/solutions/lab7/src/dragon.h +++ b/solutions/lab7/src/dragon.h @@ -0,0 +1,15 @@ +#pragma once +#include "npc.h" + +struct Dragon : public NPC { + Dragon(int x, int y); + Dragon(std::istream &is); + + void print() override; + + bool accept(std::shared_ptr visitor) override; + + void save(std::ostream &os) override; + + friend std::ostream &operator<<(std::ostream &os, Dragon &dragon); +}; diff --git a/solutions/lab7/src/elf.cpp b/solutions/lab7/src/elf.cpp index e69de29..f233861 100644 --- a/solutions/lab7/src/elf.cpp +++ b/solutions/lab7/src/elf.cpp @@ -0,0 +1,26 @@ +#include "elf.h" +#include "dragon.h" +#include "knight_errant.h" + + +Elf::Elf(int x, int y) : NPC(ElfType, x, y) {} +Elf::Elf(std::istream &is) : NPC(ElfType, is) {} + +bool Elf::accept(std::shared_ptr other) { + if (std::dynamic_pointer_cast(other)) { + return true; + } + return false; +} + +void Elf::print() { std::cout << *this; } + +void Elf::save(std::ostream &os) { + os << ElfType << std::endl; + NPC::save(os); +} + +std::ostream &operator<<(std::ostream &os, Elf &elf) { + os << "elf: " << *static_cast(&elf) << std::endl; + return os; +} diff --git a/solutions/lab7/src/elf.h b/solutions/lab7/src/elf.h index e69de29..8d42a16 100644 --- a/solutions/lab7/src/elf.h +++ b/solutions/lab7/src/elf.h @@ -0,0 +1,15 @@ +#pragma once +#include "npc.h" + +struct Elf : public NPC { + Elf(int x, int y); + Elf(std::istream &is); + + void print() override; + + bool accept(std::shared_ptr visitor) override; + + void save(std::ostream &os) override; + + friend std::ostream &operator<<(std::ostream &os, Elf &elf); +}; diff --git a/solutions/lab7/src/knight_errant.cpp b/solutions/lab7/src/knight_errant.cpp index e69de29..9129474 100644 --- a/solutions/lab7/src/knight_errant.cpp +++ b/solutions/lab7/src/knight_errant.cpp @@ -0,0 +1,26 @@ +#include "knight_errant.h" +#include "dragon.h" +#include "elf.h" + + +KnightErrant::KnightErrant(int x, int y) : NPC(KnightErrantType, x, y) {} +KnightErrant::KnightErrant(std::istream &is) : NPC(KnightErrantType, is) {} + +bool KnightErrant::accept(std::shared_ptr other) { + if (std::dynamic_pointer_cast(other)) { + return true; + } + return false; +} + +void KnightErrant::print() { std::cout << *this; } + +void KnightErrant::save(std::ostream &os) { + os << KnightErrantType << std::endl; + NPC::save(os); +} + +std::ostream &operator<<(std::ostream &os, KnightErrant &knight_errant) { + os << "knight_errant: " << *static_cast(&knight_errant) << std::endl; + return os; +} diff --git a/solutions/lab7/src/knight_errant.h b/solutions/lab7/src/knight_errant.h index e69de29..c9926ce 100644 --- a/solutions/lab7/src/knight_errant.h +++ b/solutions/lab7/src/knight_errant.h @@ -0,0 +1,16 @@ +#pragma once +#include "npc.h" + +struct KnightErrant : public NPC { + KnightErrant(int x, int y); + KnightErrant(std::istream &is); + + void print() override; + + bool accept(std::shared_ptr visitor) override; + + void save(std::ostream &os) override; + + friend std::ostream &operator<<(std::ostream &os, + KnightErrant &knight_errant); +}; diff --git a/solutions/lab7/src/npc.cpp b/solutions/lab7/src/npc.cpp index e69de29..ecd0393 100644 --- a/solutions/lab7/src/npc.cpp +++ b/solutions/lab7/src/npc.cpp @@ -0,0 +1,61 @@ +#include "npc.h" + +NPC::NPC(NpcType t, int _x, int _y) : type(t), x(_x), y(_y) {} +NPC::NPC(NpcType t, std::istream &is) : type(t) { + is >> x; + is >> y; +} + +void NPC::subscribe(std::shared_ptr observer) { + observers.push_back(observer); +} + +bool NPC::is_close(const std::shared_ptr &other, size_t distance) { + std::lock_guard lck(mtx); + auto [other_x, other_y] = other->position(); + + if ((std::pow(x - other_x, 2) + std::pow(y - other_y, 2)) <= + std::pow(distance, 2)) + return true; + else + return false; +} + +NpcType NPC::get_type() { + std::lock_guard lck(mtx); + return type; +} + +std::pair NPC::position() { + std::lock_guard lck(mtx); + return {x, y}; +} + +void NPC::save(std::ostream &os) { + os << x << std::endl; + os << y << std::endl; +} + +std::ostream &operator<<(std::ostream &os, NPC &npc) { + os << "{ x:" << npc.x << ", y:" << npc.y << "} "; + return os; +} + +void NPC::move(int shift_x, int shift_y, int max_x, int max_y) { + std::lock_guard lck(mtx); + + if ((x + shift_x >= 0) && (x + shift_x <= max_x)) + x += shift_x; + if ((y + shift_y >= 0) && (y + shift_y <= max_y)) + y += shift_y; +} + +bool NPC::is_alive() { + std::lock_guard lck(mtx); + return alive; +} + +void NPC::must_die() { + std::lock_guard lck(mtx); + alive = false; +} diff --git a/solutions/lab7/src/npc.h b/solutions/lab7/src/npc.h index e69de29..5d4acab 100644 --- a/solutions/lab7/src/npc.h +++ b/solutions/lab7/src/npc.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct NPC; +struct Dragon; +struct Elf; +struct KnightErrant; +using set_t = std::set>; + +enum NpcType { Unknown = 0, DragonType = 1, ElfType = 2, KnightErrantType = 3 }; + +struct IFightObserver { + virtual void on_fight(const std::shared_ptr attacker, int attack_roll, + const std::shared_ptr defender, int defense_roll, + bool win) = 0; +}; + +class NPC { +private: + std::mutex mtx; + + NpcType type; + int x{0}; + int y{0}; + bool alive{true}; + + std::vector> observers; + +public: + NPC(NpcType t, int _x, int _y); + NPC(NpcType t, std::istream &is); + + void subscribe(std::shared_ptr observer); + virtual bool is_close(const std::shared_ptr &other, size_t distance); + + virtual bool accept(std::shared_ptr visitor) = 0; + + virtual void print() = 0; + std::pair position(); + NpcType get_type(); + + virtual void save(std::ostream &os); + + friend std::ostream &operator<<(std::ostream &os, NPC &npc); + + void move(int shift_x, int shift_y, int max_x, int max_y); + + bool is_alive(); + void must_die(); +}; diff --git a/solutions/lab7/tests/tests.cpp b/solutions/lab7/tests/tests.cpp index e69de29..ac22f69 100644 --- a/solutions/lab7/tests/tests.cpp +++ b/solutions/lab7/tests/tests.cpp @@ -0,0 +1,300 @@ +#include "../src/dragon.h" +#include "../src/elf.h" +#include "../src/knight_errant.h" +#include "../src/npc.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; +std::mutex print_mutex; +std::atomic stop_game{false}; +std::shared_mutex npc_array_mutex; + +class TextObserver : public IFightObserver { +private: + TextObserver(){}; + +public: + static std::shared_ptr get() { + static TextObserver instance; + return std::shared_ptr(&instance, [](IFightObserver *) {}); + } + + void on_fight(const std::shared_ptr attacker, int attack_roll, + const std::shared_ptr defender, int defense_roll, + bool win) override { + std::lock_guard lck(print_mutex); + std::cout << "\nFight Details --------\n"; + std::cout << "Attacker:\n"; + attacker->print(); + std::cout << "Defender:\n"; + defender->print(); + std::cout << "Attack roll: " << attack_roll + << ", Defense roll: " << defense_roll << '\n'; + std::cout << (win ? "Defender died!" : "Defender survived.") << "\n\n"; + } +}; + +std::shared_ptr factory(std::istream &is) { + std::shared_ptr result; + int type{0}; + if (is >> type) { + switch (type) { + case DragonType: + result = std::make_shared(is); + break; + case ElfType: + result = std::make_shared(is); + break; + case KnightErrantType: + result = std::make_shared(is); + break; + } + } else + std::cerr << "unexpected NPC type:" << type << '\n'; + + if (result) + result->subscribe(TextObserver::get()); + + return result; +} + +std::shared_ptr factory(NpcType type, int x, int y) { + std::shared_ptr result; + switch (type) { + case DragonType: + result = std::make_shared(x, y); + break; + case ElfType: + result = std::make_shared(x, y); + break; + case KnightErrantType: + result = std::make_shared(x, y); + break; + default: + break; + } + if (result) + result->subscribe(TextObserver::get()); + + return result; +} + +void save(const set_t &array, const std::string &filename) { + std::ofstream fs(filename); + fs << array.size() << '\n'; + for (auto &n : array) + n->save(fs); + fs.flush(); + fs.close(); +} + +set_t load(const std::string &filename) { + set_t result; + std::ifstream is(filename); + if (is.good() && is.is_open()) { + int count; + is >> count; + for (int i = 0; i < count; ++i) + result.insert(factory(is)); + is.close(); + } else + std::cerr << "Error: " << std::strerror(errno) << '\n'; + return result; +} + +std::ostream &operator<<(std::ostream &os, const set_t &array) { + for (auto &n : array) + n->print(); + return os; +} + +set_t fight(const set_t &array, size_t distance) { + set_t dead_list; + + for (const auto &attacker : array) + for (const auto &defender : array) + if ((attacker != defender) && attacker->is_close(defender, distance) && + defender->accept(attacker)) + dead_list.insert(defender); + + return dead_list; +} + +struct print : std::stringstream { + ~print() { + static std::mutex mtx; + std::lock_guard lck(print_mutex); + std::cout << this->str(); + std::cout.flush(); + } +}; + +struct FightEvent { + std::shared_ptr attacker; + std::shared_ptr defender; +}; + +class FightManager { +private: + std::queue events; + FightManager() {} + std::mutex mtx; + +public: + static FightManager &get() { + static FightManager instance; + return instance; + } + + void add_event(FightEvent &&event) { + std::lock_guard lck(mtx); + events.push(event); + } + + void operator()() { + while (!stop_game) { + std::optional event; + + { + std::lock_guard lck(mtx); + if (!events.empty()) { + event = events.front(); + events.pop(); + } + } + + if (event) { + auto attacker = event->attacker; + auto defender = event->defender; + + if (attacker->is_alive() && defender->is_alive() && + defender->accept(attacker)) { + int attack_roll = std::rand() % 6 + 1; + int defense_roll = std::rand() % 6 + 1; + + bool win = attack_roll > defense_roll; + + if (win) { + defender->must_die(); + } + + TextObserver::get()->on_fight(attacker, attack_roll, defender, + defense_roll, win); + } + } + + std::this_thread::sleep_for(100ms); + } + } +}; + +TEST(Lab7Test, Factory) { + std::stringstream ss("1 10 20"); + auto dragon = factory(ss); + ASSERT_NE(dragon, nullptr); + ASSERT_EQ(dragon->get_type(), DragonType); + + std::stringstream ss2("2 30 40"); + auto elf = factory(ss2); + ASSERT_NE(elf, nullptr); + ASSERT_EQ(elf->get_type(), ElfType); + + auto knight = factory(KnightErrantType, 50, 60); + ASSERT_NE(knight, nullptr); + ASSERT_EQ(knight->get_type(), KnightErrantType); +} + +TEST(Lab7Test, SaveLoad) { + set_t initial_npcs; + initial_npcs.insert( + std::allocate_shared(std::allocator(), 10, 20)); + initial_npcs.insert(std::allocate_shared(std::allocator(), 30, 40)); + initial_npcs.insert(std::make_shared(50, 60)); + + const std::string filename = "test_npcs.txt"; + save(initial_npcs, filename); + + set_t loaded_npcs = load(filename); + + ASSERT_EQ(initial_npcs.size(), loaded_npcs.size()); +} + +TEST(Lab7Test, FightManager) { + FightManager &fm = FightManager::get(); + std::shared_ptr attacker = + std::allocate_shared(std::allocator(), 0, 0); + std::shared_ptr defender = std::make_shared(1, 1); + + fm.add_event({attacker, defender}); + stop_game = false; + std::thread fight_thread(std::ref(fm)); + std::this_thread::sleep_for(200ms); + stop_game = true; + fight_thread.join(); +} + +TEST(NpcTest, IsClose) { + std::shared_ptr npc1 = + std::allocate_shared(std::allocator(), 0, 0); + std::shared_ptr npc2 = + std::allocate_shared(std::allocator(), 3, 4); + ASSERT_TRUE(npc1->is_close(npc2, 5)); + ASSERT_FALSE(npc1->is_close(npc2, 4)); +} + +TEST(NpcTest, AliveStatus) { + Elf elf(0, 0); + ASSERT_TRUE(elf.is_alive()); + elf.must_die(); + ASSERT_FALSE(elf.is_alive()); +} + +TEST(DragonTest, Accept) { + std::shared_ptr dragon = + std::allocate_shared(std::allocator(), 0, 0); + std::shared_ptr elf = + std::allocate_shared(std::allocator(), 0, 0); + std::shared_ptr knight = std::make_shared(0, 0); + + ASSERT_TRUE(dragon->accept(dragon)); + ASSERT_TRUE(dragon->accept(elf)); + ASSERT_TRUE(dragon->accept(knight)); +} + +TEST(ElfTest, Accept) { + std::shared_ptr elf = + std::allocate_shared(std::allocator(), 0, 0); + std::shared_ptr knight = std::make_shared(0, 0); + std::shared_ptr dragon = + std::allocate_shared(std::allocator(), 0, 0); + + ASSERT_TRUE(elf->accept(knight)); + ASSERT_FALSE(elf->accept(elf)); + ASSERT_FALSE(elf->accept(dragon)); +} + +TEST(KnightErrantTest, Accept) { + std::shared_ptr knight = std::make_shared(0, 0); + std::shared_ptr dragon = + std::allocate_shared(std::allocator(), 0, 0); + std::shared_ptr elf = + std::allocate_shared(std::allocator(), 0, 0); + + ASSERT_TRUE(knight->accept(dragon)); + ASSERT_FALSE(knight->accept(knight)); + ASSERT_FALSE(knight->accept(elf)); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}