diff --git a/8303/Parfentev_Leonid/lab1/Makefile b/8303/Parfentev_Leonid/lab1/Makefile new file mode 100644 index 000000000..e58f66d8f --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/Makefile @@ -0,0 +1,16 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab1/catapult_units.hpp b/8303/Parfentev_Leonid/lab1/catapult_units.hpp new file mode 100644 index 000000000..26a217f7d --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/catapult_units.hpp @@ -0,0 +1,145 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + CatapultAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab1/common_policies.hpp b/8303/Parfentev_Leonid/lab1/common_policies.hpp new file mode 100644 index 000000000..1681e07bc --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/common_policies.hpp @@ -0,0 +1,82 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + BasicMovement(int n) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } +}; + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } +}; + +class DefenseLevelDeco: public DefensePolicy { + // Controls nested policy lifetime + DefensePolicy *_p; + AttackKind _kind; + double _lvl; + +public: + DefenseLevelDeco(DefensePolicy *p, + AttackKind kind, + double level) + :_p{p}, _kind{kind}, _lvl{level} {} + + ~DefenseLevelDeco() + { + delete _p; + } + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return _p->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab1/demo.cpp b/8303/Parfentev_Leonid/lab1/demo.cpp new file mode 100644 index 000000000..82a71b3fa --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/demo.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + + +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} +static const std::map unit_chars { + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +}; +#undef UNIT_ENTRY + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +std::ostream & +operator<<(std::ostream &os, Map *map) +{ + for (MapIter iter = map->begin(); + iter != map->end(); + iter.advance(1)) { + + if (Unit *u = iter.unit()) { + os << unit_chars.at(std::type_index{typeid(*u)}); + } else { + os << "."; + } + + os << ((iter.x() == map->width() - 1) ? "\n" : " "); + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP"; +} + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " at " << sw1_iter.point() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " at " << u->position() + << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u << " at " << u->position() + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} diff --git a/8303/Parfentev_Leonid/lab1/demo.hpp b/8303/Parfentev_Leonid/lab1/demo.hpp new file mode 100644 index 000000000..8141a6bdc --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/demo.hpp @@ -0,0 +1,8 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); + +#endif diff --git a/8303/Parfentev_Leonid/lab1/main.cpp b/8303/Parfentev_Leonid/lab1/main.cpp new file mode 100644 index 000000000..1568f6fd3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/main.cpp @@ -0,0 +1,16 @@ +#include + +#include "demo.hpp" + +int +main(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); +} diff --git a/8303/Parfentev_Leonid/lab1/map.cpp b/8303/Parfentev_Leonid/lab1/map.cpp new file mode 100644 index 000000000..2feecf165 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/map.cpp @@ -0,0 +1,50 @@ +#include "point.hpp" +#include "unit.hpp" +#include "map.hpp" + + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.placeable()) + return MapIter::makeNull(); + + cell.setPlaceable(u); + u->setPosition(pt); + + ++_units_count; + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.placeable()); + + if (u) { + --_units_count; + cell.setPlaceable(nullptr); + u->unsetPosition(); + } + + return u; +} + +Unit * +MapIter::unit() const +{ + return dynamic_cast(_it.cell().placeable()); +} diff --git a/8303/Parfentev_Leonid/lab1/map.hpp b/8303/Parfentev_Leonid/lab1/map.hpp new file mode 100644 index 000000000..2a84ebb31 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/map.hpp @@ -0,0 +1,97 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include "rectmap.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + bool occupied() { return unit() != nullptr; } + // other placeable types in the future +}; + +class Map { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + +public: + Map(int w, int h) + :_rm{w, h} {} + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab1/melee_units.hpp b/8303/Parfentev_Leonid/lab1/melee_units.hpp new file mode 100644 index 000000000..f36626401 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/melee_units.hpp @@ -0,0 +1,88 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + MeleeAttack(AttackKind kind, int base_dmg) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab1/pathfinder.cpp b/8303/Parfentev_Leonid/lab1/pathfinder.cpp new file mode 100644 index 000000000..4bf6c470d --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.occupied()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab1/pathfinder.hpp b/8303/Parfentev_Leonid/lab1/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab1/placeable.hpp b/8303/Parfentev_Leonid/lab1/placeable.hpp new file mode 100644 index 000000000..b7da086ea --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/placeable.hpp @@ -0,0 +1,34 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab1/point.cpp b/8303/Parfentev_Leonid/lab1/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab1/point.hpp b/8303/Parfentev_Leonid/lab1/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab1/ranged_units.hpp b/8303/Parfentev_Leonid/lab1/ranged_units.hpp new file mode 100644 index 000000000..24f296223 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/ranged_units.hpp @@ -0,0 +1,91 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + RangedAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab1/rectmap.cpp b/8303/Parfentev_Leonid/lab1/rectmap.cpp new file mode 100644 index 000000000..a9fcff46a --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/rectmap.cpp @@ -0,0 +1,53 @@ +#include "rectmap.hpp" + + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +// Vec2 +// RectMapIter::delta(const RectMapIter &o) const +// { +// return o.point().delta(point()); +// } + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab1/rectmap.hpp b/8303/Parfentev_Leonid/lab1/rectmap.hpp new file mode 100644 index 000000000..b780bd000 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/rectmap.hpp @@ -0,0 +1,85 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" + + +class Cell { + Placeable *_p = nullptr; + +public: + Cell() {} + + ~Cell() + { + delete _p; + } + + Placeable *placeable() const { return _p; } + void setPlaceable(Placeable *p) { _p = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + // Vec2 delta(const RectMapIter &o) const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab1/report.pdf b/8303/Parfentev_Leonid/lab1/report.pdf new file mode 100644 index 000000000..0ccc9c36a Binary files /dev/null and b/8303/Parfentev_Leonid/lab1/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab1/unit.cpp b/8303/Parfentev_Leonid/lab1/unit.cpp new file mode 100644 index 000000000..ef46978a2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/unit.cpp @@ -0,0 +1,5 @@ +#include + +#include "unit.hpp" + +std::default_random_engine global_random {}; diff --git a/8303/Parfentev_Leonid/lab1/unit.hpp b/8303/Parfentev_Leonid/lab1/unit.hpp new file mode 100644 index 000000000..75fc66d17 --- /dev/null +++ b/8303/Parfentev_Leonid/lab1/unit.hpp @@ -0,0 +1,152 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class Unit: public Placeable { + // Controls the lifetime of policies once they are given to the unit + // through the constructor. + MovePolicy *_move_policy; + AttackPolicy *_attack_policy; + DefensePolicy *_defence_policy; + + int _health, _base_health; + +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :_move_policy{move}, + _attack_policy{attack}, + _defence_policy{defense}, + _health{base_health}, + _base_health{base_health} {} + + ~Unit() + { + delete _move_policy; + delete _attack_policy; + delete _defence_policy; + } + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + void + takeDamage(int dmg) { _health -= dmg; } + + bool + canMove(MapIter to) const + { + return _move_policy->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return _attack_policy->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return _attack_policy->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return _attack_policy->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return _defence_policy->actualDamage(this, kind, base); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/Makefile b/8303/Parfentev_Leonid/lab2/Makefile new file mode 100644 index 000000000..bff746c2f --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/Makefile @@ -0,0 +1,19 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp event.hpp base.hpp object_w_health.hpp \ + landscape.hpp landscape_types.hpp neutral_object.hpp \ + neutral_object_types.hpp unit_factory.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o \ + event.o base.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab2/base.cpp b/8303/Parfentev_Leonid/lab2/base.cpp new file mode 100644 index 000000000..1238ec33f --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/base.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +#include "unit.hpp" +#include "unit_factory.hpp" +#include "event.hpp" +#include "base.hpp" + + +bool +Base::canCreateUnit(const std::string &key) const +{ + return _cs->canCreate(key); +} + +Unit * +Base::createUnit(const std::string &key) +{ + if (unitsCount() == maxUnitsCount()) { + return nullptr; + } + + if (!_cs->canCreate(key)) { + return nullptr; + } + + Unit *u = _cs->create(key); + + if (addUnit(u) < 0) { + delete u; + return nullptr; + } + + return u; +} + +bool +Base::setMaxUnitsCount(int m) +{ + if (m < unitsCount()) { + return false; + } + _max_count = m; + return true; +} + +int +Base::addUnit(Unit *u) +{ + if (maxUnitsCount() >= 0 + && unitsCount() == maxUnitsCount()) { + return -1; + } + + _units[_next_idx] = u; + u->subscribe(this); + // u->emit(new UnitAddedEvent {u}); + + return _next_idx++; +} + +void +Base::removeUnit(Unit *u) +{ + u->unsubscribe(this); + + for (auto iter = _units.begin(); + iter != _units.end(); + ++iter) { + if (iter->second == u) { + _units.erase(iter); + break; + } + } +} + +Unit * +Base::getUnitById(int id) const +{ + auto iter = _units.find(id); + return (iter != _units.end()) + ? iter->second + : nullptr; +} + +void +Base::handle(Event *e) +{ + EventForwarder::handle(e); + + if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } else if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } +} + +Base::~Base() +{ + for (auto p: _units) { + p.second->unsubscribe(this); + } + delete _cs; +} diff --git a/8303/Parfentev_Leonid/lab2/base.hpp b/8303/Parfentev_Leonid/lab2/base.hpp new file mode 100644 index 000000000..7ad4c826a --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/base.hpp @@ -0,0 +1,89 @@ +#ifndef _H_BASE_HPP +#define _H_BASE_HPP + +#include +#include +#include + +#include "placeable.hpp" +#include "event.hpp" +#include "unit.hpp" + + +class UnitCreationStrategy { +public: + virtual bool canCreate(const std::string &key) const =0; + virtual std::vector keys() const =0; + virtual Unit *create(const std::string &key) =0; + + virtual ~UnitCreationStrategy() {} +}; + +class Base: public Placeable, + public EventForwarder { + + std::map _units {}; + int _next_idx = 0; + int _max_count = -1; + + UnitCreationStrategy *_cs; + +public: + Base(UnitCreationStrategy *cs) + :_cs{cs} {} + + bool + canCreateUnit(const std::string &key) const; + Unit * + createUnit(const std::string &key); + + int + unitsCount() const { return (int)_units.size(); } + bool + setMaxUnitsCount(int m); + int + maxUnitsCount() const { return _max_count; } + + int + addUnit(Unit *u); + void + removeUnit(Unit *u); + Unit * + getUnitById(int id) const; + + class unitsIter { + using real_iter_t = std::map::const_iterator; + real_iter_t _iter; + + public: + unitsIter(real_iter_t it) + :_iter{it} {} + + int id() const { return _iter->first; } + Unit *unit() const { return _iter->second; } + unitsIter &operator++() { ++_iter; return *this; } + unitsIter operator++(int) { unitsIter x{_iter}; ++x; return x; } + bool + operator==(const unitsIter &o) const + { + return _iter == o._iter; + } + bool + operator!=(const unitsIter &o) const + { + return !(*this == o); + } + }; + + unitsIter + unitsBegin() const { return unitsIter{_units.begin()}; } + unitsIter + unitsEnd() const { return unitsIter{_units.end()}; } + + virtual void + handle(Event *e) override; + + virtual ~Base() override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/catapult_units.hpp b/8303/Parfentev_Leonid/lab2/catapult_units.hpp new file mode 100644 index 000000000..26a217f7d --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/catapult_units.hpp @@ -0,0 +1,145 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + CatapultAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab2/common_policies.hpp b/8303/Parfentev_Leonid/lab2/common_policies.hpp new file mode 100644 index 000000000..4a99320aa --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/common_policies.hpp @@ -0,0 +1,181 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include + +#include "unit.hpp" +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + BasicMovement(int n) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } +}; + +class NestedMovement: public MovePolicy, + public MovePolicyContainer { +public: + using MovePolicyContainer::MovePolicyContainer; +}; + +class ModifyingMovePolicy: public NestedMovement { + int _max; + +public: + ModifyingMovePolicy(MovePolicy *p, int max_dist) + :NestedMovement{p}, _max{max_dist} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _max}; + if (!pf.run()) + return false; + + return movePolicy()->canMove(u, to); + } +}; + + + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } +}; + +class NestedDefense: public DefensePolicy, + public DefensePolicyContainer { +public: + using DefensePolicyContainer::DefensePolicyContainer; +}; + +class DefenseLevelDeco: public NestedDefense { + // Controls nested policy lifetime + AttackKind _kind; + double _lvl; + +public: + DefenseLevelDeco(DefensePolicy *p, + AttackKind kind, + double level) + :NestedDefense{p}, _kind{kind}, _lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return defensePolicy()->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } +}; + +class MultiplierDefensePolicy: public NestedDefense { + double _mul; + +public: + MultiplierDefensePolicy(DefensePolicy *p, double mul) + :NestedDefense{p}, _mul{mul} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + DamageSpec ds = defensePolicy()->actualDamage(u, kind, base); + ds.scale(1/_mul); + return ds; + } +}; + + + +class AttackForbidden: public AttackPolicy { +public: + using AttackPolicy::AttackPolicy; + + virtual bool + canAttackTo(const Unit *, MapIter) override + { + return false; + } + + virtual std::pair + baseAttack(const Unit *, MapIter) override + { + return std::make_pair(AttackKind::invalid, 0); + } +}; + +class NestedAttack: public AttackPolicy, + public AttackPolicyContainer { +public: + using AttackPolicyContainer::AttackPolicyContainer; +}; + +class MultiplierAttackPolicy: public NestedAttack { + double _mul; + +public: + MultiplierAttackPolicy(AttackPolicy *p, double mul) + :NestedAttack{p}, _mul{mul} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return attackPolicy()->canAttackTo(u, to); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + auto ba = attackPolicy()->baseAttack(u, to); + return std::make_pair(ba.first, (int)(ba.second * _mul)); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab2/demo.cpp b/8303/Parfentev_Leonid/lab2/demo.cpp new file mode 100644 index 000000000..6448f7a1f --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/demo.cpp @@ -0,0 +1,538 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + +#include "base.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "unit_factory.hpp" + + +static const std::map class_chars { +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T, x) \ + {std::type_index{typeid(landscapes::T)}, x} + LANDSCAPE_ENTRY(Normal, '.'), + LANDSCAPE_ENTRY(Swamp, '='), + LANDSCAPE_ENTRY(Forest, '*'), +#undef LANDSCAPE_ENTRY +}; + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +std::ostream & +operator<<(std::ostream &os, Map *map) +{ + for (MapIter iter = map->begin(); + iter != map->end(); + iter.advance(1)) { + + if (Unit *u = iter.unit()) { + os << class_chars.at(std::type_index{typeid(*u)}); + } else { + auto *l = iter.landscape(); + os << class_chars.at(std::type_index{typeid(*l)}); + } + + os << ((iter.x() == map->width() - 1) ? "\n" : " "); + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP at " + << u->position(); +} + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} + +class EventPrinter: public EventListener { + std::ostream *_os; + std::string _prefix; + +public: + EventPrinter(std::ostream &os, const std::string &prefix) + :_os{&os}, _prefix{prefix} {} + + virtual void + handle(Event *e) override + { + if (dynamic_cast(e)) + return; + + (*_os) << _prefix << ": "; + + if (auto *ee = dynamic_cast(e)) { + (*_os) << "Unit added: " << ee->unit() << "\n"; + + } else if (auto *ee = dynamic_cast(e)) { + (*_os) << "Unit died: " << ee->unit() << "\n"; + + } else if (auto *ee = dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " takes " + << ee->damage() << " health points of damage\n"; + + } else if (auto *ee = dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " gets healed by " + << ee->health() << " health points\n"; + + } else if (auto *ee = dynamic_cast(e)) { + (*_os) << "Unit " << ee->attacker() + << " attacked another unit " << ee->target() << "\n"; + + } else if (auto *ee = dynamic_cast(e)) { + (*_os) << "Unit " << ee->target() + << " was attacked by another unit " + << ee->attacker() << "\n"; + + } else { + (*_os) << "Unknown event\n"; + } + } +}; + +MapIter +doAttack(Unit *u, MapIter to) +{ + if (!u->canAttackTo(to)) { + return MapIter::makeNull(); + + } else { + auto pos = u->actualPosition(to); + + if (Unit *targ = pos.unit()) { + auto dam = u->baseAttack(to); + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + return pos; + } + + return MapIter::makeNull(); + } +} + +class FactoryTable: public UnitCreationStrategy { + std::map _tab {}; + + void + registerFactory(const std::string &key, UnitFactory *f) + { + _tab[key] = f; + } + +public: + FactoryTable() + { +#define REG(k, T) \ + registerFactory( \ + k, new SimpleUnitFactory {}) + REG("swordsman", Swordsman); + REG("spearsman", Spearsman); + REG("cavalry", Cavalry); + REG("archer", Archer); + REG("slinger", Slinger); + REG("onager", Onager); + REG("boltthrower", BoltThrower); +#undef REG + } + + virtual bool + canCreate(const std::string &key) const override + { + return _tab.find(key) != _tab.end(); + } + + virtual std::vector + keys() const override + { + std::vector keys {}; + for (auto p: _tab) { + keys.push_back(p.first); + } + return keys; + } + + virtual Unit * + create(const std::string &key) override + { + auto iter = _tab.find(key); + if (iter == _tab.end()) { + return nullptr; + } + + return iter->second->create(); + } + + virtual ~FactoryTable() override + { + for (auto p: _tab) { + delete p.second; + } + } +}; + +struct SimpleGame { + Map *map; + Base *b1, *b2; + EventPrinter *pr1, *pr2; + + explicit SimpleGame(int w=10, int h=10, + int x1=1, int y1=1, + int x2=9, int y2=9) + { + map = new Map {w, h}; + b1 = new Base {new FactoryTable {}}; + pr1 = new EventPrinter {std::cout, "Base 1"}; + + map->addBase(b1, {x1, y1}); + b1->subscribe(pr1); + + b2 = new Base {new FactoryTable {}}; + pr2 = new EventPrinter {std::cout, "Base 2"}; + + map->addBase(b2, {x2, y2}); + b2->subscribe(pr2); + } + + ~SimpleGame() + { + delete map; + delete pr1; + delete pr2; + } +}; + +void +demo4() +{ + SimpleGame g {}; + + Unit *u1 = new units::Swordsman {}; + Unit *u2 = new units::Swordsman {}; + + g.b1->addUnit(u1); + g.b2->addUnit(u2); + + g.map->addUnit(u1, {3, 3}); + g.map->addUnit(u2, {4, 3}); + + while (u1->alive() + && u2->alive()) { + doAttack(u1, g.map->iterAt(u2->position())); + if (u2->alive()) { + doAttack(u2, g.map->iterAt(u1->position())); + } + } +} + +MapIter +doMove(Map *map, const Unit *u, Vec2 pt) +{ + auto from = map->iterAt(u->position()); + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + return MapIter::makeNull(); + } + + return map->addUnit(map->removeUnitAt(from), pt); +} + +Unit * +setupUnit(Base *base, const std::string &k, Map *map, Vec2 pt) +{ + Unit *u = base->createUnit(k); + + if (map->addUnit(u, pt).null()) { + base->removeUnit(u); + delete u; + return nullptr; + } + + return u; +} + +void +demo5() +{ + SimpleGame g {}; + + // 2,2 .. 5,5: swamp + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + g.map->setLandscape(new landscapes::Swamp {}, + {2+i, 2+j}); + } + } + + // 1,7 .. 6,9: forest + for (int j = 0; j < 2; ++j) { + for (int i = 0; i < 5; ++i) { + g.map->setLandscape(new landscapes::Forest {}, + {1+i, 7+j}); + } + } + + auto u1 = setupUnit(g.b1, "swordsman", g.map, {2, 2}); + auto u2 = setupUnit(g.b2, "swordsman", g.map, {3, 2}); + + if (doMove(g.map, u1, {0, 2}).null()) { + std::cout << "Can't move " << u1 << " across 2 cells\n"; + } else { + std::cout << "Moved " << u1 << " across 2 cells \n"; + } + + std::cout << "u1: " << u1 << "\n"; + + if (doAttack(u1, g.map->iterAt(u2->position())).null()) { + std::cout << "Can't attack in swamp\n"; + } else { + std::cout << "Attacked in a swamp\n"; + } + + std::cout << "u2: " << u2 << "\n"; + + doMove(g.map, u1, {1, 2}); + doMove(g.map, u2, {2, 3}); + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + doAttack(u1, g.map->iterAt(u2->position())); + + auto u3 = setupUnit(g.b1, "spearsman", g.map, {3, 8}); + auto u4 = setupUnit(g.b1, "spearsman", g.map, {7, 8}); + auto u5 = setupUnit(g.b2, "onager", g.map, {5, 5}); + + while (u3->alive()) + doAttack(u5, g.map->iterAt(u3->position())); + + while (u4->alive()) + doAttack(u5, g.map->iterAt(u4->position())); +} + +bool +doUseObject(MapIter u_iter) +{ + auto *u = u_iter.unit(); + auto *n = u_iter.neutralObject(); + + if (!n->canUse(u, u_iter)) { + return false; + } + + n->onUse(u, u_iter); + return true; +} + +void +demo6() +{ + SimpleGame g {}; + + auto *u1 = setupUnit(g.b1, "slinger", g.map, {1, 5}); + auto *u2 = setupUnit(g.b2, "swordsman", g.map, {6, 5}); + + auto u1_iter = g.map->iterAt(u1->position()); + auto u2_iter = g.map->iterAt(u2->position()); + + g.map->addNeutralObject(new objects::Tower {}, {1, 5}); + g.map->addNeutralObject(new objects::HealingWell {}, {6, 5}); + + if (doAttack(u1, u2_iter).null()) { + std::cout << u1 << " can't reach " << u2 << "\n"; + } else { + std::cout << u1 << " somehow reached " << u2 << "\n"; + } + + if (doUseObject(u1_iter)) { + std::cout << u1 << " used the tower\n"; + } else { + std::cout << u1 << " can't use the tower\n"; + } + + if (doAttack(u1, u2_iter).null()) { + std::cout << u1 << " still can't reach " << u2 << "\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + if (doUseObject(u2_iter)) { + std::cout << u2 << " used the healing well\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; +} diff --git a/8303/Parfentev_Leonid/lab2/demo.hpp b/8303/Parfentev_Leonid/lab2/demo.hpp new file mode 100644 index 000000000..555a0b3e7 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/demo.hpp @@ -0,0 +1,11 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); +void demo4(); +void demo5(); +void demo6(); + +#endif diff --git a/8303/Parfentev_Leonid/lab2/event.cpp b/8303/Parfentev_Leonid/lab2/event.cpp new file mode 100644 index 000000000..1de27f8a9 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/event.cpp @@ -0,0 +1,31 @@ +#include "event.hpp" + +void +EventEmitter::emit_shared(Event *e) +{ + for (auto iter = _listeners.begin(); iter != _listeners.end();) { + auto *listener = *iter++; + // note: the listener may safely unsubscribe when handling the + // event. + listener->handle(e); + } +} + +void +EventEmitter::emit(Event *e) +{ + emit_shared(e); + delete e; +} + +void +EventEmitter::subscribe(EventListener *l) +{ + _listeners.insert(l); +} + +void +EventEmitter::unsubscribe(EventListener *l) +{ + _listeners.erase(l); +} diff --git a/8303/Parfentev_Leonid/lab2/event.hpp b/8303/Parfentev_Leonid/lab2/event.hpp new file mode 100644 index 000000000..1eab3d139 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/event.hpp @@ -0,0 +1,117 @@ +#ifndef _H_EVENT_HPP +#define _H_EVENT_HPP + +#include + +class Unit; +class EventListener; +class Event; + +class EventEmitter { + std::set _listeners {}; + +public: + void emit_shared(Event *e); + void emit(Event *e); + + void subscribe(EventListener *l); + void unsubscribe(EventListener *l); + + virtual ~EventEmitter() {} +}; + +class Event { +public: + virtual ~Event() {} +}; + + + +class UnitEvent: public Event { + Unit *_u; + +public: + UnitEvent(Unit *u) :_u{u} {} + + Unit *unit() const { return _u; } +}; + +class UnitDeathEvent: public UnitEvent { +public: + using UnitEvent::UnitEvent; +}; + +class UnitAddedEvent: public UnitEvent { +public: + using UnitEvent::UnitEvent; +}; + +class UnitLiveDeletedEvent: public UnitEvent { +public: + using UnitEvent::UnitEvent; +}; + +class UnitTakesDamageEvent: public Event { + Unit *_u; + int _dmg; + +public: + UnitTakesDamageEvent(Unit *u, int dmg) + :_u{u}, _dmg{dmg} {} + + Unit *unit() const { return _u; } + int damage() const { return _dmg; } +}; + +class UnitGetsHealedEvent: public Event { + Unit *_u; + int _hp; + +public: + UnitGetsHealedEvent(Unit *u, int hp) + :_u{u}, _hp{hp} {} + + Unit *unit() const { return _u; } + int health() const { return _hp; } +}; + +class AttackEvent: public Event { + Unit *_a, *_b; + +public: + AttackEvent(Unit *a, Unit *b) :_a{a}, _b{b} {} + + Unit *attacker() const { return _a; } + Unit *target() const { return _b; } +}; + +class UnitWasAttackedEvent: public AttackEvent { +public: + using AttackEvent::AttackEvent; +}; + +class UnitAttackedEvent: public AttackEvent { +public: + using AttackEvent::AttackEvent; +}; + + + +class EventListener { +public: + virtual void handle(Event *e) =0; + + virtual ~EventListener() {} +}; + +class EventForwarder: public EventEmitter, + public EventListener { +public: + virtual void + handle(Event *e) override + { + emit_shared(e); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/landscape.hpp b/8303/Parfentev_Leonid/lab2/landscape.hpp new file mode 100644 index 000000000..7245da6b3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/landscape.hpp @@ -0,0 +1,25 @@ +#ifndef _H_LANDSCAPE_HPP +#define _H_LANDSCAPE_HPP + + +class Unit; + +class Landscape { +public: + virtual void onEnter(Unit *u) =0; + virtual void onLeave(Unit *u) =0; + + virtual ~Landscape() {} +}; + +namespace landscapes { + + class Normal: public Landscape { + public: + virtual void onEnter(Unit *) override {} + virtual void onLeave(Unit *) override {} + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab2/landscape_types.hpp b/8303/Parfentev_Leonid/lab2/landscape_types.hpp new file mode 100644 index 000000000..268521e35 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/landscape_types.hpp @@ -0,0 +1,70 @@ +#ifndef _H_LANDSCAPE_TYPES_HPP +#define _H_LANDSCAPE_TYPES_HPP + +#include "landscape.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "common_policies.hpp" + + +namespace landscapes { + + // Swamp: max speed is 1; attacking is forbidden + class Swamp: public Landscape { + ModifyingMovePolicy *_p; + AttackPolicy *_prev, *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _p = new ModifyingMovePolicy {u->movePolicy(), 1}; + u->setMovePolicy(_p); + + _prev = u->attackPolicy(); + _cur = new AttackForbidden {}; + u->setAttackPolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *mpc = u->findMoveContainerOf(_p)) { + mpc->setMovePolicy(_p->movePolicy()); + _p->setMovePolicy(nullptr); + delete _p; + _p = nullptr; + } + + // our policy might’ve been wrapped into something + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + delete _cur; + _cur = nullptr; + } + } + }; + + class Forest: public Landscape { + DefensePolicy *_prev; + MultiplierDefensePolicy *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _prev = u->defensePolicy(); + _cur = new MultiplierDefensePolicy {_prev, 2.0}; + u->setDefensePolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *dpc = u->findDefenseContainerOf(_cur)) { + dpc->setDefensePolicy(_prev); + _cur->setDefensePolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab2/main.cpp b/8303/Parfentev_Leonid/lab2/main.cpp new file mode 100644 index 000000000..d601161bb --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/main.cpp @@ -0,0 +1,25 @@ +#include + +#include "demo.hpp" + +int +main(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); + + std::cout << "\nDemo 4\n"; + demo4(); + + std::cout << "\nDemo 5\n"; + demo5(); + + std::cout << "\nDemo 6\n"; + demo6(); +} diff --git a/8303/Parfentev_Leonid/lab2/map.cpp b/8303/Parfentev_Leonid/lab2/map.cpp new file mode 100644 index 000000000..ac83b9aa1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/map.cpp @@ -0,0 +1,144 @@ +#include "point.hpp" +#include "unit.hpp" +#include "placeable.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "landscape.hpp" +#include "map.hpp" + + +Map::Map(int w, int h) + :_rm{w, h} +{ + for (auto rmiter = _rm.iterAt({0, 0}); + rmiter.y() < _rm.height(); + rmiter.advance(1)) { + rmiter.cell().setLandscape(new landscapes::Normal {}); + } +} + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.unit()) + return MapIter::makeNull(); + + cell.setUnit(u); + u->setPosition(pt); + + ++_units_count; + + cell.landscape()->onEnter(u); + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.unit()); + + if (u) { + --_units_count; + cell.landscape()->onLeave(u); + if (auto *n = dynamic_cast(cell.object())) { + n->onLeave(u); + } + + cell.setUnit(nullptr); + u->unsetPosition(); + } + + return u; +} + +MapIter +Map::addPlaceable(Placeable *p, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.object()) { + return MapIter::makeNull(); + } + + cell.setObject(p); + + return MapIter{rmiter}; +} + +MapIter +Map::addBase(Base *b, Vec2 pt) +{ + return addPlaceable(b, pt); +} + +MapIter +Map::addNeutralObject(NeutralObject *n, Vec2 pt) +{ + return addPlaceable(n, pt); +} + +void +Map::setLandscape(Landscape *l, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + cell.setLandscape(l); +} + +Unit * +MapIter::unit() const +{ + return _it.cell().unit(); +} + +bool +MapIter::moveUnitTo(MapIter to) const +{ + auto &sc = _it.cell(); + auto &dc = to._it.cell(); + + if (!sc.unit() + || dc.unit()) { + return false; + } + + Unit *u = sc.unit(); + + dc.setUnit(u); + u->setPosition(to.point()); + sc.setUnit(nullptr); + + return true; +} + +Base * +MapIter::base() const +{ + return dynamic_cast(_it.cell().object()); +} + +NeutralObject * +MapIter::neutralObject() const +{ + return dynamic_cast(_it.cell().object()); +} + +Landscape * +MapIter::landscape() const +{ + return _it.cell().landscape(); +} diff --git a/8303/Parfentev_Leonid/lab2/map.hpp b/8303/Parfentev_Leonid/lab2/map.hpp new file mode 100644 index 000000000..579ab9b75 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/map.hpp @@ -0,0 +1,106 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include "rectmap.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; +class NeutralObject; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + bool moveUnitTo(MapIter to) const; + + Base *base() const; + NeutralObject *neutralObject() const; + Landscape *landscape() const; +}; + +class Map { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + + MapIter addPlaceable(Placeable *p, Vec2 pt); + +public: + Map(int w, int h); + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + MapIter addBase(Base *b, Vec2 pt); + MapIter addNeutralObject(NeutralObject *n, Vec2 pt); + void setLandscape(Landscape *l, Vec2 pt); + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/melee_units.hpp b/8303/Parfentev_Leonid/lab2/melee_units.hpp new file mode 100644 index 000000000..f36626401 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/melee_units.hpp @@ -0,0 +1,88 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + MeleeAttack(AttackKind kind, int base_dmg) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab2/neutral_object.hpp b/8303/Parfentev_Leonid/lab2/neutral_object.hpp new file mode 100644 index 000000000..1807cabda --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/neutral_object.hpp @@ -0,0 +1,21 @@ +#ifndef _H_NEUTRAL_OBJECT_HPP +#define _H_NEUTRAL_OBJECT_HPP + +#include "placeable.hpp" +#include "unit.hpp" +#include "map.hpp" + + +class NeutralObject: public Placeable { +public: + virtual bool canUse(const Unit *, MapIter) { return true; } + virtual void onUse(Unit *u, MapIter at) =0; + + // It’s the object’s job to determine whether it was used by the + // leaving unit. + virtual void onLeave(Unit *) {}; + + virtual ~NeutralObject() {}; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/neutral_object_types.hpp b/8303/Parfentev_Leonid/lab2/neutral_object_types.hpp new file mode 100644 index 000000000..ac4528609 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/neutral_object_types.hpp @@ -0,0 +1,184 @@ +#ifndef _H_NEUTRAL_OBJECT_TYPES_HPP +#define _H_NEUTRAL_OBJECT_TYPES_HPP + +#include + +#include "neutral_object.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "ranged_units.hpp" +#include "common_policies.hpp" + + +class ExtendedShootingRange: public NestedAttack { + double _delta; + +public: + ExtendedShootingRange(AttackPolicy *p, double delta) + :NestedAttack{p}, _delta{delta} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = to.point().distance(u->position()); + auto *a = dynamic_cast(attackPolicy()); + return dist >= a->minRange() + && dist <= (a->maxRange() + _delta); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + return attackPolicy()->baseAttack(u, to); + } +}; + + + +namespace objects { + + class HealingWell: public NeutralObject { + public: + virtual void + onUse(Unit *u, MapIter) override + { + u->heal(25); + } + }; + + class Tower: public NeutralObject { + AttackPolicy *_prev; + ExtendedShootingRange *_cur = nullptr; + + public: + virtual bool + canUse(const Unit *u, MapIter) override + { + return dynamic_cast(u); + } + + virtual void + onUse(Unit *u, MapIter) override + { + _prev = u->attackPolicy(); + _cur = new ExtendedShootingRange {_prev, 5}; + u->setAttackPolicy(_cur); + } + + virtual void + onLeave(Unit *u) override + { + if (_cur == nullptr) { + return; + } + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + _cur->setAttackPolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + }; + + class TunnelsEntrance: public NeutralObject { + public: + virtual void + onUse(Unit *, MapIter at) override + { + static const int w = 5; + + int max_n = 0; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (iter.unit() == nullptr) { + ++max_n; + } + } + } + + std::uniform_int_distribution<> distr {0, max_n-1}; + int n = distr(global_random); + + MapIter dest = MapIter::makeNull(); + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (iter.unit() != nullptr) { + continue; + } + if (!--n) { + dest = iter; + break; + } + } + } + + at.moveUnitTo(dest); + } + }; + + class WeaponSmiths: public NeutralObject { + public: + class UnitFilter { + public: + virtual bool + applicable(const Unit *u) =0; + }; + + template + class SimpleUnitFilter: public UnitFilter { + public: + virtual bool + applicable(const Unit *u) override + { + return dynamic_cast(u); + } + }; + + private: + double _mul; + UnitFilter *_filter; + + public: + explicit WeaponSmiths(double mul, UnitFilter *filter=nullptr) + :_mul{mul}, _filter{filter} {} + + virtual bool + canUse(const Unit *u, MapIter) override + { + if (_filter + && !_filter->applicable(u)) { + return false; + } + + for (const AttackPolicyContainer *apc = u; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (dynamic_cast(apc)) { + return false; + } + } + + return true; + } + + virtual void + onUse(Unit *u, MapIter) override + { + auto *prev = u->attackPolicy(); + auto *new_p = new MultiplierAttackPolicy {prev, _mul}; + u->setAttackPolicy(new_p); + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab2/object_w_health.hpp b/8303/Parfentev_Leonid/lab2/object_w_health.hpp new file mode 100644 index 000000000..e3db6d050 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/object_w_health.hpp @@ -0,0 +1,30 @@ +#ifndef _H_OBJECT_W_HEALTH_HPP +#define _H_OBJECT_W_HEALTH_HPP + + +class ObjectWithHealth { + int _health, _base_health; + +public: + ObjectWithHealth(int base_health) + :_health{base_health}, + _base_health{base_health} {} + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + virtual void + heal(int hp) { _health += hp; } + + virtual void + takeDamage(int dmg) { _health -= dmg; } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab2/pathfinder.cpp b/8303/Parfentev_Leonid/lab2/pathfinder.cpp new file mode 100644 index 000000000..865f34de2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.unit()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab2/pathfinder.hpp b/8303/Parfentev_Leonid/lab2/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/placeable.hpp b/8303/Parfentev_Leonid/lab2/placeable.hpp new file mode 100644 index 000000000..b7da086ea --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/placeable.hpp @@ -0,0 +1,34 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/point.cpp b/8303/Parfentev_Leonid/lab2/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab2/point.hpp b/8303/Parfentev_Leonid/lab2/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/ranged_units.hpp b/8303/Parfentev_Leonid/lab2/ranged_units.hpp new file mode 100644 index 000000000..1798f04ce --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/ranged_units.hpp @@ -0,0 +1,94 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + double minRange() const { return _min_distance; } + double maxRange() const { return _max_distance; } + + RangedAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab2/rectmap.cpp b/8303/Parfentev_Leonid/lab2/rectmap.cpp new file mode 100644 index 000000000..b82667c40 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/rectmap.cpp @@ -0,0 +1,55 @@ +#include "rectmap.hpp" + +#include "unit.hpp" + +Cell::~Cell() +{ + delete _u; + delete _obj; + delete _l; +} + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab2/rectmap.hpp b/8303/Parfentev_Leonid/lab2/rectmap.hpp new file mode 100644 index 000000000..afe030552 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/rectmap.hpp @@ -0,0 +1,95 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" +#include "landscape.hpp" + + +class Unit; + +class Cell { + Landscape *_l = nullptr; + Unit *_u = nullptr; + Placeable *_obj = nullptr; + +public: + Cell() {} + + ~Cell(); + + Landscape *landscape() const { return _l; } + void setLandscape(Landscape *l) + { + delete _l; + _l = l; + } + + Unit *unit() const { return _u; } + void setUnit(Unit *u) { _u = u; } + + Placeable *object() const { return _obj; } + void setObject(Placeable *p) { _obj = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/report.pdf b/8303/Parfentev_Leonid/lab2/report.pdf new file mode 100644 index 000000000..561e35d12 Binary files /dev/null and b/8303/Parfentev_Leonid/lab2/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab2/unit.cpp b/8303/Parfentev_Leonid/lab2/unit.cpp new file mode 100644 index 000000000..ef46978a2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/unit.cpp @@ -0,0 +1,5 @@ +#include + +#include "unit.hpp" + +std::default_random_engine global_random {}; diff --git a/8303/Parfentev_Leonid/lab2/unit.hpp b/8303/Parfentev_Leonid/lab2/unit.hpp new file mode 100644 index 000000000..4206a06c3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/unit.hpp @@ -0,0 +1,253 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "event.hpp" +#include "object_w_health.hpp" +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + invalid, sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + void + scale(double k) + { + base_damage *= k; + damage_spread *= k; + } + + DamageSpec + scaled(double k) const + { + auto ds = *this; + ds.scale(k); + return ds; + } + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class MovePolicyContainer { + MovePolicy *_mp; + +public: + MovePolicyContainer(MovePolicy *mp) :_mp{mp} {} + + MovePolicy *movePolicy() const { return _mp; } + void setMovePolicy(MovePolicy *mp) + { + _mp = mp; + } + virtual ~MovePolicyContainer() { delete _mp; } + + MovePolicyContainer * + findMoveContainerOf(const MovePolicy *mp) + { + for (MovePolicyContainer *mpc = this; mpc; + mpc = dynamic_cast( + mpc->movePolicy())) { + if (mpc->movePolicy() == mp) { + return mpc; + } + } + return nullptr; + } +}; + +class DefensePolicyContainer { + DefensePolicy *_dp; + +public: + DefensePolicyContainer(DefensePolicy *dp) :_dp{dp} {} + + DefensePolicy *defensePolicy() const { return _dp; } + void setDefensePolicy(DefensePolicy *dp) + { + _dp = dp; + } + virtual ~DefensePolicyContainer() { delete _dp; } + + DefensePolicyContainer * + findDefenseContainerOf(const DefensePolicy *dp) + { + for (DefensePolicyContainer *dpc = this; dpc; + dpc = dynamic_cast( + dpc->defensePolicy())) { + if (dpc->defensePolicy() == dp) { + return dpc; + } + } + return nullptr; + } +}; + +class AttackPolicyContainer { + AttackPolicy *_ap; + +public: + AttackPolicyContainer(AttackPolicy *ap) :_ap{ap} {} + + AttackPolicy *attackPolicy() const { return _ap; } + void setAttackPolicy(AttackPolicy *ap) + { + _ap = ap; + } + virtual ~AttackPolicyContainer() { delete _ap; } + + AttackPolicyContainer * + findAttackContainerOf(const AttackPolicy *ap) + { + for (AttackPolicyContainer *apc = this; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (apc->attackPolicy() == ap) { + return apc; + } + } + return nullptr; + } +}; + +class Unit: public Placeable, + public ObjectWithHealth, + public EventEmitter, + public MovePolicyContainer, + public DefensePolicyContainer, + public AttackPolicyContainer { +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :ObjectWithHealth{base_health}, + MovePolicyContainer{move}, + DefensePolicyContainer{defense}, + AttackPolicyContainer{attack} {} + + virtual void + heal(int hp) + { + emit(new UnitGetsHealedEvent {this, hp}); + ObjectWithHealth::heal(hp); + } + + virtual void + takeDamage(int dmg) + { + emit(new UnitTakesDamageEvent {this, dmg}); + + ObjectWithHealth::takeDamage(dmg); + + if (!alive()) { + emit(new UnitDeathEvent {this}); + } + } + + bool + canMove(MapIter to) const + { + return movePolicy()->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return attackPolicy()->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return attackPolicy()->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return attackPolicy()->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return defensePolicy()->actualDamage(this, kind, base); + } + + virtual ~Unit() override + { + if (alive()) { + emit(new UnitLiveDeletedEvent {this}); + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab2/unit_factory.hpp b/8303/Parfentev_Leonid/lab2/unit_factory.hpp new file mode 100644 index 000000000..5f1f475bd --- /dev/null +++ b/8303/Parfentev_Leonid/lab2/unit_factory.hpp @@ -0,0 +1,24 @@ +#ifndef _H_UNIT_FACTORY_HPP +#define _H_UNIT_FACTORY_HPP + +#include "unit.hpp" + + +class UnitFactory { +public: + virtual Unit *create() const =0; + + virtual ~UnitFactory() {} +}; + +template +class SimpleUnitFactory: public UnitFactory { +public: + virtual Unit * + create() const override + { + return new U {}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/Makefile b/8303/Parfentev_Leonid/lab3/Makefile new file mode 100644 index 000000000..d94084a84 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/Makefile @@ -0,0 +1,21 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp event.hpp base.hpp object_w_health.hpp \ + landscape.hpp landscape_types.hpp neutral_object.hpp \ + neutral_object_types.hpp unit_factory.hpp game.hpp \ + object_print.hpp event_printer.hpp iostream_player.hpp \ + mediator.hpp zombie_collector.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o \ + event.o base.o game.o object_print.o iostream_player.o mediator.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab3/base.cpp b/8303/Parfentev_Leonid/lab3/base.cpp new file mode 100644 index 000000000..4322abdc0 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/base.cpp @@ -0,0 +1,121 @@ +#include +#include +#include + +#include "unit.hpp" +#include "unit_factory.hpp" +#include "event.hpp" +#include "base.hpp" +#include "mediator.hpp" + + +bool +Base::canCreateUnit(const std::string &key) const +{ + if (unitsCount() == maxUnitsCount()) { + return false; + } + + if (!_cs->canCreate(key)) { + return false; + } + + return true; +} + +int +Base::createUnit(const std::string &key, Mediator *m) +{ + if (!canCreateUnit(key)) { + return -1; + } + + if (m->infoAt(position()).unit()) { + return false; + } + + Unit *u = _cs->create(key); + int id = addUnit(u); + + if (id < 0) { + delete u; + return -1; + } + + if (!m->spawnUnit(u, position())) { + removeUnit(u); + delete u; + return -1; + } + + return id; +} + +bool +Base::setMaxUnitsCount(int m) +{ + if (m < unitsCount()) { + return false; + } + _max_count = m; + return true; +} + +int +Base::addUnit(Unit *u) +{ + if (maxUnitsCount() >= 0 + && unitsCount() == maxUnitsCount()) { + return -1; + } + + _units[_next_idx] = u; + u->subscribe(this); + u->emit(new events::UnitAdded {u}); + + return _next_idx++; +} + +void +Base::removeUnit(Unit *u) +{ + u->unsubscribe(this); + + for (auto iter = _units.begin(); + iter != _units.end(); + ++iter) { + if (iter->second == u) { + _units.erase(iter); + break; + } + } +} + +Unit * +Base::getUnitById(int id) const +{ + auto iter = _units.find(id); + return (iter != _units.end()) + ? iter->second + : nullptr; +} + +void +Base::handle(Event *e) +{ + EventForwarder::handle(e); + + if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } else if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } +} + +Base::~Base() +{ + for (auto p: _units) { + p.second->unsubscribe(this); + } + delete _cs; +} diff --git a/8303/Parfentev_Leonid/lab3/base.hpp b/8303/Parfentev_Leonid/lab3/base.hpp new file mode 100644 index 000000000..61c75dae8 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/base.hpp @@ -0,0 +1,96 @@ +#ifndef _H_BASE_HPP +#define _H_BASE_HPP + +#include +#include +#include + +#include "placeable.hpp" +#include "event.hpp" +#include "unit.hpp" +#include "mediator.hpp" + + +class UnitCreationStrategy { +public: + virtual bool canCreate(const std::string &key) const =0; + virtual std::vector keys() const =0; + virtual Unit *create(const std::string &key) =0; + + virtual ~UnitCreationStrategy() {} +}; + +class Base: public Placeable, + public EventForwarder { + + std::map _units {}; + int _next_idx = 0; + int _max_count = -1; + + UnitCreationStrategy *_cs; + +public: + Base(UnitCreationStrategy *cs) + :_cs{cs} {} + + bool + canCreateUnit(const std::string &key) const; + int + createUnit(const std::string &key, Mediator *m); + + int + unitsCount() const { return (int)_units.size(); } + bool + setMaxUnitsCount(int m); + int + maxUnitsCount() const { return _max_count; } + + int + addUnit(Unit *u); + void + removeUnit(Unit *u); + Unit * + getUnitById(int id) const; + + class unitsIter { + using real_iter_t = std::map::const_iterator; + real_iter_t _iter; + + public: + unitsIter(real_iter_t it) + :_iter{it} {} + + int id() const { return _iter->first; } + Unit *unit() const { return _iter->second; } + unitsIter &operator++() { ++_iter; return *this; } + unitsIter + operator++(int) + { + unitsIter x{_iter}; + ++*this; + return x; + } + bool + operator==(const unitsIter &o) const + { + return _iter == o._iter; + } + bool + operator!=(const unitsIter &o) const + { + return !(*this == o); + } + }; + + unitsIter + unitsBegin() const { return unitsIter{_units.begin()}; } + unitsIter + unitsEnd() const { return unitsIter{_units.end()}; } + + virtual void + handle(Event *e) override; + + virtual ~Base() override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/catapult_units.hpp b/8303/Parfentev_Leonid/lab3/catapult_units.hpp new file mode 100644 index 000000000..26a217f7d --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/catapult_units.hpp @@ -0,0 +1,145 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + CatapultAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/common_policies.hpp b/8303/Parfentev_Leonid/lab3/common_policies.hpp new file mode 100644 index 000000000..4a99320aa --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/common_policies.hpp @@ -0,0 +1,181 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include + +#include "unit.hpp" +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + BasicMovement(int n) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } +}; + +class NestedMovement: public MovePolicy, + public MovePolicyContainer { +public: + using MovePolicyContainer::MovePolicyContainer; +}; + +class ModifyingMovePolicy: public NestedMovement { + int _max; + +public: + ModifyingMovePolicy(MovePolicy *p, int max_dist) + :NestedMovement{p}, _max{max_dist} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _max}; + if (!pf.run()) + return false; + + return movePolicy()->canMove(u, to); + } +}; + + + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } +}; + +class NestedDefense: public DefensePolicy, + public DefensePolicyContainer { +public: + using DefensePolicyContainer::DefensePolicyContainer; +}; + +class DefenseLevelDeco: public NestedDefense { + // Controls nested policy lifetime + AttackKind _kind; + double _lvl; + +public: + DefenseLevelDeco(DefensePolicy *p, + AttackKind kind, + double level) + :NestedDefense{p}, _kind{kind}, _lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return defensePolicy()->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } +}; + +class MultiplierDefensePolicy: public NestedDefense { + double _mul; + +public: + MultiplierDefensePolicy(DefensePolicy *p, double mul) + :NestedDefense{p}, _mul{mul} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + DamageSpec ds = defensePolicy()->actualDamage(u, kind, base); + ds.scale(1/_mul); + return ds; + } +}; + + + +class AttackForbidden: public AttackPolicy { +public: + using AttackPolicy::AttackPolicy; + + virtual bool + canAttackTo(const Unit *, MapIter) override + { + return false; + } + + virtual std::pair + baseAttack(const Unit *, MapIter) override + { + return std::make_pair(AttackKind::invalid, 0); + } +}; + +class NestedAttack: public AttackPolicy, + public AttackPolicyContainer { +public: + using AttackPolicyContainer::AttackPolicyContainer; +}; + +class MultiplierAttackPolicy: public NestedAttack { + double _mul; + +public: + MultiplierAttackPolicy(AttackPolicy *p, double mul) + :NestedAttack{p}, _mul{mul} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return attackPolicy()->canAttackTo(u, to); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + auto ba = attackPolicy()->baseAttack(u, to); + return std::make_pair(ba.first, (int)(ba.second * _mul)); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab3/demo.cpp b/8303/Parfentev_Leonid/lab3/demo.cpp new file mode 100644 index 000000000..983d8aba4 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/demo.cpp @@ -0,0 +1,578 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + +#include "event.hpp" +#include "event_types.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "unit_factory.hpp" + +#include "game.hpp" +#include "event_printer.hpp" +#include "iostream_player.hpp" + + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} + +MapIter +doAttack(Unit *u, MapIter to) +{ + if (!u->canAttackTo(to)) { + return MapIter::makeNull(); + + } else { + auto pos = u->actualPosition(to); + + if (Unit *targ = pos.unit()) { + auto dam = u->baseAttack(pos); + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + return pos; + } + + return MapIter::makeNull(); + } +} + +class FactoryTable: public UnitCreationStrategy { + std::map _tab {}; + + void + registerFactory(const std::string &key, UnitFactory *f) + { + _tab[key] = f; + } + +public: + FactoryTable() + { +#define REG(k, T) \ + registerFactory( \ + k, new SimpleUnitFactory {}) + REG("swordsman", Swordsman); + REG("spearsman", Spearsman); + REG("cavalry", Cavalry); + REG("archer", Archer); + REG("slinger", Slinger); + REG("onager", Onager); + REG("boltthrower", BoltThrower); +#undef REG + } + + virtual bool + canCreate(const std::string &key) const override + { + return _tab.find(key) != _tab.end(); + } + + virtual std::vector + keys() const override + { + std::vector keys {}; + for (auto p: _tab) { + keys.push_back(p.first); + } + return keys; + } + + virtual Unit * + create(const std::string &key) override + { + auto iter = _tab.find(key); + if (iter == _tab.end()) { + return nullptr; + } + + return iter->second->create(); + } + + virtual ~FactoryTable() override + { + for (auto p: _tab) { + delete p.second; + } + } +}; + +struct SimpleGame { + Map *map; + Base *b1, *b2; + EventPrinter *pr; + + explicit SimpleGame(int w=10, int h=10, + int x1=1, int y1=1, + int x2=9, int y2=9) + { + pr = new EventPrinter {std::cout}; + + map = new Map {w, h}; + + b1 = new Base {new FactoryTable {}}; + pr->setPrefix(b1, "Base 1"); + + map->addBase(b1, {x1, y1}); + b1->subscribe(pr); + + b2 = new Base {new FactoryTable {}}; + pr->setPrefix(b2, "Base 2"); + + map->addBase(b2, {x2, y2}); + b2->subscribe(pr); + } + + ~SimpleGame() + { + delete map; + delete pr; + } +}; + +void +demo4() +{ + SimpleGame g {}; + + Unit *u1 = new units::Swordsman {}; + Unit *u2 = new units::Swordsman {}; + + g.b1->addUnit(u1); + g.b2->addUnit(u2); + + g.map->addUnit(u1, {3, 3}); + g.map->addUnit(u2, {4, 3}); + + while (u1->alive() + && u2->alive()) { + doAttack(u1, g.map->iterAt(u2->position())); + if (u2->alive()) { + doAttack(u2, g.map->iterAt(u1->position())); + } + } +} + +MapIter +doMove(Map *map, const Unit *u, Vec2 pt) +{ + auto from = map->iterAt(u->position()); + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + return MapIter::makeNull(); + } + + return map->addUnit(map->removeUnitAt(from), pt); +} + +Unit * +setupUnit(Base *base, const std::string &k, Mediator *m, Vec2 pt) +{ + Unit *u = base->getUnitById(base->createUnit(k, m)); + m->teleportUnit(u, pt); + return u; +} + +void +demo5() +{ + SimpleGame g {}; + + // 2,2 .. 5,5: swamp + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + g.map->setLandscape(new landscapes::Swamp {}, + {2+i, 2+j}); + } + } + + // 1,7 .. 6,9: forest + for (int j = 0; j < 2; ++j) { + for (int i = 0; i < 5; ++i) { + g.map->setLandscape(new landscapes::Forest {}, + {1+i, 7+j}); + } + } + + auto *m = new Mediator {g.map}; + auto u1 = setupUnit(g.b1, "swordsman", m, {2, 2}); + auto u2 = setupUnit(g.b2, "swordsman", m, {3, 2}); + + if (doMove(g.map, u1, {0, 2}).null()) { + std::cout << "Can't move " << u1 << " across 2 cells\n"; + } else { + std::cout << "Moved " << u1 << " across 2 cells \n"; + } + + std::cout << "u1: " << u1 << "\n"; + + if (doAttack(u1, g.map->iterAt(u2->position())).null()) { + std::cout << "Can't attack in swamp\n"; + } else { + std::cout << "Attacked in a swamp\n"; + } + + std::cout << "u2: " << u2 << "\n"; + + doMove(g.map, u1, {1, 2}); + doMove(g.map, u2, {2, 3}); + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + doAttack(u1, g.map->iterAt(u2->position())); + + auto u3 = setupUnit(g.b1, "spearsman", m, {3, 8}); + auto u4 = setupUnit(g.b1, "spearsman", m, {7, 8}); + auto u5 = setupUnit(g.b2, "onager", m, {5, 5}); + + while (u3->alive()) + doAttack(u5, g.map->iterAt(u3->position())); + + while (u4->alive()) + doAttack(u5, g.map->iterAt(u4->position())); + + std::cout << g.map; + + delete m; +} + +void +demo6() +{ + SimpleGame g {}; + + auto *m = new Mediator {g.map}; + auto *u1 = setupUnit(g.b1, "slinger", m, {1, 5}); + auto *u2 = setupUnit(g.b2, "swordsman", m, {6, 5}); + + g.map->addNeutralObject(new objects::Tower {}, {1, 5}); + g.map->addNeutralObject(new objects::HealingWell {}, {6, 5}); + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " can't reach " << u2 << "\n"; + } else { + std::cout << u1 << " somehow reached " << u2 << "\n"; + } + + if (m->useObject(u1)) { + std::cout << u1 << " used the tower\n"; + } else { + std::cout << u1 << " can't use the tower\n"; + } + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " still can't reach " << u2 << "\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + if (m->useObject(u2)) { + std::cout << u2 << " used the healing well\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; +} + +void +demo7() +{ + class MoverPlayer: public Player { + std::string _unit_type; + int _id = -1; + + public: + using Player::Player; + + MoverPlayer(std::string name, + std::string type) + :Player{name}, _unit_type{std::move(type)} {} + + virtual bool + takeTurn(Mediator *m, Base *b) override + { + if (_id >= 0) { + Unit *u = b->getUnitById(_id); + m->moveUnitTo(u, u->position().shifted({1, 0})); + } else { + _id = b->createUnit(_unit_type, m); + } + return true; + } + }; + + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + auto *b = new Base {new FactoryTable {}}; + map->addBase(b, {3, 3}); + int b_id = g.addBase(b); + + auto *p = new MoverPlayer {"Player 1", "swordsman"}; + g.setPlayer(b_id, p); + + for (int i = 0; i < 5; ++i) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo8() +{ + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + std::stringstream s1 {}; + s1 << "base\n" + << "create spearsman\n" + << "map 0 0 9 9\n" + << "move 0 3 5\n" + << "map 0 0 9 9\n" + << "describe 3 5\n"; + + auto *b1 = new Base {new FactoryTable {}}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + std::stringstream s2 {}; + s2 << "create archer\n" + << "attack 0 3 5\n"; + + auto *b2 = new Base {new FactoryTable {}}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo9() +{ + Map *map = new Map {10, 10}; + + auto *uf = (new objects + ::WeaponSmiths + ::SimpleUnitFilter {}); + auto *ws = new objects::WeaponSmiths {2.0, uf}; + map->addNeutralObject(ws, {3, 4}); + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + Base *b1 = new Base {new FactoryTable {}}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + Base *b2 = new Base {new FactoryTable {}}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + std::stringstream s1 {}; + std::stringstream s2 {}; + + s1 << "create onager\n" + << "move 0 3 4\n" + << "use 0\n" + << "create onager\n" + << "move 1 3 2\n" + << "attack 0 7 4\n" + << "attack 1 7 2\n" + << "create swordsman\n" + << "move 0 3 5\n" + << "move 2 3 4\n" + << "use 2\n"; + + s2 << "create swordsman\n" + << "move 0 7 4\n" + << "create swordsman\n" + << "move 1 7 2\n" + << "skip skip skip skip skip\n"; + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} diff --git a/8303/Parfentev_Leonid/lab3/demo.hpp b/8303/Parfentev_Leonid/lab3/demo.hpp new file mode 100644 index 000000000..ae8bc7a95 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/demo.hpp @@ -0,0 +1,14 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); +void demo4(); +void demo5(); +void demo6(); +void demo7(); +void demo8(); +void demo9(); + +#endif diff --git a/8303/Parfentev_Leonid/lab3/event.cpp b/8303/Parfentev_Leonid/lab3/event.cpp new file mode 100644 index 000000000..b7ff78ebd --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/event.cpp @@ -0,0 +1,31 @@ +#include "event.hpp" + +void +EventEmitter::emit_shared(Event *e) const +{ + for (auto iter = _listeners.begin(); iter != _listeners.end();) { + auto *listener = *iter++; + // note: the listener may safely unsubscribe when handling the + // event. + listener->handle(e); + } +} + +void +EventEmitter::emit(Event *e) const +{ + emit_shared(e); + delete e; +} + +void +EventEmitter::subscribe(EventListener *l) +{ + _listeners.insert(l); +} + +void +EventEmitter::unsubscribe(EventListener *l) +{ + _listeners.erase(l); +} diff --git a/8303/Parfentev_Leonid/lab3/event.hpp b/8303/Parfentev_Leonid/lab3/event.hpp new file mode 100644 index 000000000..fa2d1f541 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/event.hpp @@ -0,0 +1,62 @@ +#ifndef _H_EVENT_HPP +#define _H_EVENT_HPP + +#include + +class EventListener; +class Event; + +class EventEmitter { + std::set _listeners {}; + +public: + void emit_shared(Event *e) const; + void emit(Event *e) const; + + void subscribe(EventListener *l); + void unsubscribe(EventListener *l); + + virtual ~EventEmitter() {} +}; + +class Event { +public: + virtual ~Event() {} +}; + +class EventListener { +public: + virtual void handle(Event *e) =0; + + virtual ~EventListener() {} +}; + +class EventForwarder; + +namespace events { + + class Forwarded: public Event { + Event *_e; + EventForwarder *_f; + + public: + Forwarded(Event *e, EventForwarder *f) + :_e{e}, _f{f} {} + + Event *event() const { return _e; } + EventForwarder *forwarder() const { return _f; } + }; + +} + +class EventForwarder: public EventEmitter, + public EventListener { +public: + virtual void + handle(Event *e) override + { + emit(new events::Forwarded {e, this}); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/event_printer.hpp b/8303/Parfentev_Leonid/lab3/event_printer.hpp new file mode 100644 index 000000000..4648085a0 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/event_printer.hpp @@ -0,0 +1,127 @@ +#ifndef _H_EVENT_PRINTER_HPP +#define _H_EVENT_PRINTER_HPP + +#include +#include +#include +#include + +#include "event.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "player.hpp" +#include "object_print.hpp" + + +class EventPrinter: public EventListener { + std::ostream *_os; + bool _free_os; + + std::map _prefix_map {}; + int _base_idx = 0; + + std::string + makeName(const char *base, int idx) + { + std::ostringstream oss {}; + oss << base << " " << idx; + return oss.str(); + } + +public: + EventPrinter(std::ostream &os) + :_os{&os}, _free_os{false} {} + + EventPrinter(std::ostream *os) + :_os{os}, _free_os{true} {} + + void + setPrefix(EventForwarder *f, const std::string &s) + { + _prefix_map[f] = s; + } + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + auto iter = _prefix_map.find(ee->forwarder()); + if (iter != _prefix_map.end()) { + (*_os) << iter->second << ": "; + } + + return handle(ee->event()); + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit added: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit died: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " takes " + << ee->damage() << " health points of damage\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " gets healed by " + << ee->health() << " health points\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->attacker() + << " attacked another unit " << ee->target() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->target() + << " was attacked by another unit " + << ee->attacker() << "\n"; + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " moved from " << ee->sourcePos() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " used object " << ee->neutralObject() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "(Live unit " << ((void *)ee->unit()) + << " deleted)\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + auto name = makeName("Base", ++_base_idx); + setPrefix(ee->base(), name); + (*_os) << "New base: " << name << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << " over\n"; + + } else { + (*_os) << "Unknown event\n"; + } + } + + virtual ~EventPrinter() override + { + if (_free_os) { + delete _os; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/event_types.hpp b/8303/Parfentev_Leonid/lab3/event_types.hpp new file mode 100644 index 000000000..8de61313b --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/event_types.hpp @@ -0,0 +1,161 @@ +#ifndef _H_EVENT_TYPES_HPP +#define _H_EVENT_TYPES_HPP + +#include "point.hpp" +#include "event.hpp" + + +class Unit; +class Base; +class NeutralObject; +class Player; + + +class UnitEvent: public Event { + Unit *_u; + +public: + UnitEvent(Unit *u) :_u{u} {} + + Unit *unit() const { return _u; } +}; + +class AttackEvent: public Event { + Unit *_a, *_b; + +public: + AttackEvent(Unit *a, Unit *b) :_a{a}, _b{b} {} + + Unit *attacker() const { return _a; } + Unit *target() const { return _b; } +}; + +class BaseEvent: public Event { + Base *_b; + +public: + BaseEvent(Base *b) :_b{b} {} + + Base *base() const { return _b; } +}; + +class PlayerEvent: public Event { + Player *_p; + +public: + PlayerEvent(Player *p) :_p{p} {} + + Player *player() const { return _p; } +}; + +class UnitMoveEvent: public UnitEvent { + Vec2 _src; + +public: + UnitMoveEvent(Unit *u, Vec2 src) + :UnitEvent{u}, _src{src} {} + + Vec2 sourcePos() const { return _src; } +}; + + + +namespace events { + + class UnitDeath: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitAdded: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitLiveDeleted: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitTakesDamage: public UnitEvent { + int _dmg; + + public: + UnitTakesDamage(Unit *u, int dmg) + :UnitEvent{u}, _dmg{dmg} {} + + int damage() const { return _dmg; } + }; + + class UnitGetsHealed: public UnitEvent { + int _hp; + + public: + UnitGetsHealed(Unit *u, int hp) + :UnitEvent{u}, _hp{hp} {} + + int health() const { return _hp; } + }; + + class UnitWasAttacked: public AttackEvent { + public: + using AttackEvent::AttackEvent; + }; + + class UnitAttacked: public AttackEvent { + Vec2 _tc; + + public: + UnitAttacked(Unit *u, Vec2 tc, Unit *tu) + :AttackEvent{u, tu}, _tc{tc} {} + + Vec2 targetCoordinates() const { return _tc; } + }; + + class UnitMoved: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitTeleported: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitUsedObject: public UnitEvent { + NeutralObject *_n; + + public: + UnitUsedObject(Unit *u, NeutralObject *n) + :UnitEvent{u}, _n{n} {} + + NeutralObject *neutralObject() const { return _n; } + }; + + + + class BaseAdded: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + class BaseDestroyed: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + + + class TurnStarted: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + + class TurnOver: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/game.cpp b/8303/Parfentev_Leonid/lab3/game.cpp new file mode 100644 index 000000000..1daf422a4 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/game.cpp @@ -0,0 +1,89 @@ +#include "base.hpp" +#include "game.hpp" +#include "event.hpp" +#include "event_types.hpp" + + +int +Game::addBase(Base *base) +{ + base->subscribe(_coll); + base->subscribe(this); + + _recs.push_back(BaseRecord{base, nullptr}); + + emit(new events::BaseAdded {base}); + + return (int)(_recs.size() - 1); +} + +void +Game::setPlayer(int base_idx, Player *p) +{ + Player *&p_place = _recs[base_idx].player; + if (p_place) { + delete p_place; + --_players; + } + p_place = p; + if (p) { + ++_players; + } +} + +int +Game::basesCount() const +{ + return _recs.size(); +} + +int +Game::playersCount() const +{ + return _players; +} + +Base * +Game::baseByIdx(int idx) const +{ + return _recs[idx].base; +} + +void +Game::spin() +{ + auto &rec = _recs[_next]; + Player *p = rec.player; + Base *b = rec.base; + + if (p) { + emit(new events::TurnStarted {p}); + + bool res = p->takeTurn(_med, b); + + _coll->collect(_map); + + emit(new events::TurnOver {p}); + + if (!res) { + setPlayer(_next, nullptr); + } + } + + if (++_next == (int)_recs.size()) { + _next = 0; + } +} + +Game::~Game() +{ + for (int i = 0; i < (int)_recs.size(); ++i) { + setPlayer(i, nullptr); + auto &rec = _recs[i]; + rec.base->unsubscribe(this); + rec.base->unsubscribe(_coll); + } + + delete _coll; + delete _map; +} diff --git a/8303/Parfentev_Leonid/lab3/game.hpp b/8303/Parfentev_Leonid/lab3/game.hpp new file mode 100644 index 000000000..b63690d53 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/game.hpp @@ -0,0 +1,49 @@ +#ifndef _H_GAME_HPP +#define _H_GAME_HPP + +#include + +#include "event.hpp" +#include "map.hpp" +#include "base.hpp" +#include "player.hpp" +#include "zombie_collector.hpp" +#include "mediator.hpp" + + +class Game: public EventForwarder { + Map *_map; + Mediator *_med; + + struct BaseRecord { + Base *base; + Player *player; + }; + + std::vector _recs {}; + int _next = 0; + int _players = 0; + + ZombieCollector *_coll; + +public: + Game(Map *map) + :_map{map}, + _med{new Mediator {map}}, + _coll{new ZombieCollector {}} {} + + int addBase(Base *b); + void setPlayer(int base_idx, Player *p); + + int basesCount() const; + int playersCount() const; + + Base *baseByIdx(int idx) const; + + void spin(); + + ~Game(); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab3/iostream_player.cpp b/8303/Parfentev_Leonid/lab3/iostream_player.cpp new file mode 100644 index 000000000..de2bb852d --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/iostream_player.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "game.hpp" +#include "base.hpp" +#include "iostream_player.hpp" + + +void +IostreamPlayer::addCommand(const std::string &str, + IostreamCommand *cmd) +{ + _cmd_tab[str] = cmd; +} + +IostreamPlayer::IostreamPlayer(std::string name) + :Player{name} +{ + addCommand("move", new iostream_commands::Move {}); + addCommand("attack", new iostream_commands::Attack {}); + addCommand("use", new iostream_commands::Use {}); + addCommand("create", new iostream_commands::Create {}); + addCommand("base", new iostream_commands::FindBase {}); + addCommand("units", new iostream_commands::ListUnits {}); + addCommand("describe", new iostream_commands::DescribeAt {}); + addCommand("map", new iostream_commands::PrintMap {}); + addCommand("skip", new iostream_commands::Skip {}); +} + +bool +IostreamPlayer::takeTurn(Mediator *m, Base *b) +{ + for (;;) { + std::string cmd_name; + (*_is) >> cmd_name; + + if (_is->fail()) { + return false; + } + + auto iter = _cmd_tab.find(cmd_name); + if (iter == _cmd_tab.end()) { + (*_os) << "Unknown command: \"" << cmd_name << "\"\n"; + continue; + } + + if (iter->second->execute(this, m, b)) { + break; + } + } + + return true; +} + +int +IostreamPlayer::readInt() +{ + int x; + (*_is) >> x; + return x; +} + +Unit * +IostreamPlayer::readUnitId(Base *b) +{ + int id = readInt(); + Unit *u = b->getUnitById(id); + if (!u) { + (*_os) << "No such unit: " << id << "\n"; + } + + return u; +} + +Vec2 +IostreamPlayer::readVec2() +{ + int x, y; + (*_is) >> x >> y; + return {x, y}; +} + +std::string +IostreamPlayer::readString() +{ + std::string s; + (*_is) >> s; + return s; +} diff --git a/8303/Parfentev_Leonid/lab3/iostream_player.hpp b/8303/Parfentev_Leonid/lab3/iostream_player.hpp new file mode 100644 index 000000000..19f6e928d --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/iostream_player.hpp @@ -0,0 +1,252 @@ +#ifndef _H_STDIO_PLAYER_HPP +#define _H_STDIO_PLAYER_HPP + +#include +#include +#include +#include + +#include "point.hpp" +#include "game.hpp" + +#include "object_print.hpp" + + +class IostreamPlayer; + +class IostreamCommand { +public: + // -> whether to end turn + virtual bool execute(IostreamPlayer *p, Mediator *m, Base *b) =0; + + virtual ~IostreamCommand() {} +}; + +class IostreamPlayer: public Player { + std::ostream *_os = nullptr; + std::istream *_is = nullptr; + bool _free_os, _free_is; + + std::map _cmd_tab {}; + + void + addCommand(const std::string &str, + IostreamCommand *cmd); + +public: + IostreamPlayer(std::string name); + + void + setOstream(std::ostream *os) { _os = os; _free_is = true; } + void + setOstream(std::ostream &os) { _os = &os; _free_os = false; } + + std::ostream & + ostream() const { return *_os; } + + void + setIstream(std::istream *is) { _is = is; _free_is = true; } + void + setIstream(std::istream &is) { _is = &is; _free_is = false; } + + std::istream & + istream() const { return *_is; } + + virtual bool + takeTurn(Mediator *m, Base *b) override; + + int + readInt(); + + Unit * + readUnitId(Base *b); + + Vec2 + readVec2(); + + std::string + readString(); +}; + +namespace iostream_commands { + + class Move: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + if (!m->moveUnitTo(u, to)) { + p->ostream() << "Can't move unit " << u + << " to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Attack: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + if (!m->attackTo(u, to)) { + p->ostream() << "Unit " << u + << " can't attack to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Use: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + if (!m->useObject(u)) { + p->ostream() << "Unit " << u + << " can't use any object there\n"; + return false; + } + + return true; + } + }; + + class Create: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + std::string s = p->readString(); + + if (!b->canCreateUnit(s)) { + p->ostream() << "Can't create unit of type " + << s << "\n"; + return false; + } + + int id = b->createUnit(s, m); + if (id < 0) { + p->ostream() << "Failed to create a unit of type " + << s << "\n"; + return false; + } + + p->ostream() << "New unit of type " << s + << ": " << id << "\n"; + return true; + } + }; + + class FindBase: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *, Base *b) override + { + p->ostream() << "Base: " << b << "\n"; + return false; + } + }; + + class ListUnits: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *, Base *b) override + { + p->ostream() << "Units:"; + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + p->ostream() << "- " << iter.id() + << ": " << iter.unit() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class DescribeAt: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *) override + { + auto pos = p->readVec2(); + auto info = m->infoAt(pos); + + p->ostream() << "At " << pos << "\n"; + p->ostream() + << "- Landscape: " << info.landscape() << "\n"; + + if (auto *b = info.base()) { + p->ostream() << "- Base: " << b << "\n"; + } + + if (auto *u = info.unit()) { + p->ostream() << "- Unit: " << u << "\n"; + } + + if (auto *n = info.neutralObject()) { + p->ostream() << "- Object: " << n << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class PrintMap: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *) override + { + auto from = p->readVec2(); + auto to = p->readVec2(); + + p->ostream() << "From " << from + << " to " << to << ":\n"; + + for (int y = from.y(); y < to.y(); ++y) { + for (int x = from.x(); x < to.x(); ++x) { + if (x != from.x()) { + p->ostream() << " "; + } + displayMapInfo(p->ostream(), m->infoAt({x, y})); + } + p->ostream() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class Skip: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *, Mediator *, Base *) override + { + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/landscape.hpp b/8303/Parfentev_Leonid/lab3/landscape.hpp new file mode 100644 index 000000000..7245da6b3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/landscape.hpp @@ -0,0 +1,25 @@ +#ifndef _H_LANDSCAPE_HPP +#define _H_LANDSCAPE_HPP + + +class Unit; + +class Landscape { +public: + virtual void onEnter(Unit *u) =0; + virtual void onLeave(Unit *u) =0; + + virtual ~Landscape() {} +}; + +namespace landscapes { + + class Normal: public Landscape { + public: + virtual void onEnter(Unit *) override {} + virtual void onLeave(Unit *) override {} + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/landscape_types.hpp b/8303/Parfentev_Leonid/lab3/landscape_types.hpp new file mode 100644 index 000000000..268521e35 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/landscape_types.hpp @@ -0,0 +1,70 @@ +#ifndef _H_LANDSCAPE_TYPES_HPP +#define _H_LANDSCAPE_TYPES_HPP + +#include "landscape.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "common_policies.hpp" + + +namespace landscapes { + + // Swamp: max speed is 1; attacking is forbidden + class Swamp: public Landscape { + ModifyingMovePolicy *_p; + AttackPolicy *_prev, *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _p = new ModifyingMovePolicy {u->movePolicy(), 1}; + u->setMovePolicy(_p); + + _prev = u->attackPolicy(); + _cur = new AttackForbidden {}; + u->setAttackPolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *mpc = u->findMoveContainerOf(_p)) { + mpc->setMovePolicy(_p->movePolicy()); + _p->setMovePolicy(nullptr); + delete _p; + _p = nullptr; + } + + // our policy might’ve been wrapped into something + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + delete _cur; + _cur = nullptr; + } + } + }; + + class Forest: public Landscape { + DefensePolicy *_prev; + MultiplierDefensePolicy *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _prev = u->defensePolicy(); + _cur = new MultiplierDefensePolicy {_prev, 2.0}; + u->setDefensePolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *dpc = u->findDefenseContainerOf(_cur)) { + dpc->setDefensePolicy(_prev); + _cur->setDefensePolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/main.cpp b/8303/Parfentev_Leonid/lab3/main.cpp new file mode 100644 index 000000000..742fe3e11 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/main.cpp @@ -0,0 +1,34 @@ +#include + +#include "demo.hpp" + +int +main(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); + + std::cout << "\nDemo 4\n"; + demo4(); + + std::cout << "\nDemo 5\n"; + demo5(); + + std::cout << "\nDemo 6\n"; + demo6(); + + std::cout << "\nDemo 7\n"; + demo7(); + + std::cout << "\nDemo 8\n"; + demo8(); + + std::cout << "\nDemo 9\n"; + demo9(); +} diff --git a/8303/Parfentev_Leonid/lab3/map.cpp b/8303/Parfentev_Leonid/lab3/map.cpp new file mode 100644 index 000000000..c21541c17 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/map.cpp @@ -0,0 +1,155 @@ +#include "point.hpp" +#include "unit.hpp" +#include "placeable.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "landscape.hpp" +#include "map.hpp" + + +Map::Map(int w, int h) + :_rm{w, h} +{ + for (auto rmiter = _rm.iterAt({0, 0}); + rmiter.y() < _rm.height(); + rmiter.advance(1)) { + rmiter.cell().setLandscape(new landscapes::Normal {}); + } +} + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.unit()) + return MapIter::makeNull(); + + cell.setUnit(u); + u->setPosition(pt); + + ++_units_count; + + cell.landscape()->onEnter(u); + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.unit()); + + if (u) { + --_units_count; + cell.landscape()->onLeave(u); + if (auto *n = dynamic_cast(cell.object())) { + n->onLeave(u); + } + + cell.setUnit(nullptr); + u->unsetPosition(); + } + + return u; +} + +MapIter +Map::addPlaceable(Placeable *p, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.object()) { + return MapIter::makeNull(); + } + + cell.setObject(p); + p->setPosition(pt); + + return MapIter{rmiter}; +} + +MapIter +Map::addBase(Base *b, Vec2 pt) +{ + return addPlaceable(b, pt); +} + +MapIter +Map::addNeutralObject(NeutralObject *n, Vec2 pt) +{ + return addPlaceable(n, pt); +} + +void +Map::setLandscape(Landscape *l, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + cell.setLandscape(l); +} + +MapInfo +Map::infoAt(Vec2 pt) const +{ + return MapInfo{&_rm.at(pt)}; +} + +Unit * +MapIter::unit() const +{ + return _it.cell().unit(); +} + +Base * +MapIter::base() const +{ + return dynamic_cast(_it.cell().object()); +} + +NeutralObject * +MapIter::neutralObject() const +{ + return dynamic_cast(_it.cell().object()); +} + +Landscape * +MapIter::landscape() const +{ + return _it.cell().landscape(); +} + +const Landscape * +MapInfo::landscape() const +{ + return _cell->landscape(); +} + +const Unit * +MapInfo::unit() const +{ + return _cell->unit(); +} + +const Base * +MapInfo::base() const +{ + return dynamic_cast(_cell->object()); +} + +const NeutralObject * +MapInfo::neutralObject() const +{ + return dynamic_cast(_cell->object()); +} diff --git a/8303/Parfentev_Leonid/lab3/map.hpp b/8303/Parfentev_Leonid/lab3/map.hpp new file mode 100644 index 000000000..eb150cf76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/map.hpp @@ -0,0 +1,119 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include "rectmap.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; +class NeutralObject; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + Base *base() const; + NeutralObject *neutralObject() const; + Landscape *landscape() const; +}; + +class MapInfo { + const Cell *_cell; + +public: + MapInfo(const Cell *c) + :_cell{c} {} + + const Landscape *landscape() const; + const Unit *unit() const; + const Base *base() const; + const NeutralObject *neutralObject() const; +}; + +class Map { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + + MapIter addPlaceable(Placeable *p, Vec2 pt); + +public: + Map(int w, int h); + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapInfo infoAt(Vec2 pt) const; + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + MapIter addBase(Base *b, Vec2 pt); + MapIter addNeutralObject(NeutralObject *n, Vec2 pt); + void setLandscape(Landscape *l, Vec2 pt); + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/mediator.cpp b/8303/Parfentev_Leonid/lab3/mediator.cpp new file mode 100644 index 000000000..348997877 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/mediator.cpp @@ -0,0 +1,111 @@ +#include "point.hpp" +#include "map.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "neutral_object.hpp" +#include "mediator.hpp" + + +MapInfo +Mediator::infoAt(Vec2 pt) +{ + return _map->infoAt(pt); +} + +Vec2 +Mediator::mapSize() +{ + return {_map->width(), + _map->height()}; +} + +bool +Mediator::moveUnitTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit() + || !u->canMove(ito)) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitMoved {u, from}); + return true; +} + +bool +Mediator::attackTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (!ito.unit() + || !u->canAttackTo(ito)) { + return false; + } + + auto pos = u->actualPosition(ito); + Unit *t = pos.unit(); + + u->emit(new events::UnitAttacked {u, pos.point(), t}); + + if (t) { + t->emit(new events::UnitWasAttacked {u, t}); + + auto damage_pair = u->baseAttack(pos); + auto damage_spec = t->actualDamage(damage_pair.first, + damage_pair.second); + int dmg = damage_spec.evaluate(); + + t->takeDamage(dmg); + } + + return true; +} + +bool +Mediator::useObject(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + NeutralObject *n = iter.neutralObject(); + + if (!n + || !n->canUse(u)) { + return false; + } + + n->onUse(u, this); + + u->emit(new events::UnitUsedObject {u, n}); + + return true; +} + +bool +Mediator::spawnUnit(Unit *u, Vec2 at) +{ + return !_map->addUnit(u, at).null(); +} + +bool +Mediator::teleportUnit(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit()) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitTeleported {u, from}); + return true; +} diff --git a/8303/Parfentev_Leonid/lab3/mediator.hpp b/8303/Parfentev_Leonid/lab3/mediator.hpp new file mode 100644 index 000000000..cc1549f65 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/mediator.hpp @@ -0,0 +1,31 @@ +#ifndef _H_MEDIATOR_HPP +#define _H_MEDIATOR_HPP + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + + +class Mediator { + Map *_map; + +public: + Mediator(Map *map) + :_map{map} {} + + MapInfo infoAt(Vec2 pt); + + Vec2 mapSize(); + + bool moveUnitTo(Unit *u, Vec2 to); + + bool attackTo(Unit *u, Vec2 to); + + bool useObject(Unit *u); + + bool spawnUnit(Unit *u, Vec2 at); + bool teleportUnit(Unit *u, Vec2 to); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab3/melee_units.hpp b/8303/Parfentev_Leonid/lab3/melee_units.hpp new file mode 100644 index 000000000..f36626401 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/melee_units.hpp @@ -0,0 +1,88 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + MeleeAttack(AttackKind kind, int base_dmg) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/neutral_object.hpp b/8303/Parfentev_Leonid/lab3/neutral_object.hpp new file mode 100644 index 000000000..bcb94331e --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/neutral_object.hpp @@ -0,0 +1,22 @@ +#ifndef _H_NEUTRAL_OBJECT_HPP +#define _H_NEUTRAL_OBJECT_HPP + +#include "placeable.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "mediator.hpp" + + +class NeutralObject: public Placeable { +public: + virtual bool canUse(const Unit *) { return true; } + virtual void onUse(Unit *u, Mediator *m) =0; + + // It’s the object’s job to determine whether it was used by the + // leaving unit. + virtual void onLeave(Unit *) {}; + + virtual ~NeutralObject() {}; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/neutral_object_types.hpp b/8303/Parfentev_Leonid/lab3/neutral_object_types.hpp new file mode 100644 index 000000000..203d967f6 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/neutral_object_types.hpp @@ -0,0 +1,187 @@ +#ifndef _H_NEUTRAL_OBJECT_TYPES_HPP +#define _H_NEUTRAL_OBJECT_TYPES_HPP + +#include + +#include "neutral_object.hpp" +#include "mediator.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "ranged_units.hpp" +#include "common_policies.hpp" + + +class ExtendedShootingRange: public NestedAttack { + double _delta; + +public: + ExtendedShootingRange(AttackPolicy *p, double delta) + :NestedAttack{p}, _delta{delta} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = to.point().distance(u->position()); + auto *a = dynamic_cast(attackPolicy()); + return dist >= a->minRange() + && dist <= (a->maxRange() + _delta); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + return attackPolicy()->baseAttack(u, to); + } +}; + + + +namespace objects { + + class HealingWell: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *) override + { + u->heal(25); + } + }; + + class Tower: public NeutralObject { + AttackPolicy *_prev; + ExtendedShootingRange *_cur = nullptr; + + public: + virtual bool + canUse(const Unit *u) override + { + return dynamic_cast(u); + } + + virtual void + onUse(Unit *u, Mediator *) override + { + _prev = u->attackPolicy(); + _cur = new ExtendedShootingRange {_prev, 5}; + u->setAttackPolicy(_cur); + } + + virtual void + onLeave(Unit *u) override + { + if (_cur == nullptr) { + return; + } + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + _cur->setAttackPolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + }; + + class TunnelsEntrance: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *m) override + { + static const int w = 5; + + Vec2 at = u->position(); + + int max_n = 0; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() == nullptr) { + ++max_n; + } + } + } + + std::uniform_int_distribution<> distr {0, max_n-1}; + int n = distr(global_random); + + Vec2 dest; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() != nullptr) { + continue; + } + if (!--n) { + dest = iter; + break; + } + } + } + + m->teleportUnit(u, dest); + } + }; + + class WeaponSmiths: public NeutralObject { + public: + class UnitFilter { + public: + virtual bool + applicable(const Unit *u) =0; + }; + + template + class SimpleUnitFilter: public UnitFilter { + public: + virtual bool + applicable(const Unit *u) override + { + return dynamic_cast(u); + } + }; + + private: + double _mul; + UnitFilter *_filter; + + public: + explicit WeaponSmiths(double mul, UnitFilter *filter=nullptr) + :_mul{mul}, _filter{filter} {} + + virtual bool + canUse(const Unit *u) override + { + if (_filter + && !_filter->applicable(u)) { + return false; + } + + for (const AttackPolicyContainer *apc = u; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (dynamic_cast(apc)) { + return false; + } + } + + return true; + } + + virtual void + onUse(Unit *u, Mediator *) override + { + auto *prev = u->attackPolicy(); + auto *new_p = new MultiplierAttackPolicy {prev, _mul}; + u->setAttackPolicy(new_p); + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/object_print.cpp b/8303/Parfentev_Leonid/lab3/object_print.cpp new file mode 100644 index 000000000..da59e1054 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/object_print.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "object_print.hpp" + + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T) {std::type_index{typeid(landscapes::T)}, #T} +static const std::map landscape_names { + LANDSCAPE_ENTRY(Normal), + LANDSCAPE_ENTRY(Swamp), + LANDSCAPE_ENTRY(Forest), +}; +#undef LANDSCAPE_ENTRY + +#define N_OBJECT_ENTRY(T, n) {std::type_index{typeid(objects::T)}, n} +static const std::map objects_names { + N_OBJECT_ENTRY(HealingWell, "Healing Well"), + N_OBJECT_ENTRY(Tower, "Tower"), + N_OBJECT_ENTRY(TunnelsEntrance, "Tunnels Entrance"), + N_OBJECT_ENTRY(WeaponSmiths, "Weapon Smiths"), +}; +#undef N_OBJECT_ENTRY + + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, const Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP at " + << u->position(); +} + +std::ostream & +operator<<(std::ostream &os, const Landscape *l) +{ + return os << landscape_names.at(std::type_index{typeid(*l)}); +} + +std::ostream & +operator<<(std::ostream &os, const Base *b) +{ + os << "a Base with " << b->unitsCount(); + int m = b->maxUnitsCount(); + if (m >= 0) { + os << "/" << m; + } + + return os << " units at " << b->position(); +} + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n) +{ + return os << "a " + << objects_names.at(std::type_index{typeid(*n)}) + << " at " << n->position(); +} + + + +static const std::map class_chars { +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T, x) \ + {std::type_index{typeid(landscapes::T)}, x} + LANDSCAPE_ENTRY(Normal, '.'), + LANDSCAPE_ENTRY(Swamp, '='), + LANDSCAPE_ENTRY(Forest, '*'), +#undef LANDSCAPE_ENTRY +}; + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info) +{ + if (const Unit *u = info.unit()) { + os << class_chars.at(std::type_index{typeid(*u)}); + } else if (info.base()) { + os << "+"; + } else if (info.neutralObject()) { + os << '#'; + } else { + auto *l = info.landscape(); + os << class_chars.at(std::type_index{typeid(*l)}); + } + + return os; +} + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1) +{ + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + displayMapInfo(os, map->infoAt({x, y})); + } + os << "\n"; + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, const Map *map) +{ + return displayMap(os, map, 0, 0, map->width(), map->height()); +} diff --git a/8303/Parfentev_Leonid/lab3/object_print.hpp b/8303/Parfentev_Leonid/lab3/object_print.hpp new file mode 100644 index 000000000..8df10d966 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/object_print.hpp @@ -0,0 +1,40 @@ +#ifndef _H_OBJECT_PRINT_HPP +#define _H_OBJECT_PRINT_HPP + +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "neutral_object.hpp" + +std::ostream & +operator<<(std::ostream &os, Vec2 pt); + +std::ostream & +operator<<(std::ostream &os, const Unit *u); + +std::ostream & +operator<<(std::ostream &os, const Landscape *l); + +std::ostream & +operator<<(std::ostream &os, const Base *b); + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n); + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info); + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1); + +std::ostream & +operator<<(std::ostream &os, const Map *map); + +#endif diff --git a/8303/Parfentev_Leonid/lab3/object_w_health.hpp b/8303/Parfentev_Leonid/lab3/object_w_health.hpp new file mode 100644 index 000000000..e3db6d050 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/object_w_health.hpp @@ -0,0 +1,30 @@ +#ifndef _H_OBJECT_W_HEALTH_HPP +#define _H_OBJECT_W_HEALTH_HPP + + +class ObjectWithHealth { + int _health, _base_health; + +public: + ObjectWithHealth(int base_health) + :_health{base_health}, + _base_health{base_health} {} + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + virtual void + heal(int hp) { _health += hp; } + + virtual void + takeDamage(int dmg) { _health -= dmg; } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab3/pathfinder.cpp b/8303/Parfentev_Leonid/lab3/pathfinder.cpp new file mode 100644 index 000000000..865f34de2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.unit()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab3/pathfinder.hpp b/8303/Parfentev_Leonid/lab3/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/placeable.hpp b/8303/Parfentev_Leonid/lab3/placeable.hpp new file mode 100644 index 000000000..bd7f0ef74 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/placeable.hpp @@ -0,0 +1,36 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +#include "point.hpp" + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/player.hpp b/8303/Parfentev_Leonid/lab3/player.hpp new file mode 100644 index 000000000..1f8a8ebe0 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/player.hpp @@ -0,0 +1,25 @@ +#ifndef _H_PLAYER_HPP +#define _H_PLAYER_HPP + +#include +#include + +#include "mediator.hpp" +#include "base.hpp" + + +class Player { + std::string _name; + +public: + Player(std::string name) + :_name{std::move(name)} {} + + const std::string &name() const { return _name; } + + virtual bool takeTurn(Mediator *m, Base *b) =0; + + virtual ~Player() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/point.cpp b/8303/Parfentev_Leonid/lab3/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab3/point.hpp b/8303/Parfentev_Leonid/lab3/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/ranged_units.hpp b/8303/Parfentev_Leonid/lab3/ranged_units.hpp new file mode 100644 index 000000000..1798f04ce --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/ranged_units.hpp @@ -0,0 +1,94 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + double minRange() const { return _min_distance; } + double maxRange() const { return _max_distance; } + + RangedAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab3/rectmap.cpp b/8303/Parfentev_Leonid/lab3/rectmap.cpp new file mode 100644 index 000000000..b82667c40 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/rectmap.cpp @@ -0,0 +1,55 @@ +#include "rectmap.hpp" + +#include "unit.hpp" + +Cell::~Cell() +{ + delete _u; + delete _obj; + delete _l; +} + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab3/rectmap.hpp b/8303/Parfentev_Leonid/lab3/rectmap.hpp new file mode 100644 index 000000000..a3119ffb1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/rectmap.hpp @@ -0,0 +1,99 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" +#include "landscape.hpp" + + +class Unit; + +class Cell { + Landscape *_l = nullptr; + Unit *_u = nullptr; + Placeable *_obj = nullptr; + +public: + Cell() {} + + ~Cell(); + + Landscape *landscape() const { return _l; } + void setLandscape(Landscape *l) + { + delete _l; + _l = l; + } + + Unit *unit() const { return _u; } + void setUnit(Unit *u) { _u = u; } + + Placeable *object() const { return _obj; } + void setObject(Placeable *p) { _obj = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + const Cell &at(Vec2 pt) const + { + return _storage[pt.x() + pt.y()*_w]; + } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/report.pdf b/8303/Parfentev_Leonid/lab3/report.pdf new file mode 100644 index 000000000..63be5c5ba Binary files /dev/null and b/8303/Parfentev_Leonid/lab3/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab3/unit.cpp b/8303/Parfentev_Leonid/lab3/unit.cpp new file mode 100644 index 000000000..ef46978a2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/unit.cpp @@ -0,0 +1,5 @@ +#include + +#include "unit.hpp" + +std::default_random_engine global_random {}; diff --git a/8303/Parfentev_Leonid/lab3/unit.hpp b/8303/Parfentev_Leonid/lab3/unit.hpp new file mode 100644 index 000000000..45cfef0f0 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/unit.hpp @@ -0,0 +1,258 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "event.hpp" +#include "event_types.hpp" +#include "object_w_health.hpp" +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + invalid, sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + void + scale(double k) + { + base_damage *= k; + damage_spread *= k; + } + + DamageSpec + scaled(double k) const + { + auto ds = *this; + ds.scale(k); + return ds; + } + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class MovePolicyContainer { + MovePolicy *_mp; + +public: + MovePolicyContainer(MovePolicy *mp) :_mp{mp} {} + + MovePolicy *movePolicy() const { return _mp; } + void setMovePolicy(MovePolicy *mp) + { + _mp = mp; + } + virtual ~MovePolicyContainer() { delete _mp; } + + MovePolicyContainer * + findMoveContainerOf(const MovePolicy *mp) + { + for (MovePolicyContainer *mpc = this; mpc; + mpc = dynamic_cast( + mpc->movePolicy())) { + if (mpc->movePolicy() == mp) { + return mpc; + } + } + return nullptr; + } +}; + +class DefensePolicyContainer { + DefensePolicy *_dp; + +public: + DefensePolicyContainer(DefensePolicy *dp) :_dp{dp} {} + + DefensePolicy *defensePolicy() const { return _dp; } + void setDefensePolicy(DefensePolicy *dp) + { + _dp = dp; + } + virtual ~DefensePolicyContainer() { delete _dp; } + + DefensePolicyContainer * + findDefenseContainerOf(const DefensePolicy *dp) + { + for (DefensePolicyContainer *dpc = this; dpc; + dpc = dynamic_cast( + dpc->defensePolicy())) { + if (dpc->defensePolicy() == dp) { + return dpc; + } + } + return nullptr; + } +}; + +class AttackPolicyContainer { + AttackPolicy *_ap; + +public: + AttackPolicyContainer(AttackPolicy *ap) :_ap{ap} {} + + AttackPolicy *attackPolicy() const { return _ap; } + void setAttackPolicy(AttackPolicy *ap) + { + _ap = ap; + } + virtual ~AttackPolicyContainer() { delete _ap; } + + AttackPolicyContainer * + findAttackContainerOf(const AttackPolicy *ap) + { + for (AttackPolicyContainer *apc = this; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (apc->attackPolicy() == ap) { + return apc; + } + } + return nullptr; + } +}; + +class Unit: public Placeable, + public ObjectWithHealth, + public EventEmitter, + public MovePolicyContainer, + public DefensePolicyContainer, + public AttackPolicyContainer { +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :ObjectWithHealth{base_health}, + MovePolicyContainer{move}, + DefensePolicyContainer{defense}, + AttackPolicyContainer{attack} {} + + virtual void + heal(int hp) + { + emit(new events::UnitGetsHealed {this, hp}); + ObjectWithHealth::heal(hp); + } + + virtual void + takeDamage(int dmg) + { + if (!alive()) { + return; + } + + emit(new events::UnitTakesDamage {this, dmg}); + + ObjectWithHealth::takeDamage(dmg); + + if (!alive()) { + emit(new events::UnitDeath {this}); + } + } + + bool + canMove(MapIter to) const + { + return movePolicy()->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return attackPolicy()->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return attackPolicy()->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return attackPolicy()->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return defensePolicy()->actualDamage(this, kind, base); + } + + virtual ~Unit() override + { + if (alive()) { + emit(new events::UnitLiveDeleted {this}); + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/unit_factory.hpp b/8303/Parfentev_Leonid/lab3/unit_factory.hpp new file mode 100644 index 000000000..5f1f475bd --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/unit_factory.hpp @@ -0,0 +1,24 @@ +#ifndef _H_UNIT_FACTORY_HPP +#define _H_UNIT_FACTORY_HPP + +#include "unit.hpp" + + +class UnitFactory { +public: + virtual Unit *create() const =0; + + virtual ~UnitFactory() {} +}; + +template +class SimpleUnitFactory: public UnitFactory { +public: + virtual Unit * + create() const override + { + return new U {}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab3/zombie_collector.hpp b/8303/Parfentev_Leonid/lab3/zombie_collector.hpp new file mode 100644 index 000000000..9de5f6981 --- /dev/null +++ b/8303/Parfentev_Leonid/lab3/zombie_collector.hpp @@ -0,0 +1,36 @@ +#ifndef _H_ZOMBIE_COLLECTOR_HPP +#define _H_ZOMBIE_COLLECTOR_HPP + +#include + +#include "unit.hpp" +#include "event.hpp" +#include "map.hpp" + + +class ZombieCollector: public EventListener { + std::vector _units {}; + +public: + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + return handle(ee->event()); + } + if (auto *ee = dynamic_cast(e)) { + _units.push_back(ee->unit()); + } + } + + void + collect(Map *m) + { + for (auto *unit: _units) { + delete m->removeUnitAt(unit->position()); + } + _units.clear(); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/Makefile b/8303/Parfentev_Leonid/lab4/Makefile new file mode 100644 index 000000000..d54e095ab --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/Makefile @@ -0,0 +1,21 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp event.hpp base.hpp object_w_health.hpp \ + landscape.hpp landscape_types.hpp neutral_object.hpp \ + neutral_object_types.hpp unit_factory.hpp game.hpp \ + object_print.hpp event_printer.hpp iostream_player.hpp \ + mediator.hpp zombie_collector.hpp factory_table.hpp logging.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o \ + event.o base.o game.o object_print.o iostream_player.o mediator.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab4/base.cpp b/8303/Parfentev_Leonid/lab4/base.cpp new file mode 100644 index 000000000..d6bc63c82 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/base.cpp @@ -0,0 +1,121 @@ +#include +#include +#include + +#include "unit.hpp" +#include "unit_factory.hpp" +#include "event.hpp" +#include "base.hpp" +#include "mediator.hpp" +#include "factory_table.hpp" + + +bool +Base::canCreateUnit(const std::string &key) const +{ + if (unitsCount() == maxUnitsCount()) { + return false; + } + + if (!FactoryTable::instance()->canCreate(key)) { + return false; + } + + return true; +} + +int +Base::createUnit(const std::string &key, Mediator *m) +{ + if (!canCreateUnit(key)) { + return -1; + } + + if (m->infoAt(position()).unit()) { + return false; + } + + Unit *u = FactoryTable::instance()->create(key); + int id = addUnit(u); + + if (id < 0) { + delete u; + return -1; + } + + if (!m->spawnUnit(u, position())) { + removeUnit(u); + delete u; + return -1; + } + + return id; +} + +bool +Base::setMaxUnitsCount(int m) +{ + if (m < unitsCount()) { + return false; + } + _max_count = m; + return true; +} + +int +Base::addUnit(Unit *u) +{ + if (maxUnitsCount() >= 0 + && unitsCount() == maxUnitsCount()) { + return -1; + } + + _units[_next_idx] = u; + u->subscribe(this); + u->emit(new events::UnitAdded {u}); + + return _next_idx++; +} + +void +Base::removeUnit(Unit *u) +{ + u->unsubscribe(this); + + for (auto iter = _units.begin(); + iter != _units.end(); + ++iter) { + if (iter->second == u) { + _units.erase(iter); + break; + } + } +} + +Unit * +Base::getUnitById(int id) const +{ + auto iter = _units.find(id); + return (iter != _units.end()) + ? iter->second + : nullptr; +} + +void +Base::handle(Event *e) +{ + EventForwarder::handle(e); + + if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } else if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } +} + +Base::~Base() +{ + for (auto p: _units) { + p.second->unsubscribe(this); + } +} diff --git a/8303/Parfentev_Leonid/lab4/base.hpp b/8303/Parfentev_Leonid/lab4/base.hpp new file mode 100644 index 000000000..c47e6fe0f --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/base.hpp @@ -0,0 +1,84 @@ +#ifndef _H_BASE_HPP +#define _H_BASE_HPP + +#include +#include +#include + +#include "placeable.hpp" +#include "event.hpp" +#include "unit.hpp" +#include "mediator.hpp" + + +class Base: public Placeable, + public EventForwarder { + + std::map _units {}; + int _next_idx = 0; + int _max_count = -1; + +public: + Base() {} + + bool + canCreateUnit(const std::string &key) const; + int + createUnit(const std::string &key, Mediator *m); + + int + unitsCount() const { return (int)_units.size(); } + bool + setMaxUnitsCount(int m); + int + maxUnitsCount() const { return _max_count; } + + int + addUnit(Unit *u); + void + removeUnit(Unit *u); + Unit * + getUnitById(int id) const; + + class unitsIter { + using real_iter_t = std::map::const_iterator; + real_iter_t _iter; + + public: + unitsIter(real_iter_t it) + :_iter{it} {} + + int id() const { return _iter->first; } + Unit *unit() const { return _iter->second; } + unitsIter &operator++() { ++_iter; return *this; } + unitsIter + operator++(int) + { + unitsIter x{_iter}; + ++*this; + return x; + } + bool + operator==(const unitsIter &o) const + { + return _iter == o._iter; + } + bool + operator!=(const unitsIter &o) const + { + return !(*this == o); + } + }; + + unitsIter + unitsBegin() const { return unitsIter{_units.begin()}; } + unitsIter + unitsEnd() const { return unitsIter{_units.end()}; } + + virtual void + handle(Event *e) override; + + virtual ~Base() override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/catapult_units.hpp b/8303/Parfentev_Leonid/lab4/catapult_units.hpp new file mode 100644 index 000000000..26a217f7d --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/catapult_units.hpp @@ -0,0 +1,145 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + CatapultAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/common_policies.hpp b/8303/Parfentev_Leonid/lab4/common_policies.hpp new file mode 100644 index 000000000..4a99320aa --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/common_policies.hpp @@ -0,0 +1,181 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include + +#include "unit.hpp" +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + BasicMovement(int n) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } +}; + +class NestedMovement: public MovePolicy, + public MovePolicyContainer { +public: + using MovePolicyContainer::MovePolicyContainer; +}; + +class ModifyingMovePolicy: public NestedMovement { + int _max; + +public: + ModifyingMovePolicy(MovePolicy *p, int max_dist) + :NestedMovement{p}, _max{max_dist} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _max}; + if (!pf.run()) + return false; + + return movePolicy()->canMove(u, to); + } +}; + + + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } +}; + +class NestedDefense: public DefensePolicy, + public DefensePolicyContainer { +public: + using DefensePolicyContainer::DefensePolicyContainer; +}; + +class DefenseLevelDeco: public NestedDefense { + // Controls nested policy lifetime + AttackKind _kind; + double _lvl; + +public: + DefenseLevelDeco(DefensePolicy *p, + AttackKind kind, + double level) + :NestedDefense{p}, _kind{kind}, _lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return defensePolicy()->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } +}; + +class MultiplierDefensePolicy: public NestedDefense { + double _mul; + +public: + MultiplierDefensePolicy(DefensePolicy *p, double mul) + :NestedDefense{p}, _mul{mul} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + DamageSpec ds = defensePolicy()->actualDamage(u, kind, base); + ds.scale(1/_mul); + return ds; + } +}; + + + +class AttackForbidden: public AttackPolicy { +public: + using AttackPolicy::AttackPolicy; + + virtual bool + canAttackTo(const Unit *, MapIter) override + { + return false; + } + + virtual std::pair + baseAttack(const Unit *, MapIter) override + { + return std::make_pair(AttackKind::invalid, 0); + } +}; + +class NestedAttack: public AttackPolicy, + public AttackPolicyContainer { +public: + using AttackPolicyContainer::AttackPolicyContainer; +}; + +class MultiplierAttackPolicy: public NestedAttack { + double _mul; + +public: + MultiplierAttackPolicy(AttackPolicy *p, double mul) + :NestedAttack{p}, _mul{mul} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return attackPolicy()->canAttackTo(u, to); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + auto ba = attackPolicy()->baseAttack(u, to); + return std::make_pair(ba.first, (int)(ba.second * _mul)); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab4/demo.cpp b/8303/Parfentev_Leonid/lab4/demo.cpp new file mode 100644 index 000000000..e12cc82a6 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/demo.cpp @@ -0,0 +1,520 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + +#include "event.hpp" +#include "event_types.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "unit_factory.hpp" + +#include "game.hpp" +#include "event_printer.hpp" +#include "iostream_player.hpp" + +#include "factory_table.hpp" + + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} + +MapIter +doAttack(Unit *u, MapIter to) +{ + if (!u->canAttackTo(to)) { + return MapIter::makeNull(); + + } else { + auto pos = u->actualPosition(to); + + if (Unit *targ = pos.unit()) { + auto dam = u->baseAttack(pos); + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + return pos; + } + + return MapIter::makeNull(); + } +} + +struct SimpleGame { + Map *map; + Base *b1, *b2; + EventPrinter *pr; + + explicit SimpleGame(int w=10, int h=10, + int x1=1, int y1=1, + int x2=9, int y2=9) + { + pr = new EventPrinter {std::cout}; + + map = new Map {w, h}; + + b1 = new Base {}; + pr->setPrefix(b1, "Base 1"); + + map->addBase(b1, {x1, y1}); + b1->subscribe(pr); + + b2 = new Base {}; + pr->setPrefix(b2, "Base 2"); + + map->addBase(b2, {x2, y2}); + b2->subscribe(pr); + } + + ~SimpleGame() + { + delete map; + delete pr; + } +}; + +void +demo4() +{ + SimpleGame g {}; + + Unit *u1 = new units::Swordsman {}; + Unit *u2 = new units::Swordsman {}; + + g.b1->addUnit(u1); + g.b2->addUnit(u2); + + g.map->addUnit(u1, {3, 3}); + g.map->addUnit(u2, {4, 3}); + + while (u1->alive() + && u2->alive()) { + doAttack(u1, g.map->iterAt(u2->position())); + if (u2->alive()) { + doAttack(u2, g.map->iterAt(u1->position())); + } + } +} + +MapIter +doMove(Map *map, const Unit *u, Vec2 pt) +{ + auto from = map->iterAt(u->position()); + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + return MapIter::makeNull(); + } + + return map->addUnit(map->removeUnitAt(from), pt); +} + +Unit * +setupUnit(Base *base, const std::string &k, Mediator *m, Vec2 pt) +{ + Unit *u = base->getUnitById(base->createUnit(k, m)); + m->teleportUnit(u, pt); + return u; +} + +void +demo5() +{ + SimpleGame g {}; + + // 2,2 .. 5,5: swamp + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + g.map->setLandscape(new landscapes::Swamp {}, + {2+i, 2+j}); + } + } + + // 1,7 .. 6,9: forest + for (int j = 0; j < 2; ++j) { + for (int i = 0; i < 5; ++i) { + g.map->setLandscape(new landscapes::Forest {}, + {1+i, 7+j}); + } + } + + auto *m = new Mediator {g.map}; + auto u1 = setupUnit(g.b1, "swordsman", m, {2, 2}); + auto u2 = setupUnit(g.b2, "swordsman", m, {3, 2}); + + if (doMove(g.map, u1, {0, 2}).null()) { + std::cout << "Can't move " << u1 << " across 2 cells\n"; + } else { + std::cout << "Moved " << u1 << " across 2 cells \n"; + } + + std::cout << "u1: " << u1 << "\n"; + + if (doAttack(u1, g.map->iterAt(u2->position())).null()) { + std::cout << "Can't attack in swamp\n"; + } else { + std::cout << "Attacked in a swamp\n"; + } + + std::cout << "u2: " << u2 << "\n"; + + doMove(g.map, u1, {1, 2}); + doMove(g.map, u2, {2, 3}); + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + doAttack(u1, g.map->iterAt(u2->position())); + + auto u3 = setupUnit(g.b1, "spearsman", m, {3, 8}); + auto u4 = setupUnit(g.b1, "spearsman", m, {7, 8}); + auto u5 = setupUnit(g.b2, "onager", m, {5, 5}); + + while (u3->alive()) + doAttack(u5, g.map->iterAt(u3->position())); + + while (u4->alive()) + doAttack(u5, g.map->iterAt(u4->position())); + + std::cout << g.map; + + delete m; +} + +void +demo6() +{ + SimpleGame g {}; + + auto *m = new Mediator {g.map}; + auto *u1 = setupUnit(g.b1, "slinger", m, {1, 5}); + auto *u2 = setupUnit(g.b2, "swordsman", m, {6, 5}); + + g.map->addNeutralObject(new objects::Tower {}, {1, 5}); + g.map->addNeutralObject(new objects::HealingWell {}, {6, 5}); + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " can't reach " << u2 << "\n"; + } else { + std::cout << u1 << " somehow reached " << u2 << "\n"; + } + + if (m->useObject(u1)) { + std::cout << u1 << " used the tower\n"; + } else { + std::cout << u1 << " can't use the tower\n"; + } + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " still can't reach " << u2 << "\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + if (m->useObject(u2)) { + std::cout << u2 << " used the healing well\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; +} + +void +demo7() +{ + class MoverPlayer: public Player { + std::string _unit_type; + int _id = -1; + + public: + using Player::Player; + + MoverPlayer(std::string name, + std::string type) + :Player{name}, _unit_type{std::move(type)} {} + + virtual bool + takeTurn(Mediator *m, Base *b) override + { + if (_id >= 0) { + Unit *u = b->getUnitById(_id); + m->moveUnitTo(u, u->position().shifted({1, 0})); + } else { + _id = b->createUnit(_unit_type, m); + } + return true; + } + }; + + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + auto *b = new Base {}; + map->addBase(b, {3, 3}); + int b_id = g.addBase(b); + + auto *p = new MoverPlayer {"Player 1", "swordsman"}; + g.setPlayer(b_id, p); + + for (int i = 0; i < 5; ++i) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo8() +{ + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + std::stringstream s1 {}; + s1 << "base\n" + << "create spearsman\n" + << "map 0 0 9 9\n" + << "move 0 3 5\n" + << "map 0 0 9 9\n" + << "describe 3 5\n"; + + auto *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + std::stringstream s2 {}; + s2 << "create archer\n" + << "attack 0 3 5\n"; + + auto *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo9() +{ + Map *map = new Map {10, 10}; + + auto *uf = (new objects + ::WeaponSmiths + ::SimpleUnitFilter {}); + auto *ws = new objects::WeaponSmiths {2.0, uf}; + map->addNeutralObject(ws, {3, 4}); + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + Base *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + Base *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + std::stringstream s1 {}; + std::stringstream s2 {}; + + s1 << "create onager\n" + << "move 0 3 4\n" + << "use 0\n" + << "create onager\n" + << "move 1 3 2\n" + << "attack 0 7 4\n" + << "attack 1 7 2\n" + << "create swordsman\n" + << "move 0 3 5\n" + << "move 2 3 4\n" + << "use 2\n"; + + s2 << "create swordsman\n" + << "move 0 7 4\n" + << "create swordsman\n" + << "move 1 7 2\n" + << "skip skip skip skip skip\n"; + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} diff --git a/8303/Parfentev_Leonid/lab4/demo.hpp b/8303/Parfentev_Leonid/lab4/demo.hpp new file mode 100644 index 000000000..ae8bc7a95 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/demo.hpp @@ -0,0 +1,14 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); +void demo4(); +void demo5(); +void demo6(); +void demo7(); +void demo8(); +void demo9(); + +#endif diff --git a/8303/Parfentev_Leonid/lab4/event.cpp b/8303/Parfentev_Leonid/lab4/event.cpp new file mode 100644 index 000000000..b7ff78ebd --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/event.cpp @@ -0,0 +1,31 @@ +#include "event.hpp" + +void +EventEmitter::emit_shared(Event *e) const +{ + for (auto iter = _listeners.begin(); iter != _listeners.end();) { + auto *listener = *iter++; + // note: the listener may safely unsubscribe when handling the + // event. + listener->handle(e); + } +} + +void +EventEmitter::emit(Event *e) const +{ + emit_shared(e); + delete e; +} + +void +EventEmitter::subscribe(EventListener *l) +{ + _listeners.insert(l); +} + +void +EventEmitter::unsubscribe(EventListener *l) +{ + _listeners.erase(l); +} diff --git a/8303/Parfentev_Leonid/lab4/event.hpp b/8303/Parfentev_Leonid/lab4/event.hpp new file mode 100644 index 000000000..fa2d1f541 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/event.hpp @@ -0,0 +1,62 @@ +#ifndef _H_EVENT_HPP +#define _H_EVENT_HPP + +#include + +class EventListener; +class Event; + +class EventEmitter { + std::set _listeners {}; + +public: + void emit_shared(Event *e) const; + void emit(Event *e) const; + + void subscribe(EventListener *l); + void unsubscribe(EventListener *l); + + virtual ~EventEmitter() {} +}; + +class Event { +public: + virtual ~Event() {} +}; + +class EventListener { +public: + virtual void handle(Event *e) =0; + + virtual ~EventListener() {} +}; + +class EventForwarder; + +namespace events { + + class Forwarded: public Event { + Event *_e; + EventForwarder *_f; + + public: + Forwarded(Event *e, EventForwarder *f) + :_e{e}, _f{f} {} + + Event *event() const { return _e; } + EventForwarder *forwarder() const { return _f; } + }; + +} + +class EventForwarder: public EventEmitter, + public EventListener { +public: + virtual void + handle(Event *e) override + { + emit(new events::Forwarded {e, this}); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/event_printer.hpp b/8303/Parfentev_Leonid/lab4/event_printer.hpp new file mode 100644 index 000000000..05218c2a6 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/event_printer.hpp @@ -0,0 +1,131 @@ +#ifndef _H_EVENT_PRINTER_HPP +#define _H_EVENT_PRINTER_HPP + +#include +#include +#include +#include + +#include "event.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "player.hpp" +#include "object_print.hpp" + + +class EventPrinter: public EventListener { + std::ostream *_os; + bool _free_os; + + std::map _prefix_map {}; + int _base_idx = 0; + + std::string + makeName(const char *base, int idx) + { + std::ostringstream oss {}; + oss << base << " " << idx; + return oss.str(); + } + +public: + EventPrinter(std::ostream &os) + :_os{&os}, _free_os{false} {} + + EventPrinter(std::ostream *os) + :_os{os}, _free_os{true} {} + + std::ostream & + ostream() const { return *_os; } + + void + setPrefix(EventForwarder *f, const std::string &s) + { + _prefix_map[f] = s; + } + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + auto iter = _prefix_map.find(ee->forwarder()); + if (iter != _prefix_map.end()) { + (*_os) << iter->second << ": "; + } + + return handle(ee->event()); + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit added: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit died: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " takes " + << ee->damage() << " health points of damage\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " gets healed by " + << ee->health() << " health points\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->attacker() + << " attacked another unit " << ee->target() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->target() + << " was attacked by another unit " + << ee->attacker() << "\n"; + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " moved from " << ee->sourcePos() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " used object " << ee->neutralObject() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "(Live unit " << ((void *)ee->unit()) + << " deleted)\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + auto name = makeName("Base", ++_base_idx); + setPrefix(ee->base(), name); + (*_os) << "New base: " << name << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << " over\n"; + + } else { + (*_os) << "Unknown event\n"; + } + } + + virtual ~EventPrinter() override + { + if (_free_os) { + _os->flush(); + delete _os; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/event_types.hpp b/8303/Parfentev_Leonid/lab4/event_types.hpp new file mode 100644 index 000000000..8de61313b --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/event_types.hpp @@ -0,0 +1,161 @@ +#ifndef _H_EVENT_TYPES_HPP +#define _H_EVENT_TYPES_HPP + +#include "point.hpp" +#include "event.hpp" + + +class Unit; +class Base; +class NeutralObject; +class Player; + + +class UnitEvent: public Event { + Unit *_u; + +public: + UnitEvent(Unit *u) :_u{u} {} + + Unit *unit() const { return _u; } +}; + +class AttackEvent: public Event { + Unit *_a, *_b; + +public: + AttackEvent(Unit *a, Unit *b) :_a{a}, _b{b} {} + + Unit *attacker() const { return _a; } + Unit *target() const { return _b; } +}; + +class BaseEvent: public Event { + Base *_b; + +public: + BaseEvent(Base *b) :_b{b} {} + + Base *base() const { return _b; } +}; + +class PlayerEvent: public Event { + Player *_p; + +public: + PlayerEvent(Player *p) :_p{p} {} + + Player *player() const { return _p; } +}; + +class UnitMoveEvent: public UnitEvent { + Vec2 _src; + +public: + UnitMoveEvent(Unit *u, Vec2 src) + :UnitEvent{u}, _src{src} {} + + Vec2 sourcePos() const { return _src; } +}; + + + +namespace events { + + class UnitDeath: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitAdded: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitLiveDeleted: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitTakesDamage: public UnitEvent { + int _dmg; + + public: + UnitTakesDamage(Unit *u, int dmg) + :UnitEvent{u}, _dmg{dmg} {} + + int damage() const { return _dmg; } + }; + + class UnitGetsHealed: public UnitEvent { + int _hp; + + public: + UnitGetsHealed(Unit *u, int hp) + :UnitEvent{u}, _hp{hp} {} + + int health() const { return _hp; } + }; + + class UnitWasAttacked: public AttackEvent { + public: + using AttackEvent::AttackEvent; + }; + + class UnitAttacked: public AttackEvent { + Vec2 _tc; + + public: + UnitAttacked(Unit *u, Vec2 tc, Unit *tu) + :AttackEvent{u, tu}, _tc{tc} {} + + Vec2 targetCoordinates() const { return _tc; } + }; + + class UnitMoved: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitTeleported: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitUsedObject: public UnitEvent { + NeutralObject *_n; + + public: + UnitUsedObject(Unit *u, NeutralObject *n) + :UnitEvent{u}, _n{n} {} + + NeutralObject *neutralObject() const { return _n; } + }; + + + + class BaseAdded: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + class BaseDestroyed: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + + + class TurnStarted: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + + class TurnOver: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/factory_table.hpp b/8303/Parfentev_Leonid/lab4/factory_table.hpp new file mode 100644 index 000000000..693031648 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/factory_table.hpp @@ -0,0 +1,81 @@ +#ifndef _H_FACTORY_TABLE_HPP +#define _H_FACTORY_TABLE_HPP + +#include +#include + +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "unit_factory.hpp" + + +class FactoryTable { + std::map _tab {}; + + void + registerFactory(const std::string &key, UnitFactory *f) + { + _tab[key] = f; + } + + FactoryTable() + { +#define REG(k, T) \ + registerFactory( \ + k, new SimpleUnitFactory {}) + REG("swordsman", Swordsman); + REG("spearsman", Spearsman); + REG("cavalry", Cavalry); + REG("archer", Archer); + REG("slinger", Slinger); + REG("onager", Onager); + REG("boltthrower", BoltThrower); +#undef REG + } + +public: + static const FactoryTable * + instance() + { + static FactoryTable *inst = new FactoryTable {}; + return inst; + } + + virtual bool + canCreate(const std::string &key) const + { + return _tab.find(key) != _tab.end(); + } + + virtual std::vector + keys() const + { + std::vector keys {}; + for (auto p: _tab) { + keys.push_back(p.first); + } + return keys; + } + + virtual Unit * + create(const std::string &key) const + { + auto iter = _tab.find(key); + if (iter == _tab.end()) { + return nullptr; + } + + return iter->second->create(); + } + + ~FactoryTable() + { + for (auto p: _tab) { + delete p.second; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/game.cpp b/8303/Parfentev_Leonid/lab4/game.cpp new file mode 100644 index 000000000..8cb8c6fd1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/game.cpp @@ -0,0 +1,100 @@ +#include "base.hpp" +#include "game.hpp" +#include "event.hpp" +#include "event_types.hpp" + + +Game::Game(Map *map) + :_map{map}, + _med{new Mediator {map}} +{ + this->subscribe(_log_sink); +} + +int +Game::addBase(Base *base) +{ + base->subscribe(_coll); + base->subscribe(this); + + _recs.push_back(BaseRecord{base, nullptr}); + + emit(new events::BaseAdded {base}); + + return (int)(_recs.size() - 1); +} + +void +Game::setPlayer(int base_idx, Player *p) +{ + Player *&p_place = _recs[base_idx].player; + if (p_place) { + p_place->unsubscribe(_log_sink); + delete p_place; + --_players; + } + p_place = p; + if (p) { + p->subscribe(_log_sink); + ++_players; + } +} + +int +Game::basesCount() const +{ + return _recs.size(); +} + +int +Game::playersCount() const +{ + return _players; +} + +Base * +Game::baseByIdx(int idx) const +{ + return _recs[idx].base; +} + +void +Game::spin() +{ + auto &rec = _recs[_next]; + Player *p = rec.player; + Base *b = rec.base; + + if (p) { + emit(new events::TurnStarted {p}); + + bool res = p->takeTurn(_med, b); + + _coll->collect(_map); + + emit(new events::TurnOver {p}); + + if (!res) { + setPlayer(_next, nullptr); + } + } + + if (++_next == (int)_recs.size()) { + _next = 0; + } +} + +Game::~Game() +{ + for (int i = 0; i < (int)_recs.size(); ++i) { + setPlayer(i, nullptr); + auto &rec = _recs[i]; + rec.base->unsubscribe(this); + rec.base->unsubscribe(_coll); + } + + delete _coll; + delete _map; + + delete _log_sink; +} diff --git a/8303/Parfentev_Leonid/lab4/game.hpp b/8303/Parfentev_Leonid/lab4/game.hpp new file mode 100644 index 000000000..ff1750d3b --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/game.hpp @@ -0,0 +1,50 @@ +#ifndef _H_GAME_HPP +#define _H_GAME_HPP + +#include + +#include "event.hpp" +#include "map.hpp" +#include "base.hpp" +#include "player.hpp" +#include "zombie_collector.hpp" +#include "mediator.hpp" + + +class Game: public EventForwarder { + Map *_map; + Mediator *_med; + + struct BaseRecord { + Base *base; + Player *player; + }; + + std::vector _recs {}; + int _next = 0; + int _players = 0; + + ZombieCollector *_coll {new ZombieCollector {}}; + + EventForwarder *_log_sink {new EventForwarder {}}; + +public: + Game(Map *map); + + int addBase(Base *b); + void setPlayer(int base_idx, Player *p); + + int basesCount() const; + int playersCount() const; + + Base *baseByIdx(int idx) const; + + EventForwarder *logSink() const { return _log_sink; } + + void spin(); + + ~Game(); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab4/iostream_player.cpp b/8303/Parfentev_Leonid/lab4/iostream_player.cpp new file mode 100644 index 000000000..dc0928f4a --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/iostream_player.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include "game.hpp" +#include "base.hpp" +#include "iostream_player.hpp" +#include "event.hpp" +#include "logging.hpp" + + +void +IostreamPlayer::addCommand(const std::string &str, + IostreamCommand *cmd) +{ + _cmd_tab[str] = cmd; +} + +IostreamPlayer::IostreamPlayer(std::string name) + :Player{name} +{ + addCommand("move", new iostream_commands::Move {}); + addCommand("attack", new iostream_commands::Attack {}); + addCommand("use", new iostream_commands::Use {}); + addCommand("create", new iostream_commands::Create {}); + addCommand("base", new iostream_commands::FindBase {}); + addCommand("units", new iostream_commands::ListUnits {}); + addCommand("describe", new iostream_commands::DescribeAt {}); + addCommand("map", new iostream_commands::PrintMap {}); + addCommand("skip", new iostream_commands::Skip {}); +} + +bool +IostreamPlayer::takeTurn(Mediator *m, Base *b) +{ + for (;;) { + std::string cmd_name; + (*_is) >> cmd_name; + + if (_is->fail()) { + return false; + } + + { + std::ostringstream oss {}; + oss << "User picked action: " << cmd_name; + emit(new events::UserActionEvent {this, oss.str()}); + } + + auto iter = _cmd_tab.find(cmd_name); + if (iter == _cmd_tab.end()) { + std::ostringstream oss {}; + oss << "Unknown command: \"" << cmd_name << "\""; + emit(new events::UserActionEvent {this, oss.str()}); + (*_os) << oss.str() << "\n"; + continue; + } + + if (iter->second->execute(this, m, b)) { + break; + } + } + + return true; +} + +int +IostreamPlayer::readInt() +{ + int x; + (*_is) >> x; + return x; +} + +Unit * +IostreamPlayer::readUnitId(Base *b) +{ + int id = readInt(); + Unit *u = b->getUnitById(id); + if (!u) { + (*_os) << "No such unit: " << id << "\n"; + } + + return u; +} + +Vec2 +IostreamPlayer::readVec2() +{ + int x, y; + (*_is) >> x >> y; + return {x, y}; +} + +std::string +IostreamPlayer::readString() +{ + std::string s; + (*_is) >> s; + return s; +} diff --git a/8303/Parfentev_Leonid/lab4/iostream_player.hpp b/8303/Parfentev_Leonid/lab4/iostream_player.hpp new file mode 100644 index 000000000..684425469 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/iostream_player.hpp @@ -0,0 +1,286 @@ +#ifndef _H_STDIO_PLAYER_HPP +#define _H_STDIO_PLAYER_HPP + +#include +#include +#include +#include + +#include "point.hpp" +#include "game.hpp" +#include "object_print.hpp" + +#include "event.hpp" +#include "logging.hpp" + + +class IostreamPlayer; + +class IostreamCommand { +public: + // -> whether to end turn + virtual bool execute(IostreamPlayer *p, Mediator *m, Base *b) =0; + + virtual ~IostreamCommand() {} +}; + +class IostreamPlayer: public Player { + std::ostream *_os = nullptr; + std::istream *_is = nullptr; + bool _free_os, _free_is; + + std::map _cmd_tab {}; + + void + addCommand(const std::string &str, + IostreamCommand *cmd); + + +public: + IostreamPlayer(std::string name); + + void + setOstream(std::ostream *os) { _os = os; _free_is = true; } + void + setOstream(std::ostream &os) { _os = &os; _free_os = false; } + + std::ostream & + ostream() const { return *_os; } + + void + setIstream(std::istream *is) { _is = is; _free_is = true; } + void + setIstream(std::istream &is) { _is = &is; _free_is = false; } + + std::istream & + istream() const { return *_is; } + + virtual bool + takeTurn(Mediator *m, Base *b) override; + + int + readInt(); + + Unit * + readUnitId(Base *b); + + Vec2 + readVec2(); + + std::string + readString(); +}; + +namespace iostream_commands { + + class Move: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested moving " << u << " to " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->moveUnitTo(u, to)) { + p->ostream() << "Can't move unit " << u + << " to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Attack: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u << " attacks " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->attackTo(u, to)) { + p->ostream() << "Unit " << u + << " can't attack to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Use: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u + << " uses an object"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->useObject(u)) { + p->ostream() << "Unit " << u + << " can't use any object there\n"; + return false; + } + + return true; + } + }; + + class Create: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *b) override + { + std::string s = p->readString(); + + { + std::ostringstream oss {}; + oss << "User requested creation of unit " << s; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!b->canCreateUnit(s)) { + p->ostream() << "Can't create unit of type " + << s << "\n"; + return false; + } + + int id = b->createUnit(s, m); + if (id < 0) { + p->ostream() << "Failed to create a unit of type " + << s << "\n"; + return false; + } + + p->ostream() << "New unit of type " << s + << ": " << id << "\n"; + return true; + } + }; + + class FindBase: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *, Base *b) override + { + p->ostream() << "Base: " << b << "\n"; + return false; + } + }; + + class ListUnits: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *, Base *b) override + { + p->ostream() << "Units:"; + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + p->ostream() << "- " << iter.id() + << ": " << iter.unit() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class DescribeAt: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *) override + { + auto pos = p->readVec2(); + auto info = m->infoAt(pos); + + p->ostream() << "At " << pos << "\n"; + p->ostream() + << "- Landscape: " << info.landscape() << "\n"; + + if (auto *b = info.base()) { + p->ostream() << "- Base: " << b << "\n"; + } + + if (auto *u = info.unit()) { + p->ostream() << "- Unit: " << u << "\n"; + } + + if (auto *n = info.neutralObject()) { + p->ostream() << "- Object: " << n << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class PrintMap: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *m, Base *) override + { + auto from = p->readVec2(); + auto to = p->readVec2(); + + p->ostream() << "From " << from + << " to " << to << ":\n"; + + for (int y = from.y(); y < to.y(); ++y) { + for (int x = from.x(); x < to.x(); ++x) { + if (x != from.x()) { + p->ostream() << " "; + } + displayMapInfo(p->ostream(), m->infoAt({x, y})); + } + p->ostream() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class Skip: public IostreamCommand { + public: + virtual bool + execute(IostreamPlayer *p, Mediator *, Base *) override + { + { + std::ostringstream oss {}; + oss << "User decided to skip the turn"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/landscape.hpp b/8303/Parfentev_Leonid/lab4/landscape.hpp new file mode 100644 index 000000000..7245da6b3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/landscape.hpp @@ -0,0 +1,25 @@ +#ifndef _H_LANDSCAPE_HPP +#define _H_LANDSCAPE_HPP + + +class Unit; + +class Landscape { +public: + virtual void onEnter(Unit *u) =0; + virtual void onLeave(Unit *u) =0; + + virtual ~Landscape() {} +}; + +namespace landscapes { + + class Normal: public Landscape { + public: + virtual void onEnter(Unit *) override {} + virtual void onLeave(Unit *) override {} + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/landscape_types.hpp b/8303/Parfentev_Leonid/lab4/landscape_types.hpp new file mode 100644 index 000000000..268521e35 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/landscape_types.hpp @@ -0,0 +1,70 @@ +#ifndef _H_LANDSCAPE_TYPES_HPP +#define _H_LANDSCAPE_TYPES_HPP + +#include "landscape.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "common_policies.hpp" + + +namespace landscapes { + + // Swamp: max speed is 1; attacking is forbidden + class Swamp: public Landscape { + ModifyingMovePolicy *_p; + AttackPolicy *_prev, *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _p = new ModifyingMovePolicy {u->movePolicy(), 1}; + u->setMovePolicy(_p); + + _prev = u->attackPolicy(); + _cur = new AttackForbidden {}; + u->setAttackPolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *mpc = u->findMoveContainerOf(_p)) { + mpc->setMovePolicy(_p->movePolicy()); + _p->setMovePolicy(nullptr); + delete _p; + _p = nullptr; + } + + // our policy might’ve been wrapped into something + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + delete _cur; + _cur = nullptr; + } + } + }; + + class Forest: public Landscape { + DefensePolicy *_prev; + MultiplierDefensePolicy *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _prev = u->defensePolicy(); + _cur = new MultiplierDefensePolicy {_prev, 2.0}; + u->setDefensePolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *dpc = u->findDefenseContainerOf(_cur)) { + dpc->setDefensePolicy(_prev); + _cur->setDefensePolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/logging.hpp b/8303/Parfentev_Leonid/lab4/logging.hpp new file mode 100644 index 000000000..72b272969 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/logging.hpp @@ -0,0 +1,46 @@ +#ifndef _H_LOGGING_HPP +#define _H_LOGGING_HPP + +#include +#include + +#include "event.hpp" +#include "event_printer.hpp" + + +namespace events { + + class UserActionEvent: public Event { + Player *_p; + std::string _s; + + public: + UserActionEvent(Player *p, std::string s) + :_p{p}, _s{std::move(s)} {} + + const std::string & + message() const { return _s; } + + Player *player() const { return _p;} + }; + +} + +class LoggingEventPrinter: public EventPrinter { +public: + using EventPrinter::EventPrinter; + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + ostream() << ee->player()->name() + << ": " << ee->message() << "\n"; + return; + } + + EventPrinter::handle(e); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/main.cpp b/8303/Parfentev_Leonid/lab4/main.cpp new file mode 100644 index 000000000..096391d1b --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/main.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include + +#include "demo.hpp" + +#include "player.hpp" +#include "iostream_player.hpp" +#include "event_printer.hpp" +#include "base.hpp" +#include "map.hpp" +#include "factory_table.hpp" + +void +run_demos(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); + + std::cout << "\nDemo 4\n"; + demo4(); + + std::cout << "\nDemo 5\n"; + demo5(); + + std::cout << "\nDemo 6\n"; + demo6(); + + std::cout << "\nDemo 7\n"; + demo7(); + + std::cout << "\nDemo 8\n"; + demo8(); + + std::cout << "\nDemo 9\n"; + demo9(); +} + +int +run_game(int argc, char **argv) +{ + std::vector loggers {}; + bool have_stdout = false; + + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "-log")) { + char *fn = argv[++i]; + if (!strcmp(fn, "-")) { + loggers.push_back(new LoggingEventPrinter {std::cout}); + have_stdout = true; + } else { + auto *of = new std::ofstream {fn}; + if (!*of) { + std::cerr << "Failed to open file: " << fn << "\n"; + return 1; + } + loggers.push_back(new LoggingEventPrinter {of}); + } + } else { + std::cerr << "Unknown option: " << argv[i] << "\n"; + return 1; + } + } + + Map *map = new Map {10, 10}; + + Game g {map}; + + for (auto *logger: loggers) { + g.logSink()->subscribe(logger); + } + + EventPrinter *pr = nullptr; + if (!have_stdout) { + pr = new EventPrinter {std::cout}; + g.subscribe(pr); + } + + Base *b1 = new Base {}; + map->addBase(b1, {1, 1}); + g.addBase(b1); + + Base *b2 = new Base {}; + map->addBase(b2, {8, 8}); + g.addBase(b2); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(std::cin); + g.setPlayer(0, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(std::cin); + g.setPlayer(1, p2); + + while (g.playersCount()) + g.spin(); + + for (auto *logger: loggers) { + g.logSink()->unsubscribe(logger); + delete logger; + } + + if (pr) { + g.unsubscribe(pr); + delete pr; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + if (argc == 2 + && !strcmp(argv[1], "-demo")) { + run_demos(); + return 0; + } + + return run_game(argc, argv); +} diff --git a/8303/Parfentev_Leonid/lab4/map.cpp b/8303/Parfentev_Leonid/lab4/map.cpp new file mode 100644 index 000000000..c21541c17 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/map.cpp @@ -0,0 +1,155 @@ +#include "point.hpp" +#include "unit.hpp" +#include "placeable.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "landscape.hpp" +#include "map.hpp" + + +Map::Map(int w, int h) + :_rm{w, h} +{ + for (auto rmiter = _rm.iterAt({0, 0}); + rmiter.y() < _rm.height(); + rmiter.advance(1)) { + rmiter.cell().setLandscape(new landscapes::Normal {}); + } +} + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.unit()) + return MapIter::makeNull(); + + cell.setUnit(u); + u->setPosition(pt); + + ++_units_count; + + cell.landscape()->onEnter(u); + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.unit()); + + if (u) { + --_units_count; + cell.landscape()->onLeave(u); + if (auto *n = dynamic_cast(cell.object())) { + n->onLeave(u); + } + + cell.setUnit(nullptr); + u->unsetPosition(); + } + + return u; +} + +MapIter +Map::addPlaceable(Placeable *p, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.object()) { + return MapIter::makeNull(); + } + + cell.setObject(p); + p->setPosition(pt); + + return MapIter{rmiter}; +} + +MapIter +Map::addBase(Base *b, Vec2 pt) +{ + return addPlaceable(b, pt); +} + +MapIter +Map::addNeutralObject(NeutralObject *n, Vec2 pt) +{ + return addPlaceable(n, pt); +} + +void +Map::setLandscape(Landscape *l, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + cell.setLandscape(l); +} + +MapInfo +Map::infoAt(Vec2 pt) const +{ + return MapInfo{&_rm.at(pt)}; +} + +Unit * +MapIter::unit() const +{ + return _it.cell().unit(); +} + +Base * +MapIter::base() const +{ + return dynamic_cast(_it.cell().object()); +} + +NeutralObject * +MapIter::neutralObject() const +{ + return dynamic_cast(_it.cell().object()); +} + +Landscape * +MapIter::landscape() const +{ + return _it.cell().landscape(); +} + +const Landscape * +MapInfo::landscape() const +{ + return _cell->landscape(); +} + +const Unit * +MapInfo::unit() const +{ + return _cell->unit(); +} + +const Base * +MapInfo::base() const +{ + return dynamic_cast(_cell->object()); +} + +const NeutralObject * +MapInfo::neutralObject() const +{ + return dynamic_cast(_cell->object()); +} diff --git a/8303/Parfentev_Leonid/lab4/map.hpp b/8303/Parfentev_Leonid/lab4/map.hpp new file mode 100644 index 000000000..eb150cf76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/map.hpp @@ -0,0 +1,119 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include "rectmap.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; +class NeutralObject; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + Base *base() const; + NeutralObject *neutralObject() const; + Landscape *landscape() const; +}; + +class MapInfo { + const Cell *_cell; + +public: + MapInfo(const Cell *c) + :_cell{c} {} + + const Landscape *landscape() const; + const Unit *unit() const; + const Base *base() const; + const NeutralObject *neutralObject() const; +}; + +class Map { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + + MapIter addPlaceable(Placeable *p, Vec2 pt); + +public: + Map(int w, int h); + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapInfo infoAt(Vec2 pt) const; + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + MapIter addBase(Base *b, Vec2 pt); + MapIter addNeutralObject(NeutralObject *n, Vec2 pt); + void setLandscape(Landscape *l, Vec2 pt); + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/mediator.cpp b/8303/Parfentev_Leonid/lab4/mediator.cpp new file mode 100644 index 000000000..348997877 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/mediator.cpp @@ -0,0 +1,111 @@ +#include "point.hpp" +#include "map.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "neutral_object.hpp" +#include "mediator.hpp" + + +MapInfo +Mediator::infoAt(Vec2 pt) +{ + return _map->infoAt(pt); +} + +Vec2 +Mediator::mapSize() +{ + return {_map->width(), + _map->height()}; +} + +bool +Mediator::moveUnitTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit() + || !u->canMove(ito)) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitMoved {u, from}); + return true; +} + +bool +Mediator::attackTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (!ito.unit() + || !u->canAttackTo(ito)) { + return false; + } + + auto pos = u->actualPosition(ito); + Unit *t = pos.unit(); + + u->emit(new events::UnitAttacked {u, pos.point(), t}); + + if (t) { + t->emit(new events::UnitWasAttacked {u, t}); + + auto damage_pair = u->baseAttack(pos); + auto damage_spec = t->actualDamage(damage_pair.first, + damage_pair.second); + int dmg = damage_spec.evaluate(); + + t->takeDamage(dmg); + } + + return true; +} + +bool +Mediator::useObject(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + NeutralObject *n = iter.neutralObject(); + + if (!n + || !n->canUse(u)) { + return false; + } + + n->onUse(u, this); + + u->emit(new events::UnitUsedObject {u, n}); + + return true; +} + +bool +Mediator::spawnUnit(Unit *u, Vec2 at) +{ + return !_map->addUnit(u, at).null(); +} + +bool +Mediator::teleportUnit(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit()) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitTeleported {u, from}); + return true; +} diff --git a/8303/Parfentev_Leonid/lab4/mediator.hpp b/8303/Parfentev_Leonid/lab4/mediator.hpp new file mode 100644 index 000000000..cc1549f65 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/mediator.hpp @@ -0,0 +1,31 @@ +#ifndef _H_MEDIATOR_HPP +#define _H_MEDIATOR_HPP + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + + +class Mediator { + Map *_map; + +public: + Mediator(Map *map) + :_map{map} {} + + MapInfo infoAt(Vec2 pt); + + Vec2 mapSize(); + + bool moveUnitTo(Unit *u, Vec2 to); + + bool attackTo(Unit *u, Vec2 to); + + bool useObject(Unit *u); + + bool spawnUnit(Unit *u, Vec2 at); + bool teleportUnit(Unit *u, Vec2 to); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab4/melee_units.hpp b/8303/Parfentev_Leonid/lab4/melee_units.hpp new file mode 100644 index 000000000..f36626401 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/melee_units.hpp @@ -0,0 +1,88 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + MeleeAttack(AttackKind kind, int base_dmg) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/neutral_object.hpp b/8303/Parfentev_Leonid/lab4/neutral_object.hpp new file mode 100644 index 000000000..bcb94331e --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/neutral_object.hpp @@ -0,0 +1,22 @@ +#ifndef _H_NEUTRAL_OBJECT_HPP +#define _H_NEUTRAL_OBJECT_HPP + +#include "placeable.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "mediator.hpp" + + +class NeutralObject: public Placeable { +public: + virtual bool canUse(const Unit *) { return true; } + virtual void onUse(Unit *u, Mediator *m) =0; + + // It’s the object’s job to determine whether it was used by the + // leaving unit. + virtual void onLeave(Unit *) {}; + + virtual ~NeutralObject() {}; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/neutral_object_types.hpp b/8303/Parfentev_Leonid/lab4/neutral_object_types.hpp new file mode 100644 index 000000000..203d967f6 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/neutral_object_types.hpp @@ -0,0 +1,187 @@ +#ifndef _H_NEUTRAL_OBJECT_TYPES_HPP +#define _H_NEUTRAL_OBJECT_TYPES_HPP + +#include + +#include "neutral_object.hpp" +#include "mediator.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "ranged_units.hpp" +#include "common_policies.hpp" + + +class ExtendedShootingRange: public NestedAttack { + double _delta; + +public: + ExtendedShootingRange(AttackPolicy *p, double delta) + :NestedAttack{p}, _delta{delta} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = to.point().distance(u->position()); + auto *a = dynamic_cast(attackPolicy()); + return dist >= a->minRange() + && dist <= (a->maxRange() + _delta); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + return attackPolicy()->baseAttack(u, to); + } +}; + + + +namespace objects { + + class HealingWell: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *) override + { + u->heal(25); + } + }; + + class Tower: public NeutralObject { + AttackPolicy *_prev; + ExtendedShootingRange *_cur = nullptr; + + public: + virtual bool + canUse(const Unit *u) override + { + return dynamic_cast(u); + } + + virtual void + onUse(Unit *u, Mediator *) override + { + _prev = u->attackPolicy(); + _cur = new ExtendedShootingRange {_prev, 5}; + u->setAttackPolicy(_cur); + } + + virtual void + onLeave(Unit *u) override + { + if (_cur == nullptr) { + return; + } + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + _cur->setAttackPolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + }; + + class TunnelsEntrance: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *m) override + { + static const int w = 5; + + Vec2 at = u->position(); + + int max_n = 0; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() == nullptr) { + ++max_n; + } + } + } + + std::uniform_int_distribution<> distr {0, max_n-1}; + int n = distr(global_random); + + Vec2 dest; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() != nullptr) { + continue; + } + if (!--n) { + dest = iter; + break; + } + } + } + + m->teleportUnit(u, dest); + } + }; + + class WeaponSmiths: public NeutralObject { + public: + class UnitFilter { + public: + virtual bool + applicable(const Unit *u) =0; + }; + + template + class SimpleUnitFilter: public UnitFilter { + public: + virtual bool + applicable(const Unit *u) override + { + return dynamic_cast(u); + } + }; + + private: + double _mul; + UnitFilter *_filter; + + public: + explicit WeaponSmiths(double mul, UnitFilter *filter=nullptr) + :_mul{mul}, _filter{filter} {} + + virtual bool + canUse(const Unit *u) override + { + if (_filter + && !_filter->applicable(u)) { + return false; + } + + for (const AttackPolicyContainer *apc = u; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (dynamic_cast(apc)) { + return false; + } + } + + return true; + } + + virtual void + onUse(Unit *u, Mediator *) override + { + auto *prev = u->attackPolicy(); + auto *new_p = new MultiplierAttackPolicy {prev, _mul}; + u->setAttackPolicy(new_p); + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/object_print.cpp b/8303/Parfentev_Leonid/lab4/object_print.cpp new file mode 100644 index 000000000..da59e1054 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/object_print.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "object_print.hpp" + + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T) {std::type_index{typeid(landscapes::T)}, #T} +static const std::map landscape_names { + LANDSCAPE_ENTRY(Normal), + LANDSCAPE_ENTRY(Swamp), + LANDSCAPE_ENTRY(Forest), +}; +#undef LANDSCAPE_ENTRY + +#define N_OBJECT_ENTRY(T, n) {std::type_index{typeid(objects::T)}, n} +static const std::map objects_names { + N_OBJECT_ENTRY(HealingWell, "Healing Well"), + N_OBJECT_ENTRY(Tower, "Tower"), + N_OBJECT_ENTRY(TunnelsEntrance, "Tunnels Entrance"), + N_OBJECT_ENTRY(WeaponSmiths, "Weapon Smiths"), +}; +#undef N_OBJECT_ENTRY + + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, const Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP at " + << u->position(); +} + +std::ostream & +operator<<(std::ostream &os, const Landscape *l) +{ + return os << landscape_names.at(std::type_index{typeid(*l)}); +} + +std::ostream & +operator<<(std::ostream &os, const Base *b) +{ + os << "a Base with " << b->unitsCount(); + int m = b->maxUnitsCount(); + if (m >= 0) { + os << "/" << m; + } + + return os << " units at " << b->position(); +} + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n) +{ + return os << "a " + << objects_names.at(std::type_index{typeid(*n)}) + << " at " << n->position(); +} + + + +static const std::map class_chars { +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T, x) \ + {std::type_index{typeid(landscapes::T)}, x} + LANDSCAPE_ENTRY(Normal, '.'), + LANDSCAPE_ENTRY(Swamp, '='), + LANDSCAPE_ENTRY(Forest, '*'), +#undef LANDSCAPE_ENTRY +}; + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info) +{ + if (const Unit *u = info.unit()) { + os << class_chars.at(std::type_index{typeid(*u)}); + } else if (info.base()) { + os << "+"; + } else if (info.neutralObject()) { + os << '#'; + } else { + auto *l = info.landscape(); + os << class_chars.at(std::type_index{typeid(*l)}); + } + + return os; +} + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1) +{ + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + displayMapInfo(os, map->infoAt({x, y})); + } + os << "\n"; + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, const Map *map) +{ + return displayMap(os, map, 0, 0, map->width(), map->height()); +} diff --git a/8303/Parfentev_Leonid/lab4/object_print.hpp b/8303/Parfentev_Leonid/lab4/object_print.hpp new file mode 100644 index 000000000..8df10d966 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/object_print.hpp @@ -0,0 +1,40 @@ +#ifndef _H_OBJECT_PRINT_HPP +#define _H_OBJECT_PRINT_HPP + +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "neutral_object.hpp" + +std::ostream & +operator<<(std::ostream &os, Vec2 pt); + +std::ostream & +operator<<(std::ostream &os, const Unit *u); + +std::ostream & +operator<<(std::ostream &os, const Landscape *l); + +std::ostream & +operator<<(std::ostream &os, const Base *b); + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n); + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info); + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1); + +std::ostream & +operator<<(std::ostream &os, const Map *map); + +#endif diff --git a/8303/Parfentev_Leonid/lab4/object_w_health.hpp b/8303/Parfentev_Leonid/lab4/object_w_health.hpp new file mode 100644 index 000000000..e3db6d050 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/object_w_health.hpp @@ -0,0 +1,30 @@ +#ifndef _H_OBJECT_W_HEALTH_HPP +#define _H_OBJECT_W_HEALTH_HPP + + +class ObjectWithHealth { + int _health, _base_health; + +public: + ObjectWithHealth(int base_health) + :_health{base_health}, + _base_health{base_health} {} + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + virtual void + heal(int hp) { _health += hp; } + + virtual void + takeDamage(int dmg) { _health -= dmg; } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab4/pathfinder.cpp b/8303/Parfentev_Leonid/lab4/pathfinder.cpp new file mode 100644 index 000000000..865f34de2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.unit()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab4/pathfinder.hpp b/8303/Parfentev_Leonid/lab4/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/placeable.hpp b/8303/Parfentev_Leonid/lab4/placeable.hpp new file mode 100644 index 000000000..bd7f0ef74 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/placeable.hpp @@ -0,0 +1,36 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +#include "point.hpp" + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/player.hpp b/8303/Parfentev_Leonid/lab4/player.hpp new file mode 100644 index 000000000..e30e6f004 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/player.hpp @@ -0,0 +1,26 @@ +#ifndef _H_PLAYER_HPP +#define _H_PLAYER_HPP + +#include +#include + +#include "mediator.hpp" +#include "base.hpp" +#include "event.hpp" + + +class Player: public EventEmitter { + std::string _name; + +public: + Player(std::string name) + :_name{std::move(name)} {} + + const std::string &name() const { return _name; } + + virtual bool takeTurn(Mediator *m, Base *b) =0; + + virtual ~Player() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/point.cpp b/8303/Parfentev_Leonid/lab4/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab4/point.hpp b/8303/Parfentev_Leonid/lab4/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/ranged_units.hpp b/8303/Parfentev_Leonid/lab4/ranged_units.hpp new file mode 100644 index 000000000..1798f04ce --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/ranged_units.hpp @@ -0,0 +1,94 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + double minRange() const { return _min_distance; } + double maxRange() const { return _max_distance; } + + RangedAttack(AttackKind kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab4/rectmap.cpp b/8303/Parfentev_Leonid/lab4/rectmap.cpp new file mode 100644 index 000000000..b82667c40 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/rectmap.cpp @@ -0,0 +1,55 @@ +#include "rectmap.hpp" + +#include "unit.hpp" + +Cell::~Cell() +{ + delete _u; + delete _obj; + delete _l; +} + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab4/rectmap.hpp b/8303/Parfentev_Leonid/lab4/rectmap.hpp new file mode 100644 index 000000000..a3119ffb1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/rectmap.hpp @@ -0,0 +1,99 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" +#include "landscape.hpp" + + +class Unit; + +class Cell { + Landscape *_l = nullptr; + Unit *_u = nullptr; + Placeable *_obj = nullptr; + +public: + Cell() {} + + ~Cell(); + + Landscape *landscape() const { return _l; } + void setLandscape(Landscape *l) + { + delete _l; + _l = l; + } + + Unit *unit() const { return _u; } + void setUnit(Unit *u) { _u = u; } + + Placeable *object() const { return _obj; } + void setObject(Placeable *p) { _obj = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + const Cell &at(Vec2 pt) const + { + return _storage[pt.x() + pt.y()*_w]; + } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/report.pdf b/8303/Parfentev_Leonid/lab4/report.pdf new file mode 100644 index 000000000..1d15982cc Binary files /dev/null and b/8303/Parfentev_Leonid/lab4/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab4/unit.cpp b/8303/Parfentev_Leonid/lab4/unit.cpp new file mode 100644 index 000000000..ef46978a2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/unit.cpp @@ -0,0 +1,5 @@ +#include + +#include "unit.hpp" + +std::default_random_engine global_random {}; diff --git a/8303/Parfentev_Leonid/lab4/unit.hpp b/8303/Parfentev_Leonid/lab4/unit.hpp new file mode 100644 index 000000000..45cfef0f0 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/unit.hpp @@ -0,0 +1,258 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "event.hpp" +#include "event_types.hpp" +#include "object_w_health.hpp" +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + invalid, sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + void + scale(double k) + { + base_damage *= k; + damage_spread *= k; + } + + DamageSpec + scaled(double k) const + { + auto ds = *this; + ds.scale(k); + return ds; + } + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class MovePolicyContainer { + MovePolicy *_mp; + +public: + MovePolicyContainer(MovePolicy *mp) :_mp{mp} {} + + MovePolicy *movePolicy() const { return _mp; } + void setMovePolicy(MovePolicy *mp) + { + _mp = mp; + } + virtual ~MovePolicyContainer() { delete _mp; } + + MovePolicyContainer * + findMoveContainerOf(const MovePolicy *mp) + { + for (MovePolicyContainer *mpc = this; mpc; + mpc = dynamic_cast( + mpc->movePolicy())) { + if (mpc->movePolicy() == mp) { + return mpc; + } + } + return nullptr; + } +}; + +class DefensePolicyContainer { + DefensePolicy *_dp; + +public: + DefensePolicyContainer(DefensePolicy *dp) :_dp{dp} {} + + DefensePolicy *defensePolicy() const { return _dp; } + void setDefensePolicy(DefensePolicy *dp) + { + _dp = dp; + } + virtual ~DefensePolicyContainer() { delete _dp; } + + DefensePolicyContainer * + findDefenseContainerOf(const DefensePolicy *dp) + { + for (DefensePolicyContainer *dpc = this; dpc; + dpc = dynamic_cast( + dpc->defensePolicy())) { + if (dpc->defensePolicy() == dp) { + return dpc; + } + } + return nullptr; + } +}; + +class AttackPolicyContainer { + AttackPolicy *_ap; + +public: + AttackPolicyContainer(AttackPolicy *ap) :_ap{ap} {} + + AttackPolicy *attackPolicy() const { return _ap; } + void setAttackPolicy(AttackPolicy *ap) + { + _ap = ap; + } + virtual ~AttackPolicyContainer() { delete _ap; } + + AttackPolicyContainer * + findAttackContainerOf(const AttackPolicy *ap) + { + for (AttackPolicyContainer *apc = this; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (apc->attackPolicy() == ap) { + return apc; + } + } + return nullptr; + } +}; + +class Unit: public Placeable, + public ObjectWithHealth, + public EventEmitter, + public MovePolicyContainer, + public DefensePolicyContainer, + public AttackPolicyContainer { +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :ObjectWithHealth{base_health}, + MovePolicyContainer{move}, + DefensePolicyContainer{defense}, + AttackPolicyContainer{attack} {} + + virtual void + heal(int hp) + { + emit(new events::UnitGetsHealed {this, hp}); + ObjectWithHealth::heal(hp); + } + + virtual void + takeDamage(int dmg) + { + if (!alive()) { + return; + } + + emit(new events::UnitTakesDamage {this, dmg}); + + ObjectWithHealth::takeDamage(dmg); + + if (!alive()) { + emit(new events::UnitDeath {this}); + } + } + + bool + canMove(MapIter to) const + { + return movePolicy()->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return attackPolicy()->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return attackPolicy()->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return attackPolicy()->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return defensePolicy()->actualDamage(this, kind, base); + } + + virtual ~Unit() override + { + if (alive()) { + emit(new events::UnitLiveDeleted {this}); + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/unit_factory.hpp b/8303/Parfentev_Leonid/lab4/unit_factory.hpp new file mode 100644 index 000000000..5f1f475bd --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/unit_factory.hpp @@ -0,0 +1,24 @@ +#ifndef _H_UNIT_FACTORY_HPP +#define _H_UNIT_FACTORY_HPP + +#include "unit.hpp" + + +class UnitFactory { +public: + virtual Unit *create() const =0; + + virtual ~UnitFactory() {} +}; + +template +class SimpleUnitFactory: public UnitFactory { +public: + virtual Unit * + create() const override + { + return new U {}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab4/zombie_collector.hpp b/8303/Parfentev_Leonid/lab4/zombie_collector.hpp new file mode 100644 index 000000000..9de5f6981 --- /dev/null +++ b/8303/Parfentev_Leonid/lab4/zombie_collector.hpp @@ -0,0 +1,36 @@ +#ifndef _H_ZOMBIE_COLLECTOR_HPP +#define _H_ZOMBIE_COLLECTOR_HPP + +#include + +#include "unit.hpp" +#include "event.hpp" +#include "map.hpp" + + +class ZombieCollector: public EventListener { + std::vector _units {}; + +public: + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + return handle(ee->event()); + } + if (auto *ee = dynamic_cast(e)) { + _units.push_back(ee->unit()); + } + } + + void + collect(Map *m) + { + for (auto *unit: _units) { + delete m->removeUnitAt(unit->position()); + } + _units.clear(); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/Makefile b/8303/Parfentev_Leonid/lab5/Makefile new file mode 100644 index 000000000..390e0a2e5 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/Makefile @@ -0,0 +1,23 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp event.hpp base.hpp object_w_health.hpp \ + landscape.hpp landscape_types.hpp neutral_object.hpp \ + neutral_object_types.hpp unit_factory.hpp game.hpp \ + object_print.hpp event_printer.hpp iostream_player.hpp \ + mediator.hpp logging.hpp factory_table.hpp storable.hpp \ + common_storables.hpp restorers.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o \ + event.o base.o game.o object_print.o iostream_player.o mediator.o \ + storable.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab5/base.cpp b/8303/Parfentev_Leonid/lab5/base.cpp new file mode 100644 index 000000000..c2242496d --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/base.cpp @@ -0,0 +1,138 @@ +#include +#include +#include + +#include "unit.hpp" +#include "unit_factory.hpp" +#include "event.hpp" +#include "base.hpp" +#include "mediator.hpp" +#include "factory_table.hpp" + +#include "storable.hpp" +#include "common_storables.hpp" + + +bool +Base::canCreateUnit(const std::string &key) const +{ + if (unitsCount() == maxUnitsCount()) { + return false; + } + + if (!FactoryTable::instance()->canCreate(key)) { + return false; + } + + return true; +} + +int +Base::createUnit(const std::string &key, Mediator *m) +{ + if (!canCreateUnit(key)) { + return -1; + } + + if (m->infoAt(position()).unit()) { + return false; + } + + Unit *u = FactoryTable::instance()->create(key); + int id = addUnit(u); + + if (id < 0) { + delete u; + return -1; + } + + if (!m->spawnUnit(u, position())) { + removeUnit(u); + delete u; + return -1; + } + + return id; +} + +bool +Base::setMaxUnitsCount(int m) +{ + if (m < unitsCount()) { + return false; + } + _max_count = m; + return true; +} + +int +Base::addUnit(Unit *u) +{ + if (maxUnitsCount() >= 0 + && unitsCount() == maxUnitsCount()) { + return -1; + } + + _units[_next_idx] = u; + u->subscribe(this); + u->emit(new events::UnitAdded {u}); + + return _next_idx++; +} + +void +Base::removeUnit(Unit *u) +{ + u->unsubscribe(this); + + for (auto iter = _units.begin(); + iter != _units.end(); + ++iter) { + if (iter->second == u) { + _units.erase(iter); + break; + } + } +} + +Unit * +Base::getUnitById(int id) const +{ + auto iter = _units.find(id); + return (iter != _units.end()) + ? iter->second + : nullptr; +} + +void +Base::store(std::ostream &os) const +{ + os << "base " << _max_count << "\n"; +} + +bool +Base::restore(std::istream &is, + RestorerTable *) +{ + is >> _max_count; + return !is.fail(); +} + +void +Base::handle(Event *e) +{ + EventForwarder::handle(e); + + if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } else if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } +} + +Base::~Base() +{ + for (auto p: _units) { + p.second->unsubscribe(this); + } +} diff --git a/8303/Parfentev_Leonid/lab5/base.hpp b/8303/Parfentev_Leonid/lab5/base.hpp new file mode 100644 index 000000000..d07e8f29e --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/base.hpp @@ -0,0 +1,90 @@ +#ifndef _H_BASE_HPP +#define _H_BASE_HPP + +#include +#include +#include + +#include "storable.hpp" +#include "placeable.hpp" +#include "event.hpp" +#include "unit.hpp" +#include "mediator.hpp" + + +class Base: public Placeable, + public EventForwarder, + public Storable { + + std::map _units {}; + int _next_idx = 0; + int _max_count = -1; + +public: + Base() {} + + bool + canCreateUnit(const std::string &key) const; + int + createUnit(const std::string &key, Mediator *m); + + int + unitsCount() const { return (int)_units.size(); } + bool + setMaxUnitsCount(int m); + int + maxUnitsCount() const { return _max_count; } + + int + addUnit(Unit *u); + void + removeUnit(Unit *u); + Unit * + getUnitById(int id) const; + + class unitsIter { + using real_iter_t = std::map::const_iterator; + real_iter_t _iter; + + public: + unitsIter(real_iter_t it) + :_iter{it} {} + + int id() const { return _iter->first; } + Unit *unit() const { return _iter->second; } + unitsIter &operator++() { ++_iter; return *this; } + unitsIter + operator++(int) + { + unitsIter x{_iter}; + ++*this; + return x; + } + bool + operator==(const unitsIter &o) const + { + return _iter == o._iter; + } + bool + operator!=(const unitsIter &o) const + { + return !(*this == o); + } + }; + + unitsIter + unitsBegin() const { return unitsIter{_units.begin()}; } + unitsIter + unitsEnd() const { return unitsIter{_units.end()}; } + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &, + RestorerTable *) override; + + virtual void + handle(Event *e) override; + + virtual ~Base() override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/catapult_units.hpp b/8303/Parfentev_Leonid/lab5/catapult_units.hpp new file mode 100644 index 000000000..914c28041 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/catapult_units.hpp @@ -0,0 +1,170 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + explicit CatapultAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0, + double min_dist=0, + double max_dist=0, + double dist_pow=0, + double spread_t=0, + double spread_n=0) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_catapult " << static_cast(_kind) + << " " << _base_damage << " " << _min_distance + << " " << _max_distance << " " + << _dist_pow << " " << _spread_tang << " " + << _spread_normal << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage >> _min_distance + >> _max_distance >> _dist_pow >> _spread_tang + >> _spread_normal; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + + UNIT_STORABLE_NAME("u_onager"); + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + + UNIT_STORABLE_NAME("u_boltthrower"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/common_policies.hpp b/8303/Parfentev_Leonid/lab5/common_policies.hpp new file mode 100644 index 000000000..99ad30554 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/common_policies.hpp @@ -0,0 +1,308 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include + +#include "unit.hpp" +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + explicit BasicMovement(int n=0) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } + + virtual void + store(std::ostream &os) const override + { + os << "mp_basic " << _steps_per_turn << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + is >> _steps_per_turn; + return !is.fail(); + } +}; + +class NestedMovement: public MovePolicy, + public MovePolicyContainer { +public: + using MovePolicyContainer::MovePolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setMovePolicy(p); + return true; + } + delete s; + return false; + } +}; + +class ModifyingMovePolicy: public NestedMovement { + int _max; + +public: + explicit ModifyingMovePolicy(MovePolicy *p=nullptr, int max_dist=0) + :NestedMovement{p}, _max{max_dist} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _max}; + if (!pf.run()) + return false; + + return movePolicy()->canMove(u, to); + } + + virtual void + store(std::ostream &os) const override + { + os << "mp_modifyiing " << _max << "\n"; + movePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _max; + return !is.fail() && NestedMovement::restore(is, tab); + } +}; + + + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_basic " << _lvl << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + is >> _lvl; + return !is.fail(); + } +}; + +class NestedDefense: public DefensePolicy, + public DefensePolicyContainer { +public: + using DefensePolicyContainer::DefensePolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setDefensePolicy(p); + return true; + } + delete s; + return false; + } +}; + +class DefenseLevelDeco: public NestedDefense { + // Controls nested policy lifetime + AttackKind _kind; + double _lvl; + +public: + explicit DefenseLevelDeco(DefensePolicy *p=0, + AttackKind kind=AttackKind::invalid, + double level=0) + :NestedDefense{p}, _kind{kind}, _lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return defensePolicy()->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_level_deco " << static_cast(_kind) + << " " << _lvl << "\n"; + defensePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + int x; + is >> x; + _kind = static_cast(x); + is >> _lvl; + + return !is.fail() && NestedDefense::restore(is, tab); + } +}; + +class MultiplierDefensePolicy: public NestedDefense { + double _mul; + +public: + explicit MultiplierDefensePolicy(DefensePolicy *p=nullptr, + double mul=0) + :NestedDefense{p}, _mul{mul} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + DamageSpec ds = defensePolicy()->actualDamage(u, kind, base); + ds.scale(1/_mul); + return ds; + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_multiplier " << _mul << "\n"; + defensePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _mul; + return !is.fail() && NestedDefense::restore(is, tab); + } +}; + + + +class AttackForbidden: public AttackPolicy { +public: + using AttackPolicy::AttackPolicy; + + virtual bool + canAttackTo(const Unit *, MapIter) override + { + return false; + } + + virtual std::pair + baseAttack(const Unit *, MapIter) override + { + return std::make_pair(AttackKind::invalid, 0); + } + + TRIVIALLY_STORABLE("ap_forbidden"); +}; + +class NestedAttack: public AttackPolicy, + public AttackPolicyContainer { +public: + using AttackPolicyContainer::AttackPolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setAttackPolicy(p); + return true; + } + delete s; + return false; + } +}; + +class MultiplierAttackPolicy: public NestedAttack { + double _mul; + +public: + explicit MultiplierAttackPolicy(AttackPolicy *p=nullptr, + double mul=0) + :NestedAttack{p}, _mul{mul} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return attackPolicy()->canAttackTo(u, to); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + auto ba = attackPolicy()->baseAttack(u, to); + return std::make_pair(ba.first, (int)(ba.second * _mul)); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_multiplier " << _mul << "\n"; + attackPolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _mul; + return !is.fail() && NestedAttack::restore(is, tab); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab5/common_storables.hpp b/8303/Parfentev_Leonid/lab5/common_storables.hpp new file mode 100644 index 000000000..27cfd5a0a --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/common_storables.hpp @@ -0,0 +1,117 @@ +#ifndef _H_COMMON_STORABLES_HPP +#define _H_COMMON_STORABLES_HPP + +#include "storable.hpp" +#include "object_print.hpp" + + +class StorableEnd: public Storable { + TRIVIALLY_STORABLE("end"); +}; + +class StorableCoordinates: public Storable { + Vec2 _c; + +public: + StorableCoordinates() {} + + StorableCoordinates(Vec2 c) :_c{c} {} + + virtual void + store(std::ostream &os) const override + { + os << "coords " << _c.x() << " " << _c.y() << "\n"; + } + + virtual bool + restore(std::istream &is, + RestorerTable *) override + { + int x, y; + is >> x >> y; + _c = Vec2{x, y}; + return !is.fail(); + } + + Vec2 coords() const { return _c; } +}; + +class StorableWithIndex: public Storable { + int _i; + Storable *_s; + +public: + StorableWithIndex() {} + + StorableWithIndex(int idx, Storable *s) + :_i{idx}, _s{s} {} + + virtual void + store(std::ostream &os) const override + { + os << "index " << _i << " "; + _s->store(os); + } + + virtual bool + restore(std::istream &is, + RestorerTable *tab) override + { + is >> _i; + _s = tab->restore(is); + return !is.fail() && _s; + } + + int index() const { return _i; } + Storable *child() const { return _s; } + + static void + storeWithIndex(int idx, const Storable *s, + std::ostream &os) + { + os << "index " << idx << " "; + s->store(os); + } +}; + +class StorableWithCoords: public Storable { + Vec2 _c; + Storable *_s; + +public: + StorableWithCoords() {} + + StorableWithCoords(Vec2 c, Storable *s) + :_c{c}, _s{s} {} + + virtual void + store(std::ostream &os) const override + { + os << "at " << _c.x() << " " << _c.y() << " "; + _s->store(os); + } + + virtual bool + restore(std::istream &is, + RestorerTable *tab) override + { + int x, y; + is >> x >> y; + _c = Vec2{x, y}; + _s = tab->restore(is); + return !is.fail() && _s; + } + + Vec2 coords() const { return _c; } + Storable *child() const { return _s; } + + static void + storeWithCoords(Vec2 pt, const Storable *s, + std::ostream &os) + { + os << "at " << pt.x() << " " << pt.y() << " "; + s->store(os); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/demo.cpp b/8303/Parfentev_Leonid/lab5/demo.cpp new file mode 100644 index 000000000..2c438ae19 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/demo.cpp @@ -0,0 +1,520 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + +#include "event.hpp" +#include "event_types.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "unit_factory.hpp" + +#include "game.hpp" +#include "event_printer.hpp" +#include "iostream_player.hpp" + +#include "factory_table.hpp" + + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} + +MapIter +doAttack(Unit *u, MapIter to) +{ + if (!u->canAttackTo(to)) { + return MapIter::makeNull(); + + } else { + auto pos = u->actualPosition(to); + + if (Unit *targ = pos.unit()) { + auto dam = u->baseAttack(pos); + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + return pos; + } + + return MapIter::makeNull(); + } +} + +struct SimpleGame { + Map *map; + Base *b1, *b2; + EventPrinter *pr; + + explicit SimpleGame(int w=10, int h=10, + int x1=1, int y1=1, + int x2=9, int y2=9) + { + pr = new EventPrinter {std::cout}; + + map = new Map {w, h}; + + b1 = new Base {}; + pr->setPrefix(b1, "Base 1"); + + map->addBase(b1, {x1, y1}); + b1->subscribe(pr); + + b2 = new Base {}; + pr->setPrefix(b2, "Base 2"); + + map->addBase(b2, {x2, y2}); + b2->subscribe(pr); + } + + ~SimpleGame() + { + delete map; + delete pr; + } +}; + +void +demo4() +{ + SimpleGame g {}; + + Unit *u1 = new units::Swordsman {}; + Unit *u2 = new units::Swordsman {}; + + g.b1->addUnit(u1); + g.b2->addUnit(u2); + + g.map->addUnit(u1, {3, 3}); + g.map->addUnit(u2, {4, 3}); + + while (u1->alive() + && u2->alive()) { + doAttack(u1, g.map->iterAt(u2->position())); + if (u2->alive()) { + doAttack(u2, g.map->iterAt(u1->position())); + } + } +} + +MapIter +doMove(Map *map, const Unit *u, Vec2 pt) +{ + auto from = map->iterAt(u->position()); + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + return MapIter::makeNull(); + } + + return map->addUnit(map->removeUnitAt(from), pt); +} + +Unit * +setupUnit(Base *base, const std::string &k, Mediator *m, Vec2 pt) +{ + Unit *u = base->getUnitById(base->createUnit(k, m)); + m->teleportUnit(u, pt); + return u; +} + +void +demo5() +{ + SimpleGame g {}; + + // 2,2 .. 5,5: swamp + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + g.map->setLandscape(new landscapes::Swamp {}, + {2+i, 2+j}); + } + } + + // 1,7 .. 6,9: forest + for (int j = 0; j < 2; ++j) { + for (int i = 0; i < 5; ++i) { + g.map->setLandscape(new landscapes::Forest {}, + {1+i, 7+j}); + } + } + + auto *m = new Mediator {g.map}; + auto u1 = setupUnit(g.b1, "swordsman", m, {2, 2}); + auto u2 = setupUnit(g.b2, "swordsman", m, {3, 2}); + + if (doMove(g.map, u1, {0, 2}).null()) { + std::cout << "Can't move " << u1 << " across 2 cells\n"; + } else { + std::cout << "Moved " << u1 << " across 2 cells \n"; + } + + std::cout << "u1: " << u1 << "\n"; + + if (doAttack(u1, g.map->iterAt(u2->position())).null()) { + std::cout << "Can't attack in swamp\n"; + } else { + std::cout << "Attacked in a swamp\n"; + } + + std::cout << "u2: " << u2 << "\n"; + + doMove(g.map, u1, {1, 2}); + doMove(g.map, u2, {2, 3}); + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + doAttack(u1, g.map->iterAt(u2->position())); + + auto u3 = setupUnit(g.b1, "spearsman", m, {3, 8}); + auto u4 = setupUnit(g.b1, "spearsman", m, {7, 8}); + auto u5 = setupUnit(g.b2, "onager", m, {5, 5}); + + while (u3->alive()) + doAttack(u5, g.map->iterAt(u3->position())); + + while (u4->alive()) + doAttack(u5, g.map->iterAt(u4->position())); + + std::cout << g.map; + + delete m; +} + +void +demo6() +{ + SimpleGame g {}; + + auto *m = new Mediator {g.map}; + auto *u1 = setupUnit(g.b1, "slinger", m, {1, 5}); + auto *u2 = setupUnit(g.b2, "swordsman", m, {6, 5}); + + g.map->addNeutralObject(new objects::Tower {}, {1, 5}); + g.map->addNeutralObject(new objects::HealingWell {}, {6, 5}); + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " can't reach " << u2 << "\n"; + } else { + std::cout << u1 << " somehow reached " << u2 << "\n"; + } + + if (m->useObject(u1)) { + std::cout << u1 << " used the tower\n"; + } else { + std::cout << u1 << " can't use the tower\n"; + } + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " still can't reach " << u2 << "\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + if (m->useObject(u2)) { + std::cout << u2 << " used the healing well\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; +} + +void +demo7() +{ + class MoverPlayer: public Player { + std::string _unit_type; + int _id = -1; + + public: + using Player::Player; + + MoverPlayer(std::string name, + std::string type) + :Player{name}, _unit_type{std::move(type)} {} + + virtual bool + takeTurn(const Game *, Mediator *m, Base *b) override + { + if (_id >= 0) { + Unit *u = b->getUnitById(_id); + m->moveUnitTo(u, u->position().shifted({1, 0})); + } else { + _id = b->createUnit(_unit_type, m); + } + return true; + } + }; + + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + auto *b = new Base {}; + map->addBase(b, {3, 3}); + int b_id = g.addBase(b); + + auto *p = new MoverPlayer {"Player 1", "swordsman"}; + g.setPlayer(b_id, p); + + for (int i = 0; i < 5; ++i) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo8() +{ + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + std::stringstream s1 {}; + s1 << "base\n" + << "create spearsman\n" + << "map 0 0 9 9\n" + << "move 0 3 5\n" + << "map 0 0 9 9\n" + << "describe 3 5\n"; + + auto *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + std::stringstream s2 {}; + s2 << "create archer\n" + << "attack 0 3 5\n"; + + auto *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo9() +{ + Map *map = new Map {10, 10}; + + auto *uf = (new objects + ::WeaponSmiths + ::CatapultUnitFilter {}); + auto *ws = new objects::WeaponSmiths {2.0, uf}; + map->addNeutralObject(ws, {3, 4}); + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + Base *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + Base *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + std::stringstream s1 {}; + std::stringstream s2 {}; + + s1 << "create onager\n" + << "move 0 3 4\n" + << "use 0\n" + << "create onager\n" + << "move 1 3 2\n" + << "attack 0 7 4\n" + << "attack 1 7 2\n" + << "create swordsman\n" + << "move 0 3 5\n" + << "move 2 3 4\n" + << "use 2\n"; + + s2 << "create swordsman\n" + << "move 0 7 4\n" + << "create swordsman\n" + << "move 1 7 2\n" + << "skip skip skip skip skip\n"; + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} diff --git a/8303/Parfentev_Leonid/lab5/demo.hpp b/8303/Parfentev_Leonid/lab5/demo.hpp new file mode 100644 index 000000000..ae8bc7a95 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/demo.hpp @@ -0,0 +1,14 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); +void demo4(); +void demo5(); +void demo6(); +void demo7(); +void demo8(); +void demo9(); + +#endif diff --git a/8303/Parfentev_Leonid/lab5/event.cpp b/8303/Parfentev_Leonid/lab5/event.cpp new file mode 100644 index 000000000..b7ff78ebd --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/event.cpp @@ -0,0 +1,31 @@ +#include "event.hpp" + +void +EventEmitter::emit_shared(Event *e) const +{ + for (auto iter = _listeners.begin(); iter != _listeners.end();) { + auto *listener = *iter++; + // note: the listener may safely unsubscribe when handling the + // event. + listener->handle(e); + } +} + +void +EventEmitter::emit(Event *e) const +{ + emit_shared(e); + delete e; +} + +void +EventEmitter::subscribe(EventListener *l) +{ + _listeners.insert(l); +} + +void +EventEmitter::unsubscribe(EventListener *l) +{ + _listeners.erase(l); +} diff --git a/8303/Parfentev_Leonid/lab5/event.hpp b/8303/Parfentev_Leonid/lab5/event.hpp new file mode 100644 index 000000000..fa2d1f541 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/event.hpp @@ -0,0 +1,62 @@ +#ifndef _H_EVENT_HPP +#define _H_EVENT_HPP + +#include + +class EventListener; +class Event; + +class EventEmitter { + std::set _listeners {}; + +public: + void emit_shared(Event *e) const; + void emit(Event *e) const; + + void subscribe(EventListener *l); + void unsubscribe(EventListener *l); + + virtual ~EventEmitter() {} +}; + +class Event { +public: + virtual ~Event() {} +}; + +class EventListener { +public: + virtual void handle(Event *e) =0; + + virtual ~EventListener() {} +}; + +class EventForwarder; + +namespace events { + + class Forwarded: public Event { + Event *_e; + EventForwarder *_f; + + public: + Forwarded(Event *e, EventForwarder *f) + :_e{e}, _f{f} {} + + Event *event() const { return _e; } + EventForwarder *forwarder() const { return _f; } + }; + +} + +class EventForwarder: public EventEmitter, + public EventListener { +public: + virtual void + handle(Event *e) override + { + emit(new events::Forwarded {e, this}); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/event_printer.hpp b/8303/Parfentev_Leonid/lab5/event_printer.hpp new file mode 100644 index 000000000..77e363cca --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/event_printer.hpp @@ -0,0 +1,124 @@ +#ifndef _H_EVENT_PRINTER_HPP +#define _H_EVENT_PRINTER_HPP + +#include +#include +#include +#include + +#include "event.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "player.hpp" +#include "object_print.hpp" + + +class EventPrinter: public EventListener { + std::ostream *_os; + bool _free_os; + + std::map _prefix_map {}; + + std::string + makeName(const char *base, int idx) + { + std::ostringstream oss {}; + oss << base << " " << idx; + return oss.str(); + } + +public: + EventPrinter(std::ostream &os) + :_os{&os}, _free_os{false} {} + + EventPrinter(std::ostream *os) + :_os{os}, _free_os{true} {} + + std::ostream & + ostream() const { return *_os; } + + void + setPrefix(EventForwarder *f, const std::string &s) + { + _prefix_map[f] = s; + } + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + auto iter = _prefix_map.find(ee->forwarder()); + if (iter != _prefix_map.end()) { + (*_os) << iter->second << ": "; + } + + return handle(ee->event()); + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit added: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit died: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " takes " + << ee->damage() << " health points of damage\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " gets healed by " + << ee->health() << " health points\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->attacker() + << " attacked another unit " << ee->target() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->target() + << " was attacked by another unit " + << ee->attacker() << "\n"; + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " moved from " << ee->sourcePos() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " used object " << ee->neutralObject() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "(Live unit " << ((void *)ee->unit()) + << " deleted)\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << " over\n"; + + } else { + (*_os) << "Unknown event\n"; + } + } + + virtual ~EventPrinter() override + { + if (_free_os) { + _os->flush(); + delete _os; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/event_types.hpp b/8303/Parfentev_Leonid/lab5/event_types.hpp new file mode 100644 index 000000000..810d3bd94 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/event_types.hpp @@ -0,0 +1,156 @@ +#ifndef _H_EVENT_TYPES_HPP +#define _H_EVENT_TYPES_HPP + +#include "point.hpp" +#include "event.hpp" + + +class Unit; +class Base; +class NeutralObject; +class Player; + + +class UnitEvent: public Event { + Unit *_u; + +public: + UnitEvent(Unit *u) :_u{u} {} + + Unit *unit() const { return _u; } +}; + +class AttackEvent: public Event { + Unit *_a, *_b; + +public: + AttackEvent(Unit *a, Unit *b) :_a{a}, _b{b} {} + + Unit *attacker() const { return _a; } + Unit *target() const { return _b; } +}; + +class BaseEvent: public Event { + Base *_b; + +public: + BaseEvent(Base *b) :_b{b} {} + + Base *base() const { return _b; } +}; + +class PlayerEvent: public Event { + Player *_p; + +public: + PlayerEvent(Player *p) :_p{p} {} + + Player *player() const { return _p; } +}; + +class UnitMoveEvent: public UnitEvent { + Vec2 _src; + +public: + UnitMoveEvent(Unit *u, Vec2 src) + :UnitEvent{u}, _src{src} {} + + Vec2 sourcePos() const { return _src; } +}; + + + +namespace events { + + class UnitDeath: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitAdded: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitLiveDeleted: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitTakesDamage: public UnitEvent { + int _dmg; + + public: + UnitTakesDamage(Unit *u, int dmg) + :UnitEvent{u}, _dmg{dmg} {} + + int damage() const { return _dmg; } + }; + + class UnitGetsHealed: public UnitEvent { + int _hp; + + public: + UnitGetsHealed(Unit *u, int hp) + :UnitEvent{u}, _hp{hp} {} + + int health() const { return _hp; } + }; + + class UnitWasAttacked: public AttackEvent { + public: + using AttackEvent::AttackEvent; + }; + + class UnitAttacked: public AttackEvent { + Vec2 _tc; + + public: + UnitAttacked(Unit *u, Vec2 tc, Unit *tu) + :AttackEvent{u, tu}, _tc{tc} {} + + Vec2 targetCoordinates() const { return _tc; } + }; + + class UnitMoved: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitTeleported: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitUsedObject: public UnitEvent { + NeutralObject *_n; + + public: + UnitUsedObject(Unit *u, NeutralObject *n) + :UnitEvent{u}, _n{n} {} + + NeutralObject *neutralObject() const { return _n; } + }; + + + + class BaseDestroyed: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + + + class TurnStarted: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + + class TurnOver: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/factory_table.hpp b/8303/Parfentev_Leonid/lab5/factory_table.hpp new file mode 100644 index 000000000..693031648 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/factory_table.hpp @@ -0,0 +1,81 @@ +#ifndef _H_FACTORY_TABLE_HPP +#define _H_FACTORY_TABLE_HPP + +#include +#include + +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "unit_factory.hpp" + + +class FactoryTable { + std::map _tab {}; + + void + registerFactory(const std::string &key, UnitFactory *f) + { + _tab[key] = f; + } + + FactoryTable() + { +#define REG(k, T) \ + registerFactory( \ + k, new SimpleUnitFactory {}) + REG("swordsman", Swordsman); + REG("spearsman", Spearsman); + REG("cavalry", Cavalry); + REG("archer", Archer); + REG("slinger", Slinger); + REG("onager", Onager); + REG("boltthrower", BoltThrower); +#undef REG + } + +public: + static const FactoryTable * + instance() + { + static FactoryTable *inst = new FactoryTable {}; + return inst; + } + + virtual bool + canCreate(const std::string &key) const + { + return _tab.find(key) != _tab.end(); + } + + virtual std::vector + keys() const + { + std::vector keys {}; + for (auto p: _tab) { + keys.push_back(p.first); + } + return keys; + } + + virtual Unit * + create(const std::string &key) const + { + auto iter = _tab.find(key); + if (iter == _tab.end()) { + return nullptr; + } + + return iter->second->create(); + } + + ~FactoryTable() + { + for (auto p: _tab) { + delete p.second; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/game.cpp b/8303/Parfentev_Leonid/lab5/game.cpp new file mode 100644 index 000000000..26fd7ef44 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/game.cpp @@ -0,0 +1,198 @@ +#include +#include + +#include "base.hpp" +#include "game.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "storable.hpp" +#include "common_storables.hpp" + + +Game::Game(Map *map) + :_map{map}, + _med{new Mediator {map}} +{ + this->subscribe(_log_sink); +} + +int +Game::addBase(Base *base) +{ + base->subscribe(_coll); + base->subscribe(this); + + _recs.push_back(BaseRecord{base, nullptr}); + + return (int)(_recs.size() - 1); +} + +void +Game::setPlayer(int base_idx, Player *p) +{ + Player *&p_place = _recs[base_idx].player; + if (p_place) { + p_place->unsubscribe(_log_sink); + delete p_place; + --_players; + } + p_place = p; + if (p) { + p->subscribe(_log_sink); + ++_players; + } +} + +int +Game::basesCount() const +{ + return _recs.size(); +} + +int +Game::playersCount() const +{ + return _players; +} + +Base * +Game::baseByIdx(int idx) const +{ + return _recs[idx].base; +} + +void +Game::spin() +{ + auto &rec = _recs[_next]; + Player *p = rec.player; + Base *b = rec.base; + + if (p) { + emit(new events::TurnStarted {p}); + + bool res = p->takeTurn(this, _med, b); + + _coll->collect(_map); + + emit(new events::TurnOver {p}); + + if (!res) { + setPlayer(_next, nullptr); + } + } + + if (++_next == (int)_recs.size()) { + _next = 0; + } +} + +void +Game::store(std::ostream &os) const +{ + os << "game\n"; + _map->store(os); + + os << _next << "\n"; + + for (int i = 0; i < basesCount(); ++i) { + auto *b = baseByIdx(i); + + StorableWithCoords::storeWithCoords(b->position(), b, os); + + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + auto *u = iter.unit(); + auto *sc = new StorableWithCoords {u->position(), u}; + StorableWithIndex::storeWithIndex(i, sc, os); + } + } + + for (int i = 0; i < basesCount(); ++i) { + auto *p = _recs[i].player; + if (p) { + StorableWithIndex::storeWithIndex(i, p, os); + } + } + + os << "end\n"; +} + +bool +Game::restore(std::istream &is, + RestorerTable *tab) +{ + is >> _next; + + for (;;) { + Storable *s = tab->restore(is); + if (auto *sc = + dynamic_cast(s)) { + if (auto *b + = dynamic_cast(sc->child())) { + _map->addBase(b, sc->coords()); + addBase(b); + } else { + delete sc->child(); + delete sc; + return false; + } + delete sc; + } else if (auto *si = + dynamic_cast(s)) { + if (auto *p + = dynamic_cast(si->child())) { + if (si->index() < 0 + || si->index() >= basesCount()) { + delete p; + delete si; + return false; + } + setPlayer(si->index(), p); + } else if (auto *sc = + dynamic_cast( + si->child())) { + if (auto *u + = dynamic_cast(sc->child())) { + _map->addUnit(u, sc->coords()); + baseByIdx(si->index())->addUnit(u); + delete sc; + } else { + delete si; + delete sc; + delete sc->child(); + return false; + } + } else { + delete si->child(); + delete si; + return false; + } + delete si; + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} + +Game::~Game() +{ + for (int i = 0; i < (int)_recs.size(); ++i) { + setPlayer(i, nullptr); + auto &rec = _recs[i]; + rec.base->unsubscribe(this); + rec.base->unsubscribe(_coll); + } + + delete _coll; + delete _map; + + delete _log_sink; +} diff --git a/8303/Parfentev_Leonid/lab5/game.hpp b/8303/Parfentev_Leonid/lab5/game.hpp new file mode 100644 index 000000000..871262d70 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/game.hpp @@ -0,0 +1,56 @@ +#ifndef _H_GAME_HPP +#define _H_GAME_HPP + +#include + +#include "event.hpp" +#include "map.hpp" +#include "base.hpp" +#include "player.hpp" +#include "zombie_collector.hpp" +#include "mediator.hpp" +#include "storable.hpp" + + +class Game: public EventForwarder, + public Storable { + Map *_map; + Mediator *_med; + + struct BaseRecord { + Base *base; + Player *player; + }; + + std::vector _recs {}; + int _next = 0; + int _players = 0; + + ZombieCollector *_coll {new ZombieCollector {}}; + + EventForwarder *_log_sink {new EventForwarder {}}; + +public: + Game(Map *map); + + int addBase(Base *b); + void setPlayer(int base_idx, Player *p); + + int basesCount() const; + int playersCount() const; + + Base *baseByIdx(int idx) const; + + EventForwarder *logSink() const { return _log_sink; } + + void spin(); + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; + + ~Game(); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab5/iostream_player.cpp b/8303/Parfentev_Leonid/lab5/iostream_player.cpp new file mode 100644 index 000000000..80b639819 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/iostream_player.cpp @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "game.hpp" +#include "base.hpp" +#include "iostream_player.hpp" +#include "event.hpp" +#include "logging.hpp" + + +void +IostreamPlayer::addCommand(const std::string &str, + IostreamCommand *cmd) +{ + _cmd_tab[str] = cmd; +} + +IostreamPlayer::IostreamPlayer(std::string name) + :Player{name} +{ + addCommand("move", new iostream_commands::Move {}); + addCommand("attack", new iostream_commands::Attack {}); + addCommand("use", new iostream_commands::Use {}); + addCommand("create", new iostream_commands::Create {}); + addCommand("base", new iostream_commands::FindBase {}); + addCommand("units", new iostream_commands::ListUnits {}); + addCommand("describe", new iostream_commands::DescribeAt {}); + addCommand("map", new iostream_commands::PrintMap {}); + addCommand("skip", new iostream_commands::Skip {}); + addCommand("save", new iostream_commands::Save {}); +} + +bool +IostreamPlayer::takeTurn(const Game *g, Mediator *m, Base *b) +{ + for (;;) { + std::string cmd_name; + (*_is) >> cmd_name; + + if (_is->fail()) { + return false; + } + + { + std::ostringstream oss {}; + oss << "User picked action: " << cmd_name; + emit(new events::UserActionEvent {this, oss.str()}); + } + + auto iter = _cmd_tab.find(cmd_name); + if (iter == _cmd_tab.end()) { + std::ostringstream oss {}; + oss << "Unknown command: \"" << cmd_name << "\""; + emit(new events::UserActionEvent {this, oss.str()}); + (*_os) << oss.str() << "\n"; + continue; + } + + if (iter->second->execute(g, this, m, b)) { + break; + } + } + + return true; +} + +int +IostreamPlayer::readInt() +{ + int x; + (*_is) >> x; + return x; +} + +Unit * +IostreamPlayer::readUnitId(Base *b) +{ + int id = readInt(); + Unit *u = b->getUnitById(id); + if (!u) { + (*_os) << "No such unit: " << id << "\n"; + } + + return u; +} + +Vec2 +IostreamPlayer::readVec2() +{ + int x, y; + (*_is) >> x >> y; + return {x, y}; +} + +std::string +IostreamPlayer::readString() +{ + std::string s; + (*_is) >> s; + return s; +} + +void +IostreamPlayer::store(std::ostream &os) const +{ + os << "iostream_player\n" << name() << "\n"; +} + +bool +IostreamPlayer::restore(std::istream &is, + RestorerTable *tab) +{ + Player::restore(is, tab); + + // We can’t serialize/deserialize IO streams + setOstream(std::cout); + setIstream(std::cin); + return true; +} diff --git a/8303/Parfentev_Leonid/lab5/iostream_player.hpp b/8303/Parfentev_Leonid/lab5/iostream_player.hpp new file mode 100644 index 000000000..14310f8c9 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/iostream_player.hpp @@ -0,0 +1,324 @@ +#ifndef _H_STDIO_PLAYER_HPP +#define _H_STDIO_PLAYER_HPP + +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "game.hpp" +#include "object_print.hpp" + +#include "event.hpp" +#include "logging.hpp" + + +class IostreamPlayer; + +class IostreamCommand { +public: + // -> whether to end turn + virtual bool execute(const Game *g, IostreamPlayer *p, + Mediator *m, Base *b) =0; + + virtual ~IostreamCommand() {} +}; + +class IostreamPlayer: public Player { + std::ostream *_os = nullptr; + std::istream *_is = nullptr; + bool _free_os, _free_is; + + std::map _cmd_tab {}; + + void + addCommand(const std::string &str, + IostreamCommand *cmd); + + +public: + IostreamPlayer(std::string name=""); + + void + setOstream(std::ostream *os) { _os = os; _free_is = true; } + void + setOstream(std::ostream &os) { _os = &os; _free_os = false; } + + std::ostream & + ostream() const { return *_os; } + + void + setIstream(std::istream *is) { _is = is; _free_is = true; } + void + setIstream(std::istream &is) { _is = &is; _free_is = false; } + + std::istream & + istream() const { return *_is; } + + virtual bool + takeTurn(const Game *g, Mediator *m, Base *b) override; + + int + readInt(); + + Unit * + readUnitId(Base *b); + + Vec2 + readVec2(); + + std::string + readString(); + + virtual void store(std::ostream &os) const override; + + virtual bool restore(std::istream &is, + RestorerTable *tab) override; +}; + +namespace iostream_commands { + + class Move: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested moving " << u << " to " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->moveUnitTo(u, to)) { + p->ostream() << "Can't move unit " << u + << " to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Attack: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u << " attacks " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->attackTo(u, to)) { + p->ostream() << "Unit " << u + << " can't attack to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Use: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u + << " uses an object"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->useObject(u)) { + p->ostream() << "Unit " << u + << " can't use any object there\n"; + return false; + } + + return true; + } + }; + + class Create: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + std::string s = p->readString(); + + { + std::ostringstream oss {}; + oss << "User requested creation of unit " << s; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!b->canCreateUnit(s)) { + p->ostream() << "Can't create unit of type " + << s << "\n"; + return false; + } + + int id = b->createUnit(s, m); + if (id < 0) { + p->ostream() << "Failed to create a unit of type " + << s << "\n"; + return false; + } + + p->ostream() << "New unit of type " << s + << ": " << id << "\n"; + return true; + } + }; + + class FindBase: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *b) override + { + p->ostream() << "Base: " << b << "\n"; + return false; + } + }; + + class ListUnits: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *b) override + { + p->ostream() << "Units:"; + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + p->ostream() << "- " << iter.id() + << ": " << iter.unit() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class DescribeAt: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *) override + { + auto pos = p->readVec2(); + auto info = m->infoAt(pos); + + p->ostream() << "At " << pos << "\n"; + p->ostream() + << "- Landscape: " << info.landscape() << "\n"; + + if (auto *b = info.base()) { + p->ostream() << "- Base: " << b << "\n"; + } + + if (auto *u = info.unit()) { + p->ostream() << "- Unit: " << u << "\n"; + } + + if (auto *n = info.neutralObject()) { + p->ostream() << "- Object: " << n << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class PrintMap: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *) override + { + auto from = p->readVec2(); + auto to = p->readVec2(); + + p->ostream() << "From " << from + << " to " << to << ":\n"; + + for (int y = from.y(); y < to.y(); ++y) { + for (int x = from.x(); x < to.x(); ++x) { + if (x != from.x()) { + p->ostream() << " "; + } + displayMapInfo(p->ostream(), m->infoAt({x, y})); + } + p->ostream() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class Skip: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *) override + { + std::ostringstream oss {}; + oss << "User decided to skip the turn"; + p->emit(new events::UserActionEvent {p, oss.str()}); + + return true; + } + }; + + class Save: public IostreamCommand { + public: + virtual bool + execute(const Game *g, IostreamPlayer *p, + Mediator *, Base *) override + { + std::string fn = p->readString(); + std::ofstream of {fn}; + if (!of) { + p->ostream() << "Failed to open file\n"; + return false; + } + + g->store(of); + p->ostream() << "Save complete\n"; + + std::ostringstream oss {}; + oss << "Saved current game to " << fn; + p->emit(new events::UserActionEvent {p, oss.str()}); + + return false; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/landscape.hpp b/8303/Parfentev_Leonid/lab5/landscape.hpp new file mode 100644 index 000000000..3df7e506d --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/landscape.hpp @@ -0,0 +1,29 @@ +#ifndef _H_LANDSCAPE_HPP +#define _H_LANDSCAPE_HPP + + +#include "storable.hpp" + +class Unit; + +class Landscape: public Storable { +public: + virtual void onEnter(Unit *u) =0; + virtual void onLeave(Unit *u) =0; + + virtual ~Landscape() {} +}; + +namespace landscapes { + + class Normal: public Landscape { + public: + virtual void onEnter(Unit *) override {} + virtual void onLeave(Unit *) override {} + + TRIVIALLY_STORABLE("l_normal"); + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/landscape_types.hpp b/8303/Parfentev_Leonid/lab5/landscape_types.hpp new file mode 100644 index 000000000..c53d2b99a --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/landscape_types.hpp @@ -0,0 +1,75 @@ +#ifndef _H_LANDSCAPE_TYPES_HPP +#define _H_LANDSCAPE_TYPES_HPP + +#include "landscape.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "common_policies.hpp" +#include "storable.hpp" + + +namespace landscapes { + + // Swamp: max speed is 1; attacking is forbidden + class Swamp: public Landscape { + ModifyingMovePolicy *_p; + AttackPolicy *_prev, *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _p = new ModifyingMovePolicy {u->movePolicy(), 1}; + u->setMovePolicy(_p); + + _prev = u->attackPolicy(); + _cur = new AttackForbidden {}; + u->setAttackPolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *mpc = u->findMoveContainerOf(_p)) { + mpc->setMovePolicy(_p->movePolicy()); + _p->setMovePolicy(nullptr); + delete _p; + _p = nullptr; + } + + // our policy might’ve been wrapped into something + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("l_swamp"); + }; + + class Forest: public Landscape { + DefensePolicy *_prev; + MultiplierDefensePolicy *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _prev = u->defensePolicy(); + _cur = new MultiplierDefensePolicy {_prev, 2.0}; + u->setDefensePolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *dpc = u->findDefenseContainerOf(_cur)) { + dpc->setDefensePolicy(_prev); + _cur->setDefensePolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("l_forest"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/logging.hpp b/8303/Parfentev_Leonid/lab5/logging.hpp new file mode 100644 index 000000000..72b272969 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/logging.hpp @@ -0,0 +1,46 @@ +#ifndef _H_LOGGING_HPP +#define _H_LOGGING_HPP + +#include +#include + +#include "event.hpp" +#include "event_printer.hpp" + + +namespace events { + + class UserActionEvent: public Event { + Player *_p; + std::string _s; + + public: + UserActionEvent(Player *p, std::string s) + :_p{p}, _s{std::move(s)} {} + + const std::string & + message() const { return _s; } + + Player *player() const { return _p;} + }; + +} + +class LoggingEventPrinter: public EventPrinter { +public: + using EventPrinter::EventPrinter; + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + ostream() << ee->player()->name() + << ": " << ee->message() << "\n"; + return; + } + + EventPrinter::handle(e); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/main.cpp b/8303/Parfentev_Leonid/lab5/main.cpp new file mode 100644 index 000000000..ea7dbbea6 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/main.cpp @@ -0,0 +1,176 @@ +#include + +#include +#include + +#include "demo.hpp" + +#include "player.hpp" +#include "iostream_player.hpp" +#include "event_printer.hpp" +#include "base.hpp" +#include "map.hpp" +#include "factory_table.hpp" + +void +run_demos(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); + + std::cout << "\nDemo 4\n"; + demo4(); + + std::cout << "\nDemo 5\n"; + demo5(); + + std::cout << "\nDemo 6\n"; + demo6(); + + std::cout << "\nDemo 7\n"; + demo7(); + + std::cout << "\nDemo 8\n"; + demo8(); + + std::cout << "\nDemo 9\n"; + demo9(); +} + +void +registerBases(Game *g, EventPrinter *p) +{ + int n = g->basesCount(); + for (int i = 0; i < n; ++i) { + std::ostringstream oss {}; + oss << "Base " << (i+1); + p->setPrefix(g->baseByIdx(i), oss.str()); + } +} + +int +run_game(int argc, char **argv) +{ + std::vector loggers {}; + bool have_stdout = false; + + const char *load_fn = nullptr; + + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "-log")) { + char *fn = argv[++i]; + if (!strcmp(fn, "-")) { + loggers.push_back(new LoggingEventPrinter {std::cout}); + have_stdout = true; + } else { + auto *of = new std::ofstream {fn}; + if (!*of) { + std::cerr << "Failed to open file: " << fn << "\n"; + return 1; + } + loggers.push_back(new LoggingEventPrinter {of}); + } + } else if (!strcmp(argv[i], "-load")) { + load_fn = argv[++i]; + } else { + std::cerr << "Unknown option: " << argv[i] << "\n"; + return 1; + } + } + + Map *map = new Map {10, 10}; + + Game *g; + + if (load_fn) { + std::ifstream f {load_fn}; + if (!f) { + std::cerr << "Failed to open save file: " + << load_fn << "\n"; + return 1; + } + auto *tab = RestorerTable::defaultTable(); + Storable *s = tab->restore(f); + delete tab; + + if (auto *lg = dynamic_cast(s)) { + g = lg; + } else { + std::cerr << "Invalid save file contents\n"; + delete s; + return 1; + } + } else { + g = new Game {map}; + } + + for (auto *logger: loggers) { + g->logSink()->subscribe(logger); + } + + EventPrinter *pr = nullptr; + if (!have_stdout) { + pr = new EventPrinter {std::cout}; + g->subscribe(pr); + } + + Base *b1 = new Base {}; + map->addBase(b1, {1, 1}); + g->addBase(b1); + + Base *b2 = new Base {}; + map->addBase(b2, {8, 8}); + g->addBase(b2); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(std::cin); + g->setPlayer(0, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(std::cin); + g->setPlayer(1, p2); + + for (auto *logger: loggers) { + registerBases(g, logger); + } + if (pr) { + registerBases(g, pr); + } + + while (g->playersCount()) + g->spin(); + + for (auto *logger: loggers) { + g->logSink()->unsubscribe(logger); + delete logger; + } + + if (pr) { + g->unsubscribe(pr); + delete pr; + } + + delete g; + + return 0; +} + +int +main(int argc, char **argv) +{ + if (argc == 2 + && !strcmp(argv[1], "-demo")) { + run_demos(); + return 0; + } + + return run_game(argc, argv); +} diff --git a/8303/Parfentev_Leonid/lab5/map.cpp b/8303/Parfentev_Leonid/lab5/map.cpp new file mode 100644 index 000000000..66eddff6e --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/map.cpp @@ -0,0 +1,222 @@ +#include + +#include "point.hpp" +#include "unit.hpp" +#include "placeable.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "landscape.hpp" +#include "map.hpp" +#include "storable.hpp" +#include "common_storables.hpp" + + +Map::Map(int w, int h) + :_rm{w, h} +{ + for (auto rmiter = _rm.iterAt({0, 0}); + rmiter.y() < _rm.height(); + rmiter.advance(1)) { + rmiter.cell().setLandscape(new landscapes::Normal {}); + } +} + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.unit()) + return MapIter::makeNull(); + + cell.setUnit(u); + u->setPosition(pt); + + ++_units_count; + + cell.landscape()->onEnter(u); + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.unit()); + + if (u) { + --_units_count; + cell.landscape()->onLeave(u); + if (auto *n = dynamic_cast(cell.object())) { + n->onLeave(u); + } + + cell.setUnit(nullptr); + u->unsetPosition(); + } + + return u; +} + +MapIter +Map::addPlaceable(Placeable *p, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.object()) { + return MapIter::makeNull(); + } + + cell.setObject(p); + p->setPosition(pt); + + return MapIter{rmiter}; +} + +MapIter +Map::addBase(Base *b, Vec2 pt) +{ + return addPlaceable(b, pt); +} + +MapIter +Map::addNeutralObject(NeutralObject *n, Vec2 pt) +{ + return addPlaceable(n, pt); +} + +void +Map::setLandscape(Landscape *l, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + cell.setLandscape(l); +} + +void +Map::store(std::ostream &os) const +{ + os << "map " << width() << " " << height() << "\n"; + os << _units_max << "\n"; + + for (int y = 0; y < height(); ++y) { + for (int x = 0; x < width(); ++x) { + auto info = infoAt({x, y}); + + const auto *l = info.landscape(); + if (!dynamic_cast(l)) { + StorableWithCoords::storeWithCoords({x, y}, l, os); + } else if (const auto *n = info.neutralObject()) { + StorableWithCoords::storeWithCoords({x, y}, n, os); + } + + // bases and units are restored in Game::restore + } + } + + os << "end\n"; +} + +bool +Map::restore(std::istream &is, + RestorerTable *tab) +{ + is >> _units_max; + if (is.fail()) { + return false; + } + + for (;;) { + Storable *s = tab->restore(is); + + if (auto *sc = + dynamic_cast(s)) { + if (auto *l = + dynamic_cast(sc->child())) { + setLandscape(l, sc->coords()); + delete sc; + } else if (auto *n = + dynamic_cast(sc->child())) { + addNeutralObject(n, sc->coords()); + delete sc; + } else { + delete sc->child(); + delete sc; + return false; + } + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} + +MapInfo +Map::infoAt(Vec2 pt) const +{ + return MapInfo{&_rm.at(pt)}; +} + +Unit * +MapIter::unit() const +{ + return _it.cell().unit(); +} + +Base * +MapIter::base() const +{ + return dynamic_cast(_it.cell().object()); +} + +NeutralObject * +MapIter::neutralObject() const +{ + return dynamic_cast(_it.cell().object()); +} + +Landscape * +MapIter::landscape() const +{ + return _it.cell().landscape(); +} + +const Landscape * +MapInfo::landscape() const +{ + return _cell->landscape(); +} + +const Unit * +MapInfo::unit() const +{ + return _cell->unit(); +} + +const Base * +MapInfo::base() const +{ + return dynamic_cast(_cell->object()); +} + +const NeutralObject * +MapInfo::neutralObject() const +{ + return dynamic_cast(_cell->object()); +} diff --git a/8303/Parfentev_Leonid/lab5/map.hpp b/8303/Parfentev_Leonid/lab5/map.hpp new file mode 100644 index 000000000..d8831d7cf --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/map.hpp @@ -0,0 +1,126 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include + +#include "rectmap.hpp" +#include "storable.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; +class NeutralObject; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + Base *base() const; + NeutralObject *neutralObject() const; + Landscape *landscape() const; +}; + +class MapInfo { + const Cell *_cell; + +public: + MapInfo(const Cell *c) + :_cell{c} {} + + const Landscape *landscape() const; + const Unit *unit() const; + const Base *base() const; + const NeutralObject *neutralObject() const; +}; + +class Map: public Storable { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + + MapIter addPlaceable(Placeable *p, Vec2 pt); + +public: + Map(int w, int h); + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapInfo infoAt(Vec2 pt) const; + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + MapIter addBase(Base *b, Vec2 pt); + MapIter addNeutralObject(NeutralObject *n, Vec2 pt); + void setLandscape(Landscape *l, Vec2 pt); + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/mediator.cpp b/8303/Parfentev_Leonid/lab5/mediator.cpp new file mode 100644 index 000000000..348997877 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/mediator.cpp @@ -0,0 +1,111 @@ +#include "point.hpp" +#include "map.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "neutral_object.hpp" +#include "mediator.hpp" + + +MapInfo +Mediator::infoAt(Vec2 pt) +{ + return _map->infoAt(pt); +} + +Vec2 +Mediator::mapSize() +{ + return {_map->width(), + _map->height()}; +} + +bool +Mediator::moveUnitTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit() + || !u->canMove(ito)) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitMoved {u, from}); + return true; +} + +bool +Mediator::attackTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (!ito.unit() + || !u->canAttackTo(ito)) { + return false; + } + + auto pos = u->actualPosition(ito); + Unit *t = pos.unit(); + + u->emit(new events::UnitAttacked {u, pos.point(), t}); + + if (t) { + t->emit(new events::UnitWasAttacked {u, t}); + + auto damage_pair = u->baseAttack(pos); + auto damage_spec = t->actualDamage(damage_pair.first, + damage_pair.second); + int dmg = damage_spec.evaluate(); + + t->takeDamage(dmg); + } + + return true; +} + +bool +Mediator::useObject(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + NeutralObject *n = iter.neutralObject(); + + if (!n + || !n->canUse(u)) { + return false; + } + + n->onUse(u, this); + + u->emit(new events::UnitUsedObject {u, n}); + + return true; +} + +bool +Mediator::spawnUnit(Unit *u, Vec2 at) +{ + return !_map->addUnit(u, at).null(); +} + +bool +Mediator::teleportUnit(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit()) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitTeleported {u, from}); + return true; +} diff --git a/8303/Parfentev_Leonid/lab5/mediator.hpp b/8303/Parfentev_Leonid/lab5/mediator.hpp new file mode 100644 index 000000000..cc1549f65 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/mediator.hpp @@ -0,0 +1,31 @@ +#ifndef _H_MEDIATOR_HPP +#define _H_MEDIATOR_HPP + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + + +class Mediator { + Map *_map; + +public: + Mediator(Map *map) + :_map{map} {} + + MapInfo infoAt(Vec2 pt); + + Vec2 mapSize(); + + bool moveUnitTo(Unit *u, Vec2 to); + + bool attackTo(Unit *u, Vec2 to); + + bool useObject(Unit *u); + + bool spawnUnit(Unit *u, Vec2 at); + bool teleportUnit(Unit *u, Vec2 to); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab5/melee_units.hpp b/8303/Parfentev_Leonid/lab5/melee_units.hpp new file mode 100644 index 000000000..6409e96cb --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/melee_units.hpp @@ -0,0 +1,111 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + explicit MeleeAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_melee " << static_cast(_kind) + << " " << _base_damage << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + + UNIT_STORABLE_NAME("u_swordsman"); + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + + UNIT_STORABLE_NAME("u_spearsman"); + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + + UNIT_STORABLE_NAME("u_cavalry"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/neutral_object.hpp b/8303/Parfentev_Leonid/lab5/neutral_object.hpp new file mode 100644 index 000000000..95e11d9c7 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/neutral_object.hpp @@ -0,0 +1,24 @@ +#ifndef _H_NEUTRAL_OBJECT_HPP +#define _H_NEUTRAL_OBJECT_HPP + +#include "placeable.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "mediator.hpp" +#include "storable.hpp" + + +class NeutralObject: public Placeable, + public Storable { +public: + virtual bool canUse(const Unit *) { return true; } + virtual void onUse(Unit *u, Mediator *m) =0; + + // It’s the object’s job to determine whether it was used by the + // leaving unit. + virtual void onLeave(Unit *) {}; + + virtual ~NeutralObject() {}; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/neutral_object_types.hpp b/8303/Parfentev_Leonid/lab5/neutral_object_types.hpp new file mode 100644 index 000000000..e6fc373c1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/neutral_object_types.hpp @@ -0,0 +1,255 @@ +#ifndef _H_NEUTRAL_OBJECT_TYPES_HPP +#define _H_NEUTRAL_OBJECT_TYPES_HPP + +#include + +#include "neutral_object.hpp" +#include "mediator.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "ranged_units.hpp" +#include "common_policies.hpp" + +#include "storable.hpp" +#include "common_storables.hpp" + + +class ExtendedShootingRange: public NestedAttack { + double _delta; + +public: + explicit ExtendedShootingRange(AttackPolicy *p=nullptr, + double delta=0) + :NestedAttack{p}, _delta{delta} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = to.point().distance(u->position()); + auto *a = dynamic_cast(attackPolicy()); + return dist >= a->minRange() + && dist <= (a->maxRange() + _delta); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + return attackPolicy()->baseAttack(u, to); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_extended " << _delta << "\n"; + attackPolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _delta; + return !is.fail() && NestedAttack::restore(is, tab); + } +}; + + + +namespace objects { + + class HealingWell: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *) override + { + u->heal(25); + } + + TRIVIALLY_STORABLE("n_healingwell"); + }; + + class Tower: public NeutralObject { + AttackPolicy *_prev; + ExtendedShootingRange *_cur = nullptr; + + public: + virtual bool + canUse(const Unit *u) override + { + return dynamic_cast(u); + } + + virtual void + onUse(Unit *u, Mediator *) override + { + _prev = u->attackPolicy(); + _cur = new ExtendedShootingRange {_prev, 5}; + u->setAttackPolicy(_cur); + } + + virtual void + onLeave(Unit *u) override + { + if (_cur == nullptr) { + return; + } + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + _cur->setAttackPolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("n_tower"); + }; + + class TunnelsEntrance: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *m) override + { + static const int w = 5; + + Vec2 at = u->position(); + + int max_n = 0; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() == nullptr) { + ++max_n; + } + } + } + + std::uniform_int_distribution<> distr {0, max_n-1}; + int n = distr(global_random); + + Vec2 dest; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() != nullptr) { + continue; + } + if (!--n) { + dest = iter; + break; + } + } + } + + m->teleportUnit(u, dest); + } + + TRIVIALLY_STORABLE("n_tunnelentrance"); + }; + + class WeaponSmiths: public NeutralObject { + public: + class UnitFilter: public Storable { + public: + virtual bool + applicable(const Unit *u) =0; + }; + + template + class SimpleUnitFilter: public UnitFilter{ + public: + virtual bool + applicable(const Unit *u) override + { + return dynamic_cast(u); + } + }; + + class MeleeUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_melee"); + }; + + class RangedUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_ranged"); + }; + + class CatapultUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_catapult"); + }; + + private: + double _mul; + UnitFilter *_filter; + + public: + explicit WeaponSmiths(double mul=0, UnitFilter *filter=nullptr) + :_mul{mul}, _filter{filter} {} + + virtual bool + canUse(const Unit *u) override + { + if (_filter + && !_filter->applicable(u)) { + return false; + } + + for (const AttackPolicyContainer *apc = u; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (dynamic_cast(apc)) { + return false; + } + } + + return true; + } + + virtual void + onUse(Unit *u, Mediator *) override + { + auto *prev = u->attackPolicy(); + auto *new_p = new MultiplierAttackPolicy {prev, _mul}; + u->setAttackPolicy(new_p); + } + + virtual void + store(std::ostream &os) const override + { + os << "n_weaponsmiths " << _mul << "\n"; + if (_filter) { + _filter->store(os); + } else { + os << "end\n"; + } + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + + if (auto *uf = dynamic_cast(s)) { + _filter = uf; + } else if (dynamic_cast(s)) { + _filter = nullptr; + delete s; + } else { + delete s; + return false; + } + + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/object_print.cpp b/8303/Parfentev_Leonid/lab5/object_print.cpp new file mode 100644 index 000000000..da59e1054 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/object_print.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "object_print.hpp" + + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T) {std::type_index{typeid(landscapes::T)}, #T} +static const std::map landscape_names { + LANDSCAPE_ENTRY(Normal), + LANDSCAPE_ENTRY(Swamp), + LANDSCAPE_ENTRY(Forest), +}; +#undef LANDSCAPE_ENTRY + +#define N_OBJECT_ENTRY(T, n) {std::type_index{typeid(objects::T)}, n} +static const std::map objects_names { + N_OBJECT_ENTRY(HealingWell, "Healing Well"), + N_OBJECT_ENTRY(Tower, "Tower"), + N_OBJECT_ENTRY(TunnelsEntrance, "Tunnels Entrance"), + N_OBJECT_ENTRY(WeaponSmiths, "Weapon Smiths"), +}; +#undef N_OBJECT_ENTRY + + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, const Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP at " + << u->position(); +} + +std::ostream & +operator<<(std::ostream &os, const Landscape *l) +{ + return os << landscape_names.at(std::type_index{typeid(*l)}); +} + +std::ostream & +operator<<(std::ostream &os, const Base *b) +{ + os << "a Base with " << b->unitsCount(); + int m = b->maxUnitsCount(); + if (m >= 0) { + os << "/" << m; + } + + return os << " units at " << b->position(); +} + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n) +{ + return os << "a " + << objects_names.at(std::type_index{typeid(*n)}) + << " at " << n->position(); +} + + + +static const std::map class_chars { +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T, x) \ + {std::type_index{typeid(landscapes::T)}, x} + LANDSCAPE_ENTRY(Normal, '.'), + LANDSCAPE_ENTRY(Swamp, '='), + LANDSCAPE_ENTRY(Forest, '*'), +#undef LANDSCAPE_ENTRY +}; + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info) +{ + if (const Unit *u = info.unit()) { + os << class_chars.at(std::type_index{typeid(*u)}); + } else if (info.base()) { + os << "+"; + } else if (info.neutralObject()) { + os << '#'; + } else { + auto *l = info.landscape(); + os << class_chars.at(std::type_index{typeid(*l)}); + } + + return os; +} + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1) +{ + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + displayMapInfo(os, map->infoAt({x, y})); + } + os << "\n"; + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, const Map *map) +{ + return displayMap(os, map, 0, 0, map->width(), map->height()); +} diff --git a/8303/Parfentev_Leonid/lab5/object_print.hpp b/8303/Parfentev_Leonid/lab5/object_print.hpp new file mode 100644 index 000000000..8df10d966 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/object_print.hpp @@ -0,0 +1,40 @@ +#ifndef _H_OBJECT_PRINT_HPP +#define _H_OBJECT_PRINT_HPP + +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "neutral_object.hpp" + +std::ostream & +operator<<(std::ostream &os, Vec2 pt); + +std::ostream & +operator<<(std::ostream &os, const Unit *u); + +std::ostream & +operator<<(std::ostream &os, const Landscape *l); + +std::ostream & +operator<<(std::ostream &os, const Base *b); + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n); + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info); + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1); + +std::ostream & +operator<<(std::ostream &os, const Map *map); + +#endif diff --git a/8303/Parfentev_Leonid/lab5/object_w_health.hpp b/8303/Parfentev_Leonid/lab5/object_w_health.hpp new file mode 100644 index 000000000..e9c925221 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/object_w_health.hpp @@ -0,0 +1,43 @@ +#ifndef _H_OBJECT_W_HEALTH_HPP +#define _H_OBJECT_W_HEALTH_HPP + +#include "storable.hpp" + + +class ObjectWithHealth: public Storable { + int _health, _base_health; + +public: + ObjectWithHealth(int base_health) + :_health{base_health}, + _base_health{base_health} {} + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + virtual void + heal(int hp) { _health += hp; } + + virtual void + takeDamage(int dmg) { _health -= dmg; } + + virtual void store(std::ostream &os) const override + { + os << health(); + } + + virtual bool restore(std::istream &is, RestorerTable *) override + { + is >> _health; + return !is.fail(); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab5/pathfinder.cpp b/8303/Parfentev_Leonid/lab5/pathfinder.cpp new file mode 100644 index 000000000..865f34de2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.unit()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab5/pathfinder.hpp b/8303/Parfentev_Leonid/lab5/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/placeable.hpp b/8303/Parfentev_Leonid/lab5/placeable.hpp new file mode 100644 index 000000000..bd7f0ef74 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/placeable.hpp @@ -0,0 +1,36 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +#include "point.hpp" + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/player.hpp b/8303/Parfentev_Leonid/lab5/player.hpp new file mode 100644 index 000000000..58c15829d --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/player.hpp @@ -0,0 +1,44 @@ +#ifndef _H_PLAYER_HPP +#define _H_PLAYER_HPP + +#include +#include + +#include "mediator.hpp" +#include "base.hpp" +#include "event.hpp" +#include "storable.hpp" + + +class Game; + +class Player: public EventEmitter, + public Storable { + std::string _name; + +public: + explicit Player(std::string name="") + :_name{std::move(name)} {} + + const std::string &name() const { return _name; } + + virtual bool takeTurn(const Game *g, Mediator *m, Base *b) =0; + + virtual void + store(std::ostream &os) const + { + os << name() << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + std::ws(is); + std::getline(is, _name); + return !is.fail(); + } + + virtual ~Player() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/point.cpp b/8303/Parfentev_Leonid/lab5/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab5/point.hpp b/8303/Parfentev_Leonid/lab5/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/ranged_units.hpp b/8303/Parfentev_Leonid/lab5/ranged_units.hpp new file mode 100644 index 000000000..2a41e3ecb --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/ranged_units.hpp @@ -0,0 +1,118 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { +protected: + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + double minRange() const { return _min_distance; } + double maxRange() const { return _max_distance; } + + explicit RangedAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0, + double min_dist=0, + double max_dist=0, + double dist_pow=0) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_ranged " << static_cast(_kind) + << " " << _base_damage << " " << _min_distance + << " " << _max_distance << " " + << _dist_pow << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage >> _min_distance + >> _max_distance >> _dist_pow; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + + UNIT_STORABLE_NAME("u_archer"); + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + + UNIT_STORABLE_NAME("u_slinger"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/rectmap.cpp b/8303/Parfentev_Leonid/lab5/rectmap.cpp new file mode 100644 index 000000000..b82667c40 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/rectmap.cpp @@ -0,0 +1,55 @@ +#include "rectmap.hpp" + +#include "unit.hpp" + +Cell::~Cell() +{ + delete _u; + delete _obj; + delete _l; +} + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab5/rectmap.hpp b/8303/Parfentev_Leonid/lab5/rectmap.hpp new file mode 100644 index 000000000..a3119ffb1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/rectmap.hpp @@ -0,0 +1,99 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" +#include "landscape.hpp" + + +class Unit; + +class Cell { + Landscape *_l = nullptr; + Unit *_u = nullptr; + Placeable *_obj = nullptr; + +public: + Cell() {} + + ~Cell(); + + Landscape *landscape() const { return _l; } + void setLandscape(Landscape *l) + { + delete _l; + _l = l; + } + + Unit *unit() const { return _u; } + void setUnit(Unit *u) { _u = u; } + + Placeable *object() const { return _obj; } + void setObject(Placeable *p) { _obj = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + const Cell &at(Vec2 pt) const + { + return _storage[pt.x() + pt.y()*_w]; + } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/report.pdf b/8303/Parfentev_Leonid/lab5/report.pdf new file mode 100644 index 000000000..2d4c4df4e Binary files /dev/null and b/8303/Parfentev_Leonid/lab5/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab5/restorers.hpp b/8303/Parfentev_Leonid/lab5/restorers.hpp new file mode 100644 index 000000000..6fe67c4e2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/restorers.hpp @@ -0,0 +1,61 @@ +#ifndef _H_RESTORERS_HPP +#define _H_RESTORERS_HPP + +#include "storable.hpp" + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "base.hpp" +#include "game.hpp" +#include "player.hpp" + + +template +class SimpleRestorer: public Restorer { +public: + virtual Storable * + restore(std::istream &, + RestorerTable *) const override + { + return new T {}; + } +}; + + +namespace restorers { + + class GameRestorer: public Restorer { + public: + virtual Storable * + restore(std::istream &is, + RestorerTable *tab) const override + { + Storable *s = tab->restore(is); + Map *map = dynamic_cast(s); + if (!map) { + delete s; + return nullptr; + } + + return new Game {map}; + } + }; + + class MapRestorer: public Restorer { + public: + virtual Storable * + restore(std::istream &is, + RestorerTable *) const override + { + int w, h; + is >> w >> h; + + return new Map {w, h}; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab5/storable.cpp b/8303/Parfentev_Leonid/lab5/storable.cpp new file mode 100644 index 000000000..8a28f8f64 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/storable.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +#include "storable.hpp" +#include "restorers.hpp" +#include "common_storables.hpp" + +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "landscape_types.hpp" +#include "neutral_object_types.hpp" + +#include "factory_table.hpp" +#include "iostream_player.hpp" + + +RestorerTable * +RestorerTable::defaultTable() +{ + return new RestorerTable {{ + +{"end", new SimpleRestorer {}}, +{"coords", new SimpleRestorer {}}, +{"index", new SimpleRestorer {}}, +{"at", new SimpleRestorer {}}, + +{"game", new restorers::GameRestorer {}}, +{"map", new restorers::MapRestorer {}}, + +{"base", new SimpleRestorer {}}, + +{"iostream_player", new SimpleRestorer {}}, + +{"l_normal", new SimpleRestorer {}}, +{"l_swamp", new SimpleRestorer {}}, +{"l_forest", new SimpleRestorer {}}, + +{"u_swordsman", new SimpleRestorer {}}, +{"u_spearsman", new SimpleRestorer {}}, +{"u_cavalry", new SimpleRestorer {}}, + +{"u_archer", new SimpleRestorer {}}, +{"u_slinger", new SimpleRestorer {}}, + +{"u_onager", new SimpleRestorer {}}, +{"u_boltthrower", new SimpleRestorer {}}, + +{"n_healingwell", new SimpleRestorer {}}, +{"n_tower", new SimpleRestorer {}}, +{"n_tunnelentrance", new SimpleRestorer {}}, +{"n_weaponsmiths", new SimpleRestorer {}}, + +{"uf_melee", + new SimpleRestorer {}}, +{"uf_ranged", + new SimpleRestorer {}}, +{"uf_catapult", + new SimpleRestorer {}}, + +{"mp_basic", new SimpleRestorer {}}, +{"mp_modifyiing", new SimpleRestorer {}}, + +{"dp_basic", new SimpleRestorer {}}, +{"dp_level_deco", new SimpleRestorer {}}, +{"dp_multiplier", new SimpleRestorer {}}, + +{"ap_forbidden", new SimpleRestorer {}}, +{"ap_multiplier", new SimpleRestorer {}}, +{"ap_extended", new SimpleRestorer {}}, + +{"ap_melee", new SimpleRestorer {}}, +{"ap_ranged", new SimpleRestorer {}}, +{"ap_catapult", new SimpleRestorer {}}, + + }}; +} diff --git a/8303/Parfentev_Leonid/lab5/storable.hpp b/8303/Parfentev_Leonid/lab5/storable.hpp new file mode 100644 index 000000000..d152b700c --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/storable.hpp @@ -0,0 +1,70 @@ +#ifndef _STORABLE_HPP +#define _STORABLE_HPP + +#include +#include +#include +#include + + +class RestorerTable; + +class Storable { +public: + virtual void store(std::ostream &os) const =0; + virtual bool restore(std::istream &, + RestorerTable *) { return true; }; + + virtual ~Storable() {} +}; + +#define TRIVIALLY_STORABLE(keyword) \ + public: \ + virtual void \ + store(std::ostream &os) const override \ + { \ + os << keyword "\n"; \ + } + + + +class Restorer { +public: + virtual Storable *restore(std::istream &is, + RestorerTable *tab) const =0; +}; + +class RestorerTable { + std::map _tab; + +public: + RestorerTable(std::map m) + :_tab{std::move(m)} {} + + Storable * + restore(std::istream &is) + { + std::string n; + is >> n; + + auto iter = _tab.find(n); + if (iter == _tab.end()) { + return nullptr; + } + + Storable *s = iter->second->restore(is, this); + if (!s) { + return nullptr; + } + + if (!s->restore(is, this)) { + delete s; + return nullptr; + } + return s; + } + + static RestorerTable *defaultTable(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/unit.cpp b/8303/Parfentev_Leonid/lab5/unit.cpp new file mode 100644 index 000000000..39098695f --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/unit.cpp @@ -0,0 +1,48 @@ +#include + +#include "storable.hpp" +#include "common_storables.hpp" +#include "unit.hpp" + + +std::default_random_engine global_random {}; + +void +Unit::store(std::ostream &os) const +{ + os << health() << "\n"; + + movePolicy()->store(os); + defensePolicy()->store(os); + attackPolicy()->store(os); + + os << "end\n"; +} + +bool +Unit::restore(std::istream &is, RestorerTable *tab) +{ + if (!ObjectWithHealth::restore(is, tab)) { + return false; + } + + for (;;) { + Storable *s = tab->restore(is); + + if (auto *mp = dynamic_cast(s)) { + setMovePolicy(mp); + } else if (auto *dp = dynamic_cast(s)) { + setDefensePolicy(dp); + } else if (auto *ap = dynamic_cast(s)) { + setAttackPolicy(ap); + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} diff --git a/8303/Parfentev_Leonid/lab5/unit.hpp b/8303/Parfentev_Leonid/lab5/unit.hpp new file mode 100644 index 000000000..93f745061 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/unit.hpp @@ -0,0 +1,271 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "event.hpp" +#include "event_types.hpp" +#include "object_w_health.hpp" +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy: public Storable { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + invalid, sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy: public Storable { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + void + scale(double k) + { + base_damage *= k; + damage_spread *= k; + } + + DamageSpec + scaled(double k) const + { + auto ds = *this; + ds.scale(k); + return ds; + } + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy: public Storable { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class MovePolicyContainer { + MovePolicy *_mp; + +public: + MovePolicyContainer(MovePolicy *mp) :_mp{mp} {} + + MovePolicy *movePolicy() const { return _mp; } + void setMovePolicy(MovePolicy *mp) + { + _mp = mp; + } + virtual ~MovePolicyContainer() { delete _mp; } + + MovePolicyContainer * + findMoveContainerOf(const MovePolicy *mp) + { + for (MovePolicyContainer *mpc = this; mpc; + mpc = dynamic_cast( + mpc->movePolicy())) { + if (mpc->movePolicy() == mp) { + return mpc; + } + } + return nullptr; + } +}; + +class DefensePolicyContainer { + DefensePolicy *_dp; + +public: + DefensePolicyContainer(DefensePolicy *dp) :_dp{dp} {} + + DefensePolicy *defensePolicy() const { return _dp; } + void setDefensePolicy(DefensePolicy *dp) + { + _dp = dp; + } + virtual ~DefensePolicyContainer() { delete _dp; } + + DefensePolicyContainer * + findDefenseContainerOf(const DefensePolicy *dp) + { + for (DefensePolicyContainer *dpc = this; dpc; + dpc = dynamic_cast( + dpc->defensePolicy())) { + if (dpc->defensePolicy() == dp) { + return dpc; + } + } + return nullptr; + } +}; + +class AttackPolicyContainer { + AttackPolicy *_ap; + +public: + AttackPolicyContainer(AttackPolicy *ap) :_ap{ap} {} + + AttackPolicy *attackPolicy() const { return _ap; } + void setAttackPolicy(AttackPolicy *ap) + { + _ap = ap; + } + virtual ~AttackPolicyContainer() { delete _ap; } + + AttackPolicyContainer * + findAttackContainerOf(const AttackPolicy *ap) + { + for (AttackPolicyContainer *apc = this; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (apc->attackPolicy() == ap) { + return apc; + } + } + return nullptr; + } +}; + +class Unit: public Placeable, + public ObjectWithHealth, + public EventEmitter, + public MovePolicyContainer, + public DefensePolicyContainer, + public AttackPolicyContainer { +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :ObjectWithHealth{base_health}, + MovePolicyContainer{move}, + DefensePolicyContainer{defense}, + AttackPolicyContainer{attack} {} + + virtual void + heal(int hp) + { + emit(new events::UnitGetsHealed {this, hp}); + ObjectWithHealth::heal(hp); + } + + virtual void + takeDamage(int dmg) + { + if (!alive()) { + return; + } + + emit(new events::UnitTakesDamage {this, dmg}); + + ObjectWithHealth::takeDamage(dmg); + + if (!alive()) { + emit(new events::UnitDeath {this}); + } + } + + bool + canMove(MapIter to) const + { + return movePolicy()->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return attackPolicy()->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return attackPolicy()->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return attackPolicy()->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return defensePolicy()->actualDamage(this, kind, base); + } + + // override to add type symbol! + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; + + virtual ~Unit() override + { + if (alive()) { + emit(new events::UnitLiveDeleted {this}); + } + } +}; + +#define UNIT_STORABLE_NAME(n) \ + virtual void \ + store(std::ostream &os) const override \ + { \ + os << n "\n"; \ + Unit::store(os); \ + } + +#endif diff --git a/8303/Parfentev_Leonid/lab5/unit_factory.hpp b/8303/Parfentev_Leonid/lab5/unit_factory.hpp new file mode 100644 index 000000000..5f1f475bd --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/unit_factory.hpp @@ -0,0 +1,24 @@ +#ifndef _H_UNIT_FACTORY_HPP +#define _H_UNIT_FACTORY_HPP + +#include "unit.hpp" + + +class UnitFactory { +public: + virtual Unit *create() const =0; + + virtual ~UnitFactory() {} +}; + +template +class SimpleUnitFactory: public UnitFactory { +public: + virtual Unit * + create() const override + { + return new U {}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab5/zombie_collector.hpp b/8303/Parfentev_Leonid/lab5/zombie_collector.hpp new file mode 100644 index 000000000..9de5f6981 --- /dev/null +++ b/8303/Parfentev_Leonid/lab5/zombie_collector.hpp @@ -0,0 +1,36 @@ +#ifndef _H_ZOMBIE_COLLECTOR_HPP +#define _H_ZOMBIE_COLLECTOR_HPP + +#include + +#include "unit.hpp" +#include "event.hpp" +#include "map.hpp" + + +class ZombieCollector: public EventListener { + std::vector _units {}; + +public: + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + return handle(ee->event()); + } + if (auto *ee = dynamic_cast(e)) { + _units.push_back(ee->unit()); + } + } + + void + collect(Map *m) + { + for (auto *unit: _units) { + delete m->removeUnitAt(unit->position()); + } + _units.clear(); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/Makefile b/8303/Parfentev_Leonid/lab6/Makefile new file mode 100644 index 000000000..94cc99971 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/Makefile @@ -0,0 +1,23 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp event.hpp base.hpp object_w_health.hpp \ + landscape.hpp landscape_types.hpp neutral_object.hpp \ + neutral_object_types.hpp unit_factory.hpp game.hpp \ + object_print.hpp event_printer.hpp iostream_player.hpp \ + mediator.hpp logging.hpp factory_table.hpp storable.hpp \ + common_storables.hpp restorers.hpp game_driver.hpp game_rules.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o \ + event.o base.o game.o object_print.o iostream_player.o mediator.o \ + storable.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab6/base.cpp b/8303/Parfentev_Leonid/lab6/base.cpp new file mode 100644 index 000000000..78079331b --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/base.cpp @@ -0,0 +1,162 @@ +#include +#include +#include + +#include "unit.hpp" +#include "unit_factory.hpp" +#include "event.hpp" +#include "base.hpp" +#include "mediator.hpp" +#include "factory_table.hpp" + +#include "storable.hpp" +#include "common_storables.hpp" + + +bool +Base::canCreateUnit(const std::string &key) const +{ + if (unitsCount() == maxUnitsCount()) { + return false; + } + + if (!FactoryTable::instance()->canCreate(key)) { + return false; + } + + return true; +} + +int +Base::createUnit(const std::string &key, Mediator *m) +{ + if (!canCreateUnit(key)) { + return -1; + } + + if (m->infoAt(position()).unit()) { + return false; + } + + Unit *u = FactoryTable::instance()->create(key); + int id = addUnit(u); + + if (id < 0) { + delete u; + return -1; + } + + if (!m->spawnUnit(u, position())) { + removeUnit(u); + delete u; + return -1; + } + + return id; +} + +bool +Base::setMaxUnitsCount(int m) +{ + if (m < unitsCount()) { + return false; + } + _max_count = m; + return true; +} + +int +Base::addUnit(Unit *u) +{ + if (maxUnitsCount() >= 0 + && unitsCount() == maxUnitsCount()) { + return -1; + } + + _units[_next_idx] = u; + u->subscribe(this); + u->emit(new events::UnitAdded {u}); + + return _next_idx++; +} + +void +Base::removeUnit(Unit *u) +{ + u->unsubscribe(this); + + for (auto iter = _units.begin(); + iter != _units.end(); + ++iter) { + if (iter->second == u) { + _units.erase(iter); + break; + } + } +} + +Unit * +Base::getUnitById(int id) const +{ + auto iter = _units.find(id); + return (iter != _units.end()) + ? iter->second + : nullptr; +} + +bool +Base::becomeDestroyedBy(Unit *u) +{ + for (auto iter = unitsBegin(); + iter != unitsEnd(); + ++iter) { + if (iter.unit() == u) { + return false; + } + } + + _destroyed = true; + + for (auto iter = unitsBegin(); + iter != unitsEnd(); + ++iter) { + Unit *v = iter.unit(); + v->emit(new events::UnitDeath {v}); + } + + emit(new events::BaseDestroyed {this}); + return true; +} + +void +Base::store(std::ostream &os) const +{ + os << "base " << _max_count << " " << _destroyed << "\n"; +} + +bool +Base::restore(std::istream &is, + RestorerTable *) +{ + is >> _max_count >> _destroyed; + return !is.fail(); +} + +void +Base::handle(Event *e) +{ + EventForwarder::handle(e); + + if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } else if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } +} + +Base::~Base() +{ + for (auto p: _units) { + p.second->unsubscribe(this); + } +} diff --git a/8303/Parfentev_Leonid/lab6/base.hpp b/8303/Parfentev_Leonid/lab6/base.hpp new file mode 100644 index 000000000..deadd93cc --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/base.hpp @@ -0,0 +1,98 @@ +#ifndef _H_BASE_HPP +#define _H_BASE_HPP + +#include +#include +#include + +#include "storable.hpp" +#include "placeable.hpp" +#include "event.hpp" +#include "unit.hpp" +#include "mediator.hpp" + + +class Base: public Placeable, + public EventForwarder, + public Storable { + + std::map _units {}; + int _next_idx = 0; + int _max_count = -1; + bool _destroyed = false; + +public: + Base() {} + + virtual bool + canCreateUnit(const std::string &key) const; + virtual int + createUnit(const std::string &key, Mediator *m); + + int + unitsCount() const { return (int)_units.size(); } + bool + setMaxUnitsCount(int m); + int + maxUnitsCount() const { return _max_count; } + + int + addUnit(Unit *u); + void + removeUnit(Unit *u); + Unit * + getUnitById(int id) const; + + virtual void spin() {} + + bool + becomeDestroyedBy(Unit *u); + bool + destroyed() const { return _destroyed; } + + class unitsIter { + using real_iter_t = std::map::const_iterator; + real_iter_t _iter; + + public: + unitsIter(real_iter_t it) + :_iter{it} {} + + int id() const { return _iter->first; } + Unit *unit() const { return _iter->second; } + unitsIter &operator++() { ++_iter; return *this; } + unitsIter + operator++(int) + { + unitsIter x{_iter}; + ++*this; + return x; + } + bool + operator==(const unitsIter &o) const + { + return _iter == o._iter; + } + bool + operator!=(const unitsIter &o) const + { + return !(*this == o); + } + }; + + unitsIter + unitsBegin() const { return unitsIter{_units.begin()}; } + unitsIter + unitsEnd() const { return unitsIter{_units.end()}; } + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &, + RestorerTable *) override; + + virtual void + handle(Event *e) override; + + virtual ~Base() override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/catapult_units.hpp b/8303/Parfentev_Leonid/lab6/catapult_units.hpp new file mode 100644 index 000000000..914c28041 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/catapult_units.hpp @@ -0,0 +1,170 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + explicit CatapultAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0, + double min_dist=0, + double max_dist=0, + double dist_pow=0, + double spread_t=0, + double spread_n=0) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_catapult " << static_cast(_kind) + << " " << _base_damage << " " << _min_distance + << " " << _max_distance << " " + << _dist_pow << " " << _spread_tang << " " + << _spread_normal << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage >> _min_distance + >> _max_distance >> _dist_pow >> _spread_tang + >> _spread_normal; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + + UNIT_STORABLE_NAME("u_onager"); + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + + UNIT_STORABLE_NAME("u_boltthrower"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/common_policies.hpp b/8303/Parfentev_Leonid/lab6/common_policies.hpp new file mode 100644 index 000000000..99ad30554 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/common_policies.hpp @@ -0,0 +1,308 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include + +#include "unit.hpp" +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + explicit BasicMovement(int n=0) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } + + virtual void + store(std::ostream &os) const override + { + os << "mp_basic " << _steps_per_turn << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + is >> _steps_per_turn; + return !is.fail(); + } +}; + +class NestedMovement: public MovePolicy, + public MovePolicyContainer { +public: + using MovePolicyContainer::MovePolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setMovePolicy(p); + return true; + } + delete s; + return false; + } +}; + +class ModifyingMovePolicy: public NestedMovement { + int _max; + +public: + explicit ModifyingMovePolicy(MovePolicy *p=nullptr, int max_dist=0) + :NestedMovement{p}, _max{max_dist} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _max}; + if (!pf.run()) + return false; + + return movePolicy()->canMove(u, to); + } + + virtual void + store(std::ostream &os) const override + { + os << "mp_modifyiing " << _max << "\n"; + movePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _max; + return !is.fail() && NestedMovement::restore(is, tab); + } +}; + + + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_basic " << _lvl << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + is >> _lvl; + return !is.fail(); + } +}; + +class NestedDefense: public DefensePolicy, + public DefensePolicyContainer { +public: + using DefensePolicyContainer::DefensePolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setDefensePolicy(p); + return true; + } + delete s; + return false; + } +}; + +class DefenseLevelDeco: public NestedDefense { + // Controls nested policy lifetime + AttackKind _kind; + double _lvl; + +public: + explicit DefenseLevelDeco(DefensePolicy *p=0, + AttackKind kind=AttackKind::invalid, + double level=0) + :NestedDefense{p}, _kind{kind}, _lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return defensePolicy()->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_level_deco " << static_cast(_kind) + << " " << _lvl << "\n"; + defensePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + int x; + is >> x; + _kind = static_cast(x); + is >> _lvl; + + return !is.fail() && NestedDefense::restore(is, tab); + } +}; + +class MultiplierDefensePolicy: public NestedDefense { + double _mul; + +public: + explicit MultiplierDefensePolicy(DefensePolicy *p=nullptr, + double mul=0) + :NestedDefense{p}, _mul{mul} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + DamageSpec ds = defensePolicy()->actualDamage(u, kind, base); + ds.scale(1/_mul); + return ds; + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_multiplier " << _mul << "\n"; + defensePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _mul; + return !is.fail() && NestedDefense::restore(is, tab); + } +}; + + + +class AttackForbidden: public AttackPolicy { +public: + using AttackPolicy::AttackPolicy; + + virtual bool + canAttackTo(const Unit *, MapIter) override + { + return false; + } + + virtual std::pair + baseAttack(const Unit *, MapIter) override + { + return std::make_pair(AttackKind::invalid, 0); + } + + TRIVIALLY_STORABLE("ap_forbidden"); +}; + +class NestedAttack: public AttackPolicy, + public AttackPolicyContainer { +public: + using AttackPolicyContainer::AttackPolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setAttackPolicy(p); + return true; + } + delete s; + return false; + } +}; + +class MultiplierAttackPolicy: public NestedAttack { + double _mul; + +public: + explicit MultiplierAttackPolicy(AttackPolicy *p=nullptr, + double mul=0) + :NestedAttack{p}, _mul{mul} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return attackPolicy()->canAttackTo(u, to); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + auto ba = attackPolicy()->baseAttack(u, to); + return std::make_pair(ba.first, (int)(ba.second * _mul)); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_multiplier " << _mul << "\n"; + attackPolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _mul; + return !is.fail() && NestedAttack::restore(is, tab); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab6/common_storables.hpp b/8303/Parfentev_Leonid/lab6/common_storables.hpp new file mode 100644 index 000000000..27cfd5a0a --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/common_storables.hpp @@ -0,0 +1,117 @@ +#ifndef _H_COMMON_STORABLES_HPP +#define _H_COMMON_STORABLES_HPP + +#include "storable.hpp" +#include "object_print.hpp" + + +class StorableEnd: public Storable { + TRIVIALLY_STORABLE("end"); +}; + +class StorableCoordinates: public Storable { + Vec2 _c; + +public: + StorableCoordinates() {} + + StorableCoordinates(Vec2 c) :_c{c} {} + + virtual void + store(std::ostream &os) const override + { + os << "coords " << _c.x() << " " << _c.y() << "\n"; + } + + virtual bool + restore(std::istream &is, + RestorerTable *) override + { + int x, y; + is >> x >> y; + _c = Vec2{x, y}; + return !is.fail(); + } + + Vec2 coords() const { return _c; } +}; + +class StorableWithIndex: public Storable { + int _i; + Storable *_s; + +public: + StorableWithIndex() {} + + StorableWithIndex(int idx, Storable *s) + :_i{idx}, _s{s} {} + + virtual void + store(std::ostream &os) const override + { + os << "index " << _i << " "; + _s->store(os); + } + + virtual bool + restore(std::istream &is, + RestorerTable *tab) override + { + is >> _i; + _s = tab->restore(is); + return !is.fail() && _s; + } + + int index() const { return _i; } + Storable *child() const { return _s; } + + static void + storeWithIndex(int idx, const Storable *s, + std::ostream &os) + { + os << "index " << idx << " "; + s->store(os); + } +}; + +class StorableWithCoords: public Storable { + Vec2 _c; + Storable *_s; + +public: + StorableWithCoords() {} + + StorableWithCoords(Vec2 c, Storable *s) + :_c{c}, _s{s} {} + + virtual void + store(std::ostream &os) const override + { + os << "at " << _c.x() << " " << _c.y() << " "; + _s->store(os); + } + + virtual bool + restore(std::istream &is, + RestorerTable *tab) override + { + int x, y; + is >> x >> y; + _c = Vec2{x, y}; + _s = tab->restore(is); + return !is.fail() && _s; + } + + Vec2 coords() const { return _c; } + Storable *child() const { return _s; } + + static void + storeWithCoords(Vec2 pt, const Storable *s, + std::ostream &os) + { + os << "at " << pt.x() << " " << pt.y() << " "; + s->store(os); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/demo.cpp b/8303/Parfentev_Leonid/lab6/demo.cpp new file mode 100644 index 000000000..2c438ae19 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/demo.cpp @@ -0,0 +1,520 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + +#include "event.hpp" +#include "event_types.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "unit_factory.hpp" + +#include "game.hpp" +#include "event_printer.hpp" +#include "iostream_player.hpp" + +#include "factory_table.hpp" + + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} + +MapIter +doAttack(Unit *u, MapIter to) +{ + if (!u->canAttackTo(to)) { + return MapIter::makeNull(); + + } else { + auto pos = u->actualPosition(to); + + if (Unit *targ = pos.unit()) { + auto dam = u->baseAttack(pos); + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + return pos; + } + + return MapIter::makeNull(); + } +} + +struct SimpleGame { + Map *map; + Base *b1, *b2; + EventPrinter *pr; + + explicit SimpleGame(int w=10, int h=10, + int x1=1, int y1=1, + int x2=9, int y2=9) + { + pr = new EventPrinter {std::cout}; + + map = new Map {w, h}; + + b1 = new Base {}; + pr->setPrefix(b1, "Base 1"); + + map->addBase(b1, {x1, y1}); + b1->subscribe(pr); + + b2 = new Base {}; + pr->setPrefix(b2, "Base 2"); + + map->addBase(b2, {x2, y2}); + b2->subscribe(pr); + } + + ~SimpleGame() + { + delete map; + delete pr; + } +}; + +void +demo4() +{ + SimpleGame g {}; + + Unit *u1 = new units::Swordsman {}; + Unit *u2 = new units::Swordsman {}; + + g.b1->addUnit(u1); + g.b2->addUnit(u2); + + g.map->addUnit(u1, {3, 3}); + g.map->addUnit(u2, {4, 3}); + + while (u1->alive() + && u2->alive()) { + doAttack(u1, g.map->iterAt(u2->position())); + if (u2->alive()) { + doAttack(u2, g.map->iterAt(u1->position())); + } + } +} + +MapIter +doMove(Map *map, const Unit *u, Vec2 pt) +{ + auto from = map->iterAt(u->position()); + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + return MapIter::makeNull(); + } + + return map->addUnit(map->removeUnitAt(from), pt); +} + +Unit * +setupUnit(Base *base, const std::string &k, Mediator *m, Vec2 pt) +{ + Unit *u = base->getUnitById(base->createUnit(k, m)); + m->teleportUnit(u, pt); + return u; +} + +void +demo5() +{ + SimpleGame g {}; + + // 2,2 .. 5,5: swamp + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + g.map->setLandscape(new landscapes::Swamp {}, + {2+i, 2+j}); + } + } + + // 1,7 .. 6,9: forest + for (int j = 0; j < 2; ++j) { + for (int i = 0; i < 5; ++i) { + g.map->setLandscape(new landscapes::Forest {}, + {1+i, 7+j}); + } + } + + auto *m = new Mediator {g.map}; + auto u1 = setupUnit(g.b1, "swordsman", m, {2, 2}); + auto u2 = setupUnit(g.b2, "swordsman", m, {3, 2}); + + if (doMove(g.map, u1, {0, 2}).null()) { + std::cout << "Can't move " << u1 << " across 2 cells\n"; + } else { + std::cout << "Moved " << u1 << " across 2 cells \n"; + } + + std::cout << "u1: " << u1 << "\n"; + + if (doAttack(u1, g.map->iterAt(u2->position())).null()) { + std::cout << "Can't attack in swamp\n"; + } else { + std::cout << "Attacked in a swamp\n"; + } + + std::cout << "u2: " << u2 << "\n"; + + doMove(g.map, u1, {1, 2}); + doMove(g.map, u2, {2, 3}); + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + doAttack(u1, g.map->iterAt(u2->position())); + + auto u3 = setupUnit(g.b1, "spearsman", m, {3, 8}); + auto u4 = setupUnit(g.b1, "spearsman", m, {7, 8}); + auto u5 = setupUnit(g.b2, "onager", m, {5, 5}); + + while (u3->alive()) + doAttack(u5, g.map->iterAt(u3->position())); + + while (u4->alive()) + doAttack(u5, g.map->iterAt(u4->position())); + + std::cout << g.map; + + delete m; +} + +void +demo6() +{ + SimpleGame g {}; + + auto *m = new Mediator {g.map}; + auto *u1 = setupUnit(g.b1, "slinger", m, {1, 5}); + auto *u2 = setupUnit(g.b2, "swordsman", m, {6, 5}); + + g.map->addNeutralObject(new objects::Tower {}, {1, 5}); + g.map->addNeutralObject(new objects::HealingWell {}, {6, 5}); + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " can't reach " << u2 << "\n"; + } else { + std::cout << u1 << " somehow reached " << u2 << "\n"; + } + + if (m->useObject(u1)) { + std::cout << u1 << " used the tower\n"; + } else { + std::cout << u1 << " can't use the tower\n"; + } + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " still can't reach " << u2 << "\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + if (m->useObject(u2)) { + std::cout << u2 << " used the healing well\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; +} + +void +demo7() +{ + class MoverPlayer: public Player { + std::string _unit_type; + int _id = -1; + + public: + using Player::Player; + + MoverPlayer(std::string name, + std::string type) + :Player{name}, _unit_type{std::move(type)} {} + + virtual bool + takeTurn(const Game *, Mediator *m, Base *b) override + { + if (_id >= 0) { + Unit *u = b->getUnitById(_id); + m->moveUnitTo(u, u->position().shifted({1, 0})); + } else { + _id = b->createUnit(_unit_type, m); + } + return true; + } + }; + + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + auto *b = new Base {}; + map->addBase(b, {3, 3}); + int b_id = g.addBase(b); + + auto *p = new MoverPlayer {"Player 1", "swordsman"}; + g.setPlayer(b_id, p); + + for (int i = 0; i < 5; ++i) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo8() +{ + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + std::stringstream s1 {}; + s1 << "base\n" + << "create spearsman\n" + << "map 0 0 9 9\n" + << "move 0 3 5\n" + << "map 0 0 9 9\n" + << "describe 3 5\n"; + + auto *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + std::stringstream s2 {}; + s2 << "create archer\n" + << "attack 0 3 5\n"; + + auto *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo9() +{ + Map *map = new Map {10, 10}; + + auto *uf = (new objects + ::WeaponSmiths + ::CatapultUnitFilter {}); + auto *ws = new objects::WeaponSmiths {2.0, uf}; + map->addNeutralObject(ws, {3, 4}); + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + Base *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + Base *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + std::stringstream s1 {}; + std::stringstream s2 {}; + + s1 << "create onager\n" + << "move 0 3 4\n" + << "use 0\n" + << "create onager\n" + << "move 1 3 2\n" + << "attack 0 7 4\n" + << "attack 1 7 2\n" + << "create swordsman\n" + << "move 0 3 5\n" + << "move 2 3 4\n" + << "use 2\n"; + + s2 << "create swordsman\n" + << "move 0 7 4\n" + << "create swordsman\n" + << "move 1 7 2\n" + << "skip skip skip skip skip\n"; + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} diff --git a/8303/Parfentev_Leonid/lab6/demo.hpp b/8303/Parfentev_Leonid/lab6/demo.hpp new file mode 100644 index 000000000..ae8bc7a95 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/demo.hpp @@ -0,0 +1,14 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); +void demo4(); +void demo5(); +void demo6(); +void demo7(); +void demo8(); +void demo9(); + +#endif diff --git a/8303/Parfentev_Leonid/lab6/event.cpp b/8303/Parfentev_Leonid/lab6/event.cpp new file mode 100644 index 000000000..b7ff78ebd --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/event.cpp @@ -0,0 +1,31 @@ +#include "event.hpp" + +void +EventEmitter::emit_shared(Event *e) const +{ + for (auto iter = _listeners.begin(); iter != _listeners.end();) { + auto *listener = *iter++; + // note: the listener may safely unsubscribe when handling the + // event. + listener->handle(e); + } +} + +void +EventEmitter::emit(Event *e) const +{ + emit_shared(e); + delete e; +} + +void +EventEmitter::subscribe(EventListener *l) +{ + _listeners.insert(l); +} + +void +EventEmitter::unsubscribe(EventListener *l) +{ + _listeners.erase(l); +} diff --git a/8303/Parfentev_Leonid/lab6/event.hpp b/8303/Parfentev_Leonid/lab6/event.hpp new file mode 100644 index 000000000..fa2d1f541 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/event.hpp @@ -0,0 +1,62 @@ +#ifndef _H_EVENT_HPP +#define _H_EVENT_HPP + +#include + +class EventListener; +class Event; + +class EventEmitter { + std::set _listeners {}; + +public: + void emit_shared(Event *e) const; + void emit(Event *e) const; + + void subscribe(EventListener *l); + void unsubscribe(EventListener *l); + + virtual ~EventEmitter() {} +}; + +class Event { +public: + virtual ~Event() {} +}; + +class EventListener { +public: + virtual void handle(Event *e) =0; + + virtual ~EventListener() {} +}; + +class EventForwarder; + +namespace events { + + class Forwarded: public Event { + Event *_e; + EventForwarder *_f; + + public: + Forwarded(Event *e, EventForwarder *f) + :_e{e}, _f{f} {} + + Event *event() const { return _e; } + EventForwarder *forwarder() const { return _f; } + }; + +} + +class EventForwarder: public EventEmitter, + public EventListener { +public: + virtual void + handle(Event *e) override + { + emit(new events::Forwarded {e, this}); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/event_printer.hpp b/8303/Parfentev_Leonid/lab6/event_printer.hpp new file mode 100644 index 000000000..afeca85b3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/event_printer.hpp @@ -0,0 +1,127 @@ +#ifndef _H_EVENT_PRINTER_HPP +#define _H_EVENT_PRINTER_HPP + +#include +#include +#include +#include + +#include "event.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "player.hpp" +#include "object_print.hpp" + + +class EventPrinter: public EventListener { + std::ostream *_os; + bool _free_os; + + std::map _prefix_map {}; + + std::string + makeName(const char *base, int idx) + { + std::ostringstream oss {}; + oss << base << " " << idx; + return oss.str(); + } + +public: + EventPrinter(std::ostream &os) + :_os{&os}, _free_os{false} {} + + EventPrinter(std::ostream *os) + :_os{os}, _free_os{true} {} + + std::ostream & + ostream() const { return *_os; } + + void + setPrefix(EventForwarder *f, const std::string &s) + { + _prefix_map[f] = s; + } + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + auto iter = _prefix_map.find(ee->forwarder()); + if (iter != _prefix_map.end()) { + (*_os) << iter->second << ": "; + } + + return handle(ee->event()); + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit added: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit died: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " takes " + << ee->damage() << " health points of damage\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " gets healed by " + << ee->health() << " health points\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->attacker() + << " attacked another unit " << ee->target() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->target() + << " was attacked by another unit " + << ee->attacker() << "\n"; + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " moved from " << ee->sourcePos() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " used object " << ee->neutralObject() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "(Live unit " << ((void *)ee->unit()) + << " deleted)\n"; + + } else if (dynamic_cast(e)) { + (*_os) << "Base destroyed\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << " over\n"; + + } else { + (*_os) << "Unknown event\n"; + } + } + + virtual ~EventPrinter() override + { + if (_free_os) { + _os->flush(); + delete _os; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/event_types.hpp b/8303/Parfentev_Leonid/lab6/event_types.hpp new file mode 100644 index 000000000..810d3bd94 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/event_types.hpp @@ -0,0 +1,156 @@ +#ifndef _H_EVENT_TYPES_HPP +#define _H_EVENT_TYPES_HPP + +#include "point.hpp" +#include "event.hpp" + + +class Unit; +class Base; +class NeutralObject; +class Player; + + +class UnitEvent: public Event { + Unit *_u; + +public: + UnitEvent(Unit *u) :_u{u} {} + + Unit *unit() const { return _u; } +}; + +class AttackEvent: public Event { + Unit *_a, *_b; + +public: + AttackEvent(Unit *a, Unit *b) :_a{a}, _b{b} {} + + Unit *attacker() const { return _a; } + Unit *target() const { return _b; } +}; + +class BaseEvent: public Event { + Base *_b; + +public: + BaseEvent(Base *b) :_b{b} {} + + Base *base() const { return _b; } +}; + +class PlayerEvent: public Event { + Player *_p; + +public: + PlayerEvent(Player *p) :_p{p} {} + + Player *player() const { return _p; } +}; + +class UnitMoveEvent: public UnitEvent { + Vec2 _src; + +public: + UnitMoveEvent(Unit *u, Vec2 src) + :UnitEvent{u}, _src{src} {} + + Vec2 sourcePos() const { return _src; } +}; + + + +namespace events { + + class UnitDeath: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitAdded: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitLiveDeleted: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitTakesDamage: public UnitEvent { + int _dmg; + + public: + UnitTakesDamage(Unit *u, int dmg) + :UnitEvent{u}, _dmg{dmg} {} + + int damage() const { return _dmg; } + }; + + class UnitGetsHealed: public UnitEvent { + int _hp; + + public: + UnitGetsHealed(Unit *u, int hp) + :UnitEvent{u}, _hp{hp} {} + + int health() const { return _hp; } + }; + + class UnitWasAttacked: public AttackEvent { + public: + using AttackEvent::AttackEvent; + }; + + class UnitAttacked: public AttackEvent { + Vec2 _tc; + + public: + UnitAttacked(Unit *u, Vec2 tc, Unit *tu) + :AttackEvent{u, tu}, _tc{tc} {} + + Vec2 targetCoordinates() const { return _tc; } + }; + + class UnitMoved: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitTeleported: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitUsedObject: public UnitEvent { + NeutralObject *_n; + + public: + UnitUsedObject(Unit *u, NeutralObject *n) + :UnitEvent{u}, _n{n} {} + + NeutralObject *neutralObject() const { return _n; } + }; + + + + class BaseDestroyed: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + + + class TurnStarted: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + + class TurnOver: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/factory_table.hpp b/8303/Parfentev_Leonid/lab6/factory_table.hpp new file mode 100644 index 000000000..693031648 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/factory_table.hpp @@ -0,0 +1,81 @@ +#ifndef _H_FACTORY_TABLE_HPP +#define _H_FACTORY_TABLE_HPP + +#include +#include + +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "unit_factory.hpp" + + +class FactoryTable { + std::map _tab {}; + + void + registerFactory(const std::string &key, UnitFactory *f) + { + _tab[key] = f; + } + + FactoryTable() + { +#define REG(k, T) \ + registerFactory( \ + k, new SimpleUnitFactory {}) + REG("swordsman", Swordsman); + REG("spearsman", Spearsman); + REG("cavalry", Cavalry); + REG("archer", Archer); + REG("slinger", Slinger); + REG("onager", Onager); + REG("boltthrower", BoltThrower); +#undef REG + } + +public: + static const FactoryTable * + instance() + { + static FactoryTable *inst = new FactoryTable {}; + return inst; + } + + virtual bool + canCreate(const std::string &key) const + { + return _tab.find(key) != _tab.end(); + } + + virtual std::vector + keys() const + { + std::vector keys {}; + for (auto p: _tab) { + keys.push_back(p.first); + } + return keys; + } + + virtual Unit * + create(const std::string &key) const + { + auto iter = _tab.find(key); + if (iter == _tab.end()) { + return nullptr; + } + + return iter->second->create(); + } + + ~FactoryTable() + { + for (auto p: _tab) { + delete p.second; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/game.cpp b/8303/Parfentev_Leonid/lab6/game.cpp new file mode 100644 index 000000000..dd9272b12 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/game.cpp @@ -0,0 +1,215 @@ +#include +#include + +#include "base.hpp" +#include "game.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "storable.hpp" +#include "common_storables.hpp" + + +Game::Game(Map *map) + :_map{map}, + _med{new Mediator {map}} +{ + this->subscribe(_log_sink); +} + +int +Game::addBase(Base *base) +{ + base->subscribe(_coll); + base->subscribe(this); + + _recs.push_back(BaseRecord{base, nullptr}); + + return (int)(_recs.size() - 1); +} + +void +Game::setPlayer(int base_idx, Player *p) +{ + Player *&p_place = _recs[base_idx].player; + if (p_place) { + p_place->unsubscribe(_log_sink); + delete p_place; + --_players; + } + p_place = p; + if (p) { + p->subscribe(_log_sink); + ++_players; + } +} + +int +Game::basesCount() const +{ + return _recs.size(); +} + +int +Game::playersCount() const +{ + return _players; +} + +Base * +Game::baseByIdx(int idx) const +{ + return _recs[idx].base; +} + +void +Game::spin() +{ + auto &rec = _recs[_next]; + Player *p = rec.player; + Base *b = rec.base; + + if (p) { + if (b->destroyed()) { + setPlayer(_next, nullptr); + + } else { + emit(new events::TurnStarted {p}); + + bool res = p->takeTurn(this, _med, b); + + _coll->collect(_map); + + emit(new events::TurnOver {p}); + + if (!res) { + setPlayer(_next, nullptr); + } + } + } + + if (++_next == (int)_recs.size()) { + _next = 0; + } +} + +void +Game::setResetHandler(ResetHandler *r) +{ + _reset = r; +} + +void +Game::requestReset() const +{ + _reset->reset(); +} + +void +Game::store(std::ostream &os) const +{ + os << "game\n"; + _map->store(os); + + os << _next << "\n"; + + for (int i = 0; i < basesCount(); ++i) { + auto *b = baseByIdx(i); + + StorableWithCoords::storeWithCoords(b->position(), b, os); + + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + auto *u = iter.unit(); + auto *sc = new StorableWithCoords {u->position(), u}; + StorableWithIndex::storeWithIndex(i, sc, os); + } + } + + for (int i = 0; i < basesCount(); ++i) { + auto *p = _recs[i].player; + if (p) { + StorableWithIndex::storeWithIndex(i, p, os); + } + } + + os << "end\n"; +} + +bool +Game::restore(std::istream &is, + RestorerTable *tab) +{ + is >> _next; + + for (;;) { + Storable *s = tab->restore(is); + if (auto *sc = + dynamic_cast(s)) { + if (auto *b + = dynamic_cast(sc->child())) { + _map->addBase(b, sc->coords()); + addBase(b); + } else { + delete sc->child(); + delete sc; + return false; + } + delete sc; + } else if (auto *si = + dynamic_cast(s)) { + if (auto *p + = dynamic_cast(si->child())) { + if (si->index() < 0 + || si->index() >= basesCount()) { + delete p; + delete si; + return false; + } + setPlayer(si->index(), p); + } else if (auto *sc = + dynamic_cast( + si->child())) { + if (auto *u + = dynamic_cast(sc->child())) { + _map->addUnit(u, sc->coords()); + baseByIdx(si->index())->addUnit(u); + delete sc; + } else { + delete si; + delete sc; + delete sc->child(); + return false; + } + } else { + delete si->child(); + delete si; + return false; + } + delete si; + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} + +Game::~Game() +{ + for (int i = 0; i < (int)_recs.size(); ++i) { + setPlayer(i, nullptr); + auto &rec = _recs[i]; + rec.base->unsubscribe(this); + rec.base->unsubscribe(_coll); + } + + delete _coll; + delete _map; + + delete _log_sink; +} diff --git a/8303/Parfentev_Leonid/lab6/game.hpp b/8303/Parfentev_Leonid/lab6/game.hpp new file mode 100644 index 000000000..732271be2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/game.hpp @@ -0,0 +1,68 @@ +#ifndef _H_GAME_HPP +#define _H_GAME_HPP + +#include + +#include "event.hpp" +#include "map.hpp" +#include "base.hpp" +#include "player.hpp" +#include "zombie_collector.hpp" +#include "mediator.hpp" +#include "storable.hpp" + + +class ResetHandler { +public: + virtual void reset() =0; + + virtual ~ResetHandler() {} +}; + +class Game: public EventForwarder, + public Storable { + Map *_map; + Mediator *_med; + + struct BaseRecord { + Base *base; + Player *player; + }; + + std::vector _recs {}; + int _next = 0; + int _players = 0; + + ZombieCollector *_coll {new ZombieCollector {}}; + + EventForwarder *_log_sink {new EventForwarder {}}; + + ResetHandler *_reset; + +public: + Game(Map *map); + + int addBase(Base *b); + void setPlayer(int base_idx, Player *p); + + int basesCount() const; + int playersCount() const; + + Base *baseByIdx(int idx) const; + + EventForwarder *logSink() const { return _log_sink; } + + void spin(); + + void setResetHandler(ResetHandler *r); + void requestReset() const; + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; + + ~Game(); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab6/game_driver.hpp b/8303/Parfentev_Leonid/lab6/game_driver.hpp new file mode 100644 index 000000000..79f78c3fd --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/game_driver.hpp @@ -0,0 +1,123 @@ +#ifndef _H_GAME_DRIVER_HPP +#define _H_GAME_DRIVER_HPP + +#include "map.hpp" +#include "game.hpp" + + +class GameRules { +public: + virtual Map *makeMap() =0; + virtual void setup(Game *g, Map *m) =0; + virtual bool gameEnded(Game *g) =0; + virtual int winner(Game *g) =0; +}; + +template +class GameDriver: public ResetHandler { + + Game *_g = nullptr; + Game *_g_reset = nullptr; // required to reset game while running + Rules *_r {new Rules {}}; + + std::vector _loggers {}; + EventPrinter *_printer = nullptr; + + Game *init() + { + Map *m = _r->makeMap(); + Game *g = new Game {m}; + _r->setup(g, m); + return g; + } + + void setBasePrefixes(EventPrinter *p) + { + int n = _g->basesCount(); + for (int i = 0; i < n; ++i) { + std::ostringstream oss {}; + oss << "Base " << (i+1); + p->setPrefix(_g->baseByIdx(i), oss.str()); + } + } + +public: + void + addLogger(EventPrinter *l) + { + _loggers.push_back(l); + if (_g) { + _g->logSink()->subscribe(l); + setBasePrefixes(l); + } + } + + void + setPrinter(EventPrinter *pr) + { + if (_printer) { + if (_g) { + _g->unsubscribe(_printer); + } + delete _printer; + } + + _printer = pr; + if (_g && _printer) { + _g->subscribe(_printer); + setBasePrefixes(_printer); + } + } + + virtual void reset() override + { + resetFrom(init()); + } + + void resetFrom(Game *g) + { + _g_reset = g; + } + + void run() + { + for (;;) { + if (_g_reset) { + delete _g; + _g = _g_reset; + _g_reset = nullptr; + _g->setResetHandler(this); + + if (_printer) { + _g->subscribe(_printer); + setBasePrefixes(_printer); + } + for (auto *l: _loggers) { + _g->logSink()->subscribe(l); + setBasePrefixes(l); + } + } + if (_r->gameEnded(_g)) { + break; + } + _g->spin(); + } + } + + int winner() + { + return _r->winner(_g); + } + + virtual ~GameDriver() override + { + delete _g; + delete _g_reset; + delete _printer; + for (auto *l: _loggers) { + delete l; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/game_rules.hpp b/8303/Parfentev_Leonid/lab6/game_rules.hpp new file mode 100644 index 000000000..6a43579aa --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/game_rules.hpp @@ -0,0 +1,145 @@ +#ifndef _H_GAME_RULES_HPP +#define _H_GAME_RULES_HPP + +#include +#include + +#include "map.hpp" +#include "game.hpp" +#include "iostream_player.hpp" +#include "game_driver.hpp" +#include "landscape_types.hpp" + + +class BaseWithSpawnCountdown: public Base { + int _cd_max; + int _cd = 0; + +public: + BaseWithSpawnCountdown(int cd_max=0) + :_cd_max{cd_max} {} + + virtual bool + canCreateUnit(const std::string &key) const override + { + return _cd == 0 + && Base::canCreateUnit(key); + } + + virtual int + createUnit(const std::string &key, Mediator *m) override + { + int id = Base::createUnit(key, m); + if (id < 0) { + return id; + } + + _cd = _cd_max; + return id; + } + + virtual void + store(std::ostream &os) const override + { + os << "base_w_countdown " << maxUnitsCount() + << " " << destroyed() << " " << _cd_max << " " + << _cd << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) override + { + if (!Base::restore(is, tab)) { + return false; + } + + is >> _cd_max >> _cd; + return !is.fail(); + } + + virtual void + spin() override + { + if (_cd > 0) { + --_cd; + } + } +}; + +class DefaultRules: public GameRules { +protected: + static void + addBaseAndPlayer(Game *g, Map *m, + std::string name, int x, int y) + { + Base *b = new BaseWithSpawnCountdown {5}; + m->addBase(b, {x, y}); + int id = g->addBase(b); + + auto *p = new IostreamPlayer {std::move(name)}; + p->setOstream(std::cout); + p->setIstream(std::cin); + g->setPlayer(id, p); + } + +public: + virtual Map *makeMap() override + { + return new Map {10, 10}; + } + + virtual void setup(Game *g, Map *m) override + { + addBaseAndPlayer(g, m, "Player 1", 1, 1); + addBaseAndPlayer(g, m, "Player 2", 8, 8); + } + + virtual bool gameEnded(Game *g) override + { + return g->playersCount() < 2; + } + + virtual int winner(Game *g) override + { + int n = g->basesCount(); + int only = -1; + + for (int i = 0; i < n; ++i) { + if (!g->baseByIdx(i)->destroyed()) { + if (only < 0) { + only = i; + } else { + return -1; + } + } + } + + return only; + } +}; + +class FancyRules: public DefaultRules { + virtual Map *makeMap() override + { + Map *m = new Map {15, 15}; + + for (int y = 0; y < 5; ++y) { + for (int x = 0; x < 5; ++x) { + m->setLandscape(new landscapes::Forest {}, + {5+x, 5+y}); + } + } + + return m; + } + + virtual void setup(Game *g, Map *m) override + { + addBaseAndPlayer(g, m, "Player 1", 3, 3); + addBaseAndPlayer(g, m, "Player 2", 3, 11); + addBaseAndPlayer(g, m, "Player 3", 11, 11); + addBaseAndPlayer(g, m, "Player 4", 11, 3); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/iostream_player.cpp b/8303/Parfentev_Leonid/lab6/iostream_player.cpp new file mode 100644 index 000000000..b66882c5e --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/iostream_player.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +#include "game.hpp" +#include "base.hpp" +#include "iostream_player.hpp" +#include "event.hpp" +#include "logging.hpp" + + +void +IostreamPlayer::addCommand(const std::string &str, + IostreamCommand *cmd) +{ + _cmd_tab[str] = cmd; +} + +IostreamPlayer::IostreamPlayer(std::string name) + :Player{name} +{ + addCommand("move", new iostream_commands::Move {}); + addCommand("attack", new iostream_commands::Attack {}); + addCommand("use", new iostream_commands::Use {}); + addCommand("destroy", new iostream_commands::DestroyBase {}); + addCommand("create", new iostream_commands::Create {}); + addCommand("base", new iostream_commands::FindBase {}); + addCommand("units", new iostream_commands::ListUnits {}); + addCommand("describe", new iostream_commands::DescribeAt {}); + addCommand("map", new iostream_commands::PrintMap {}); + addCommand("skip", new iostream_commands::Skip {}); + addCommand("save", new iostream_commands::Save {}); + addCommand("reset", new iostream_commands::Reset {}); +} + +bool +IostreamPlayer::takeTurn(const Game *g, Mediator *m, Base *b) +{ + for (;;) { + std::string cmd_name; + (*_is) >> cmd_name; + + if (_is->fail()) { + return false; + } + + { + std::ostringstream oss {}; + oss << "User picked action: " << cmd_name; + emit(new events::UserActionEvent {this, oss.str()}); + } + + auto iter = _cmd_tab.find(cmd_name); + if (iter == _cmd_tab.end()) { + std::ostringstream oss {}; + oss << "Unknown command: \"" << cmd_name << "\""; + emit(new events::UserActionEvent {this, oss.str()}); + (*_os) << oss.str() << "\n"; + continue; + } + + if (iter->second->execute(g, this, m, b)) { + break; + } + } + + return true; +} + +int +IostreamPlayer::readInt() +{ + int x; + (*_is) >> x; + return x; +} + +Unit * +IostreamPlayer::readUnitId(Base *b) +{ + int id = readInt(); + Unit *u = b->getUnitById(id); + if (!u) { + (*_os) << "No such unit: " << id << "\n"; + } + + return u; +} + +Vec2 +IostreamPlayer::readVec2() +{ + int x, y; + (*_is) >> x >> y; + return {x, y}; +} + +std::string +IostreamPlayer::readString() +{ + std::string s; + (*_is) >> s; + return s; +} + +void +IostreamPlayer::store(std::ostream &os) const +{ + os << "iostream_player\n" << name() << "\n"; +} + +bool +IostreamPlayer::restore(std::istream &is, + RestorerTable *tab) +{ + Player::restore(is, tab); + + // We can’t serialize/deserialize IO streams + setOstream(std::cout); + setIstream(std::cin); + return true; +} diff --git a/8303/Parfentev_Leonid/lab6/iostream_player.hpp b/8303/Parfentev_Leonid/lab6/iostream_player.hpp new file mode 100644 index 000000000..95e5a8345 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/iostream_player.hpp @@ -0,0 +1,366 @@ +#ifndef _H_STDIO_PLAYER_HPP +#define _H_STDIO_PLAYER_HPP + +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "game.hpp" +#include "object_print.hpp" + +#include "event.hpp" +#include "logging.hpp" + + +class IostreamPlayer; + +class IostreamCommand { +public: + // -> whether to end turn + virtual bool execute(const Game *g, IostreamPlayer *p, + Mediator *m, Base *b) =0; + + virtual ~IostreamCommand() {} +}; + +class IostreamPlayer: public Player { + std::ostream *_os = nullptr; + std::istream *_is = nullptr; + bool _free_os, _free_is; + + std::map _cmd_tab {}; + + void + addCommand(const std::string &str, + IostreamCommand *cmd); + + +public: + IostreamPlayer(std::string name=""); + + void + setOstream(std::ostream *os) { _os = os; _free_is = true; } + void + setOstream(std::ostream &os) { _os = &os; _free_os = false; } + + std::ostream & + ostream() const { return *_os; } + + void + setIstream(std::istream *is) { _is = is; _free_is = true; } + void + setIstream(std::istream &is) { _is = &is; _free_is = false; } + + std::istream & + istream() const { return *_is; } + + virtual bool + takeTurn(const Game *g, Mediator *m, Base *b) override; + + int + readInt(); + + Unit * + readUnitId(Base *b); + + Vec2 + readVec2(); + + std::string + readString(); + + virtual void store(std::ostream &os) const override; + + virtual bool restore(std::istream &is, + RestorerTable *tab) override; +}; + +namespace iostream_commands { + + class Move: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested moving " << u << " to " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->moveUnitTo(u, to)) { + p->ostream() << "Can't move unit " << u + << " to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Attack: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u << " attacks " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->attackTo(u, to)) { + p->ostream() << "Unit " << u + << " can't attack to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Use: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u + << " uses an object"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->useObject(u)) { + p->ostream() << "Unit " << u + << " can't use any object there\n"; + return false; + } + + return true; + } + }; + + class DestroyBase: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u + << " destroys a base"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->destroyBase(u)) { + p->ostream() << "Unit " << u + << " cannot destroy any bases there\n"; + return false; + } + + return true; + } + }; + + class Create: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + std::string s = p->readString(); + + { + std::ostringstream oss {}; + oss << "User requested creation of unit " << s; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!b->canCreateUnit(s)) { + p->ostream() << "Can't create unit of type " + << s << "\n"; + return false; + } + + int id = b->createUnit(s, m); + if (id < 0) { + p->ostream() << "Failed to create a unit of type " + << s << "\n"; + return false; + } + + p->ostream() << "New unit of type " << s + << ": " << id << "\n"; + return true; + } + }; + + class FindBase: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *b) override + { + p->ostream() << "Base: " << b << "\n"; + return false; + } + }; + + class ListUnits: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *b) override + { + p->ostream() << "Units:"; + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + p->ostream() << "- " << iter.id() + << ": " << iter.unit() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class DescribeAt: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *) override + { + auto pos = p->readVec2(); + auto info = m->infoAt(pos); + + p->ostream() << "At " << pos << "\n"; + p->ostream() + << "- Landscape: " << info.landscape() << "\n"; + + if (auto *b = info.base()) { + p->ostream() << "- Base: " << b << "\n"; + } + + if (auto *u = info.unit()) { + p->ostream() << "- Unit: " << u << "\n"; + } + + if (auto *n = info.neutralObject()) { + p->ostream() << "- Object: " << n << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class PrintMap: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *) override + { + auto from = p->readVec2(); + auto to = p->readVec2(); + + p->ostream() << "From " << from + << " to " << to << ":\n"; + + for (int y = from.y(); y < to.y(); ++y) { + for (int x = from.x(); x < to.x(); ++x) { + if (x != from.x()) { + p->ostream() << " "; + } + displayMapInfo(p->ostream(), m->infoAt({x, y})); + } + p->ostream() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class Skip: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *) override + { + std::ostringstream oss {}; + oss << "User decided to skip the turn"; + p->emit(new events::UserActionEvent {p, oss.str()}); + + return true; + } + }; + + class Save: public IostreamCommand { + public: + virtual bool + execute(const Game *g, IostreamPlayer *p, + Mediator *, Base *) override + { + std::string fn = p->readString(); + std::ofstream of {fn}; + if (!of) { + p->ostream() << "Failed to open file\n"; + return false; + } + + g->store(of); + p->ostream() << "Save complete\n"; + + std::ostringstream oss {}; + oss << "Saved current game to " << fn; + p->emit(new events::UserActionEvent {p, oss.str()}); + + return false; + } + }; + + class Reset: public IostreamCommand { + public: + virtual bool + execute(const Game *g, IostreamPlayer *p, + Mediator *, Base *) override + { + std::ostringstream oss {}; + oss << "Requesting reset"; + p->emit(new events::UserActionEvent {p, oss.str()}); + + g->requestReset(); + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/landscape.hpp b/8303/Parfentev_Leonid/lab6/landscape.hpp new file mode 100644 index 000000000..3df7e506d --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/landscape.hpp @@ -0,0 +1,29 @@ +#ifndef _H_LANDSCAPE_HPP +#define _H_LANDSCAPE_HPP + + +#include "storable.hpp" + +class Unit; + +class Landscape: public Storable { +public: + virtual void onEnter(Unit *u) =0; + virtual void onLeave(Unit *u) =0; + + virtual ~Landscape() {} +}; + +namespace landscapes { + + class Normal: public Landscape { + public: + virtual void onEnter(Unit *) override {} + virtual void onLeave(Unit *) override {} + + TRIVIALLY_STORABLE("l_normal"); + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/landscape_types.hpp b/8303/Parfentev_Leonid/lab6/landscape_types.hpp new file mode 100644 index 000000000..c53d2b99a --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/landscape_types.hpp @@ -0,0 +1,75 @@ +#ifndef _H_LANDSCAPE_TYPES_HPP +#define _H_LANDSCAPE_TYPES_HPP + +#include "landscape.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "common_policies.hpp" +#include "storable.hpp" + + +namespace landscapes { + + // Swamp: max speed is 1; attacking is forbidden + class Swamp: public Landscape { + ModifyingMovePolicy *_p; + AttackPolicy *_prev, *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _p = new ModifyingMovePolicy {u->movePolicy(), 1}; + u->setMovePolicy(_p); + + _prev = u->attackPolicy(); + _cur = new AttackForbidden {}; + u->setAttackPolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *mpc = u->findMoveContainerOf(_p)) { + mpc->setMovePolicy(_p->movePolicy()); + _p->setMovePolicy(nullptr); + delete _p; + _p = nullptr; + } + + // our policy might’ve been wrapped into something + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("l_swamp"); + }; + + class Forest: public Landscape { + DefensePolicy *_prev; + MultiplierDefensePolicy *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _prev = u->defensePolicy(); + _cur = new MultiplierDefensePolicy {_prev, 2.0}; + u->setDefensePolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *dpc = u->findDefenseContainerOf(_cur)) { + dpc->setDefensePolicy(_prev); + _cur->setDefensePolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("l_forest"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/logging.hpp b/8303/Parfentev_Leonid/lab6/logging.hpp new file mode 100644 index 000000000..72b272969 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/logging.hpp @@ -0,0 +1,46 @@ +#ifndef _H_LOGGING_HPP +#define _H_LOGGING_HPP + +#include +#include + +#include "event.hpp" +#include "event_printer.hpp" + + +namespace events { + + class UserActionEvent: public Event { + Player *_p; + std::string _s; + + public: + UserActionEvent(Player *p, std::string s) + :_p{p}, _s{std::move(s)} {} + + const std::string & + message() const { return _s; } + + Player *player() const { return _p;} + }; + +} + +class LoggingEventPrinter: public EventPrinter { +public: + using EventPrinter::EventPrinter; + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + ostream() << ee->player()->name() + << ": " << ee->message() << "\n"; + return; + } + + EventPrinter::handle(e); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/main.cpp b/8303/Parfentev_Leonid/lab6/main.cpp new file mode 100644 index 000000000..25739d5ef --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/main.cpp @@ -0,0 +1,122 @@ +#include + +#include +#include + +#include "demo.hpp" + +#include "event_printer.hpp" +#include "game_driver.hpp" +#include "game_rules.hpp" + +void +run_demos(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); + + std::cout << "\nDemo 4\n"; + demo4(); + + std::cout << "\nDemo 5\n"; + demo5(); + + std::cout << "\nDemo 6\n"; + demo6(); + + std::cout << "\nDemo 7\n"; + demo7(); + + std::cout << "\nDemo 8\n"; + demo8(); + + std::cout << "\nDemo 9\n"; + demo9(); +} + +int +run_game(int argc, char **argv) +{ + std::vector loggers {}; + bool have_stdout = false; + + const char *load_fn = nullptr; + + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "-log")) { + char *fn = argv[++i]; + if (!strcmp(fn, "-")) { + loggers.push_back(new LoggingEventPrinter {std::cout}); + have_stdout = true; + } else { + auto *of = new std::ofstream {fn}; + if (!*of) { + std::cerr << "Failed to open file: " << fn << "\n"; + return 1; + } + loggers.push_back(new LoggingEventPrinter {of}); + } + } else if (!strcmp(argv[i], "-load")) { + load_fn = argv[++i]; + } else { + std::cerr << "Unknown option: " << argv[i] << "\n"; + return 1; + } + } + + GameDriver drv {}; + + for (auto *logger: loggers) { + drv.addLogger(logger); + } + + if (!have_stdout) { + drv.setPrinter(new EventPrinter {std::cout}); + } + + if (load_fn) { + std::ifstream f {load_fn}; + if (!f) { + std::cerr << "Failed to open save file: " + << load_fn << "\n"; + return 1; + } + auto *tab = RestorerTable::defaultTable(); + Storable *s = tab->restore(f); + delete tab; + + if (auto *lg = dynamic_cast(s)) { + + drv.resetFrom(lg); + + } else { + std::cerr << "Invalid save file contents\n"; + delete s; + return 1; + } + } else { + drv.reset(); + } + + drv.run(); + + return 0; +} + +int +main(int argc, char **argv) +{ + if (argc == 2 + && !strcmp(argv[1], "-demo")) { + run_demos(); + return 0; + } + + return run_game(argc, argv); +} diff --git a/8303/Parfentev_Leonid/lab6/map.cpp b/8303/Parfentev_Leonid/lab6/map.cpp new file mode 100644 index 000000000..66eddff6e --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/map.cpp @@ -0,0 +1,222 @@ +#include + +#include "point.hpp" +#include "unit.hpp" +#include "placeable.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "landscape.hpp" +#include "map.hpp" +#include "storable.hpp" +#include "common_storables.hpp" + + +Map::Map(int w, int h) + :_rm{w, h} +{ + for (auto rmiter = _rm.iterAt({0, 0}); + rmiter.y() < _rm.height(); + rmiter.advance(1)) { + rmiter.cell().setLandscape(new landscapes::Normal {}); + } +} + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.unit()) + return MapIter::makeNull(); + + cell.setUnit(u); + u->setPosition(pt); + + ++_units_count; + + cell.landscape()->onEnter(u); + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.unit()); + + if (u) { + --_units_count; + cell.landscape()->onLeave(u); + if (auto *n = dynamic_cast(cell.object())) { + n->onLeave(u); + } + + cell.setUnit(nullptr); + u->unsetPosition(); + } + + return u; +} + +MapIter +Map::addPlaceable(Placeable *p, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.object()) { + return MapIter::makeNull(); + } + + cell.setObject(p); + p->setPosition(pt); + + return MapIter{rmiter}; +} + +MapIter +Map::addBase(Base *b, Vec2 pt) +{ + return addPlaceable(b, pt); +} + +MapIter +Map::addNeutralObject(NeutralObject *n, Vec2 pt) +{ + return addPlaceable(n, pt); +} + +void +Map::setLandscape(Landscape *l, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + cell.setLandscape(l); +} + +void +Map::store(std::ostream &os) const +{ + os << "map " << width() << " " << height() << "\n"; + os << _units_max << "\n"; + + for (int y = 0; y < height(); ++y) { + for (int x = 0; x < width(); ++x) { + auto info = infoAt({x, y}); + + const auto *l = info.landscape(); + if (!dynamic_cast(l)) { + StorableWithCoords::storeWithCoords({x, y}, l, os); + } else if (const auto *n = info.neutralObject()) { + StorableWithCoords::storeWithCoords({x, y}, n, os); + } + + // bases and units are restored in Game::restore + } + } + + os << "end\n"; +} + +bool +Map::restore(std::istream &is, + RestorerTable *tab) +{ + is >> _units_max; + if (is.fail()) { + return false; + } + + for (;;) { + Storable *s = tab->restore(is); + + if (auto *sc = + dynamic_cast(s)) { + if (auto *l = + dynamic_cast(sc->child())) { + setLandscape(l, sc->coords()); + delete sc; + } else if (auto *n = + dynamic_cast(sc->child())) { + addNeutralObject(n, sc->coords()); + delete sc; + } else { + delete sc->child(); + delete sc; + return false; + } + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} + +MapInfo +Map::infoAt(Vec2 pt) const +{ + return MapInfo{&_rm.at(pt)}; +} + +Unit * +MapIter::unit() const +{ + return _it.cell().unit(); +} + +Base * +MapIter::base() const +{ + return dynamic_cast(_it.cell().object()); +} + +NeutralObject * +MapIter::neutralObject() const +{ + return dynamic_cast(_it.cell().object()); +} + +Landscape * +MapIter::landscape() const +{ + return _it.cell().landscape(); +} + +const Landscape * +MapInfo::landscape() const +{ + return _cell->landscape(); +} + +const Unit * +MapInfo::unit() const +{ + return _cell->unit(); +} + +const Base * +MapInfo::base() const +{ + return dynamic_cast(_cell->object()); +} + +const NeutralObject * +MapInfo::neutralObject() const +{ + return dynamic_cast(_cell->object()); +} diff --git a/8303/Parfentev_Leonid/lab6/map.hpp b/8303/Parfentev_Leonid/lab6/map.hpp new file mode 100644 index 000000000..d8831d7cf --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/map.hpp @@ -0,0 +1,126 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include + +#include "rectmap.hpp" +#include "storable.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; +class NeutralObject; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + Base *base() const; + NeutralObject *neutralObject() const; + Landscape *landscape() const; +}; + +class MapInfo { + const Cell *_cell; + +public: + MapInfo(const Cell *c) + :_cell{c} {} + + const Landscape *landscape() const; + const Unit *unit() const; + const Base *base() const; + const NeutralObject *neutralObject() const; +}; + +class Map: public Storable { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + + MapIter addPlaceable(Placeable *p, Vec2 pt); + +public: + Map(int w, int h); + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapInfo infoAt(Vec2 pt) const; + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + MapIter addBase(Base *b, Vec2 pt); + MapIter addNeutralObject(NeutralObject *n, Vec2 pt); + void setLandscape(Landscape *l, Vec2 pt); + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/mediator.cpp b/8303/Parfentev_Leonid/lab6/mediator.cpp new file mode 100644 index 000000000..d7e6e9ff1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/mediator.cpp @@ -0,0 +1,127 @@ +#include "point.hpp" +#include "map.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "neutral_object.hpp" +#include "base.hpp" +#include "mediator.hpp" + + +MapInfo +Mediator::infoAt(Vec2 pt) +{ + return _map->infoAt(pt); +} + +Vec2 +Mediator::mapSize() +{ + return {_map->width(), + _map->height()}; +} + +bool +Mediator::moveUnitTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit() + || !u->canMove(ito)) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitMoved {u, from}); + return true; +} + +bool +Mediator::attackTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (!ito.unit() + || !u->canAttackTo(ito)) { + return false; + } + + auto pos = u->actualPosition(ito); + Unit *t = pos.unit(); + + u->emit(new events::UnitAttacked {u, pos.point(), t}); + + if (t) { + t->emit(new events::UnitWasAttacked {u, t}); + + auto damage_pair = u->baseAttack(pos); + auto damage_spec = t->actualDamage(damage_pair.first, + damage_pair.second); + int dmg = damage_spec.evaluate(); + + t->takeDamage(dmg); + } + + return true; +} + +bool +Mediator::useObject(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + NeutralObject *n = iter.neutralObject(); + + if (!n + || !n->canUse(u)) { + return false; + } + + n->onUse(u, this); + + u->emit(new events::UnitUsedObject {u, n}); + + return true; +} + +bool +Mediator::destroyBase(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + Base *b = iter.base(); + + if (!b + || !b->becomeDestroyedBy(u)) { + return false; + } + + return true; +} + +bool +Mediator::spawnUnit(Unit *u, Vec2 at) +{ + return !_map->addUnit(u, at).null(); +} + +bool +Mediator::teleportUnit(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit()) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitTeleported {u, from}); + return true; +} diff --git a/8303/Parfentev_Leonid/lab6/mediator.hpp b/8303/Parfentev_Leonid/lab6/mediator.hpp new file mode 100644 index 000000000..31a0d545a --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/mediator.hpp @@ -0,0 +1,32 @@ +#ifndef _H_MEDIATOR_HPP +#define _H_MEDIATOR_HPP + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + + +class Mediator { + Map *_map; + +public: + Mediator(Map *map) + :_map{map} {} + + MapInfo infoAt(Vec2 pt); + + Vec2 mapSize(); + + bool moveUnitTo(Unit *u, Vec2 to); + + bool attackTo(Unit *u, Vec2 to); + + bool useObject(Unit *u); + bool destroyBase(Unit *u); + + bool spawnUnit(Unit *u, Vec2 at); + bool teleportUnit(Unit *u, Vec2 to); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab6/melee_units.hpp b/8303/Parfentev_Leonid/lab6/melee_units.hpp new file mode 100644 index 000000000..6409e96cb --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/melee_units.hpp @@ -0,0 +1,111 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + explicit MeleeAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_melee " << static_cast(_kind) + << " " << _base_damage << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + + UNIT_STORABLE_NAME("u_swordsman"); + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + + UNIT_STORABLE_NAME("u_spearsman"); + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + + UNIT_STORABLE_NAME("u_cavalry"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/neutral_object.hpp b/8303/Parfentev_Leonid/lab6/neutral_object.hpp new file mode 100644 index 000000000..95e11d9c7 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/neutral_object.hpp @@ -0,0 +1,24 @@ +#ifndef _H_NEUTRAL_OBJECT_HPP +#define _H_NEUTRAL_OBJECT_HPP + +#include "placeable.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "mediator.hpp" +#include "storable.hpp" + + +class NeutralObject: public Placeable, + public Storable { +public: + virtual bool canUse(const Unit *) { return true; } + virtual void onUse(Unit *u, Mediator *m) =0; + + // It’s the object’s job to determine whether it was used by the + // leaving unit. + virtual void onLeave(Unit *) {}; + + virtual ~NeutralObject() {}; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/neutral_object_types.hpp b/8303/Parfentev_Leonid/lab6/neutral_object_types.hpp new file mode 100644 index 000000000..e6fc373c1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/neutral_object_types.hpp @@ -0,0 +1,255 @@ +#ifndef _H_NEUTRAL_OBJECT_TYPES_HPP +#define _H_NEUTRAL_OBJECT_TYPES_HPP + +#include + +#include "neutral_object.hpp" +#include "mediator.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "ranged_units.hpp" +#include "common_policies.hpp" + +#include "storable.hpp" +#include "common_storables.hpp" + + +class ExtendedShootingRange: public NestedAttack { + double _delta; + +public: + explicit ExtendedShootingRange(AttackPolicy *p=nullptr, + double delta=0) + :NestedAttack{p}, _delta{delta} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = to.point().distance(u->position()); + auto *a = dynamic_cast(attackPolicy()); + return dist >= a->minRange() + && dist <= (a->maxRange() + _delta); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + return attackPolicy()->baseAttack(u, to); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_extended " << _delta << "\n"; + attackPolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _delta; + return !is.fail() && NestedAttack::restore(is, tab); + } +}; + + + +namespace objects { + + class HealingWell: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *) override + { + u->heal(25); + } + + TRIVIALLY_STORABLE("n_healingwell"); + }; + + class Tower: public NeutralObject { + AttackPolicy *_prev; + ExtendedShootingRange *_cur = nullptr; + + public: + virtual bool + canUse(const Unit *u) override + { + return dynamic_cast(u); + } + + virtual void + onUse(Unit *u, Mediator *) override + { + _prev = u->attackPolicy(); + _cur = new ExtendedShootingRange {_prev, 5}; + u->setAttackPolicy(_cur); + } + + virtual void + onLeave(Unit *u) override + { + if (_cur == nullptr) { + return; + } + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + _cur->setAttackPolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("n_tower"); + }; + + class TunnelsEntrance: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *m) override + { + static const int w = 5; + + Vec2 at = u->position(); + + int max_n = 0; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() == nullptr) { + ++max_n; + } + } + } + + std::uniform_int_distribution<> distr {0, max_n-1}; + int n = distr(global_random); + + Vec2 dest; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() != nullptr) { + continue; + } + if (!--n) { + dest = iter; + break; + } + } + } + + m->teleportUnit(u, dest); + } + + TRIVIALLY_STORABLE("n_tunnelentrance"); + }; + + class WeaponSmiths: public NeutralObject { + public: + class UnitFilter: public Storable { + public: + virtual bool + applicable(const Unit *u) =0; + }; + + template + class SimpleUnitFilter: public UnitFilter{ + public: + virtual bool + applicable(const Unit *u) override + { + return dynamic_cast(u); + } + }; + + class MeleeUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_melee"); + }; + + class RangedUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_ranged"); + }; + + class CatapultUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_catapult"); + }; + + private: + double _mul; + UnitFilter *_filter; + + public: + explicit WeaponSmiths(double mul=0, UnitFilter *filter=nullptr) + :_mul{mul}, _filter{filter} {} + + virtual bool + canUse(const Unit *u) override + { + if (_filter + && !_filter->applicable(u)) { + return false; + } + + for (const AttackPolicyContainer *apc = u; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (dynamic_cast(apc)) { + return false; + } + } + + return true; + } + + virtual void + onUse(Unit *u, Mediator *) override + { + auto *prev = u->attackPolicy(); + auto *new_p = new MultiplierAttackPolicy {prev, _mul}; + u->setAttackPolicy(new_p); + } + + virtual void + store(std::ostream &os) const override + { + os << "n_weaponsmiths " << _mul << "\n"; + if (_filter) { + _filter->store(os); + } else { + os << "end\n"; + } + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + + if (auto *uf = dynamic_cast(s)) { + _filter = uf; + } else if (dynamic_cast(s)) { + _filter = nullptr; + delete s; + } else { + delete s; + return false; + } + + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/object_print.cpp b/8303/Parfentev_Leonid/lab6/object_print.cpp new file mode 100644 index 000000000..da59e1054 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/object_print.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "object_print.hpp" + + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T) {std::type_index{typeid(landscapes::T)}, #T} +static const std::map landscape_names { + LANDSCAPE_ENTRY(Normal), + LANDSCAPE_ENTRY(Swamp), + LANDSCAPE_ENTRY(Forest), +}; +#undef LANDSCAPE_ENTRY + +#define N_OBJECT_ENTRY(T, n) {std::type_index{typeid(objects::T)}, n} +static const std::map objects_names { + N_OBJECT_ENTRY(HealingWell, "Healing Well"), + N_OBJECT_ENTRY(Tower, "Tower"), + N_OBJECT_ENTRY(TunnelsEntrance, "Tunnels Entrance"), + N_OBJECT_ENTRY(WeaponSmiths, "Weapon Smiths"), +}; +#undef N_OBJECT_ENTRY + + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, const Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP at " + << u->position(); +} + +std::ostream & +operator<<(std::ostream &os, const Landscape *l) +{ + return os << landscape_names.at(std::type_index{typeid(*l)}); +} + +std::ostream & +operator<<(std::ostream &os, const Base *b) +{ + os << "a Base with " << b->unitsCount(); + int m = b->maxUnitsCount(); + if (m >= 0) { + os << "/" << m; + } + + return os << " units at " << b->position(); +} + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n) +{ + return os << "a " + << objects_names.at(std::type_index{typeid(*n)}) + << " at " << n->position(); +} + + + +static const std::map class_chars { +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T, x) \ + {std::type_index{typeid(landscapes::T)}, x} + LANDSCAPE_ENTRY(Normal, '.'), + LANDSCAPE_ENTRY(Swamp, '='), + LANDSCAPE_ENTRY(Forest, '*'), +#undef LANDSCAPE_ENTRY +}; + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info) +{ + if (const Unit *u = info.unit()) { + os << class_chars.at(std::type_index{typeid(*u)}); + } else if (info.base()) { + os << "+"; + } else if (info.neutralObject()) { + os << '#'; + } else { + auto *l = info.landscape(); + os << class_chars.at(std::type_index{typeid(*l)}); + } + + return os; +} + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1) +{ + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + displayMapInfo(os, map->infoAt({x, y})); + } + os << "\n"; + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, const Map *map) +{ + return displayMap(os, map, 0, 0, map->width(), map->height()); +} diff --git a/8303/Parfentev_Leonid/lab6/object_print.hpp b/8303/Parfentev_Leonid/lab6/object_print.hpp new file mode 100644 index 000000000..8df10d966 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/object_print.hpp @@ -0,0 +1,40 @@ +#ifndef _H_OBJECT_PRINT_HPP +#define _H_OBJECT_PRINT_HPP + +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "neutral_object.hpp" + +std::ostream & +operator<<(std::ostream &os, Vec2 pt); + +std::ostream & +operator<<(std::ostream &os, const Unit *u); + +std::ostream & +operator<<(std::ostream &os, const Landscape *l); + +std::ostream & +operator<<(std::ostream &os, const Base *b); + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n); + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info); + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1); + +std::ostream & +operator<<(std::ostream &os, const Map *map); + +#endif diff --git a/8303/Parfentev_Leonid/lab6/object_w_health.hpp b/8303/Parfentev_Leonid/lab6/object_w_health.hpp new file mode 100644 index 000000000..e9c925221 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/object_w_health.hpp @@ -0,0 +1,43 @@ +#ifndef _H_OBJECT_W_HEALTH_HPP +#define _H_OBJECT_W_HEALTH_HPP + +#include "storable.hpp" + + +class ObjectWithHealth: public Storable { + int _health, _base_health; + +public: + ObjectWithHealth(int base_health) + :_health{base_health}, + _base_health{base_health} {} + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + virtual void + heal(int hp) { _health += hp; } + + virtual void + takeDamage(int dmg) { _health -= dmg; } + + virtual void store(std::ostream &os) const override + { + os << health(); + } + + virtual bool restore(std::istream &is, RestorerTable *) override + { + is >> _health; + return !is.fail(); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab6/pathfinder.cpp b/8303/Parfentev_Leonid/lab6/pathfinder.cpp new file mode 100644 index 000000000..865f34de2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.unit()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab6/pathfinder.hpp b/8303/Parfentev_Leonid/lab6/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/placeable.hpp b/8303/Parfentev_Leonid/lab6/placeable.hpp new file mode 100644 index 000000000..bd7f0ef74 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/placeable.hpp @@ -0,0 +1,36 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +#include "point.hpp" + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/player.hpp b/8303/Parfentev_Leonid/lab6/player.hpp new file mode 100644 index 000000000..58c15829d --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/player.hpp @@ -0,0 +1,44 @@ +#ifndef _H_PLAYER_HPP +#define _H_PLAYER_HPP + +#include +#include + +#include "mediator.hpp" +#include "base.hpp" +#include "event.hpp" +#include "storable.hpp" + + +class Game; + +class Player: public EventEmitter, + public Storable { + std::string _name; + +public: + explicit Player(std::string name="") + :_name{std::move(name)} {} + + const std::string &name() const { return _name; } + + virtual bool takeTurn(const Game *g, Mediator *m, Base *b) =0; + + virtual void + store(std::ostream &os) const + { + os << name() << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + std::ws(is); + std::getline(is, _name); + return !is.fail(); + } + + virtual ~Player() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/point.cpp b/8303/Parfentev_Leonid/lab6/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab6/point.hpp b/8303/Parfentev_Leonid/lab6/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/ranged_units.hpp b/8303/Parfentev_Leonid/lab6/ranged_units.hpp new file mode 100644 index 000000000..2a41e3ecb --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/ranged_units.hpp @@ -0,0 +1,118 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { +protected: + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + double minRange() const { return _min_distance; } + double maxRange() const { return _max_distance; } + + explicit RangedAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0, + double min_dist=0, + double max_dist=0, + double dist_pow=0) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_ranged " << static_cast(_kind) + << " " << _base_damage << " " << _min_distance + << " " << _max_distance << " " + << _dist_pow << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage >> _min_distance + >> _max_distance >> _dist_pow; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + + UNIT_STORABLE_NAME("u_archer"); + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + + UNIT_STORABLE_NAME("u_slinger"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/rectmap.cpp b/8303/Parfentev_Leonid/lab6/rectmap.cpp new file mode 100644 index 000000000..b82667c40 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/rectmap.cpp @@ -0,0 +1,55 @@ +#include "rectmap.hpp" + +#include "unit.hpp" + +Cell::~Cell() +{ + delete _u; + delete _obj; + delete _l; +} + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab6/rectmap.hpp b/8303/Parfentev_Leonid/lab6/rectmap.hpp new file mode 100644 index 000000000..a3119ffb1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/rectmap.hpp @@ -0,0 +1,99 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" +#include "landscape.hpp" + + +class Unit; + +class Cell { + Landscape *_l = nullptr; + Unit *_u = nullptr; + Placeable *_obj = nullptr; + +public: + Cell() {} + + ~Cell(); + + Landscape *landscape() const { return _l; } + void setLandscape(Landscape *l) + { + delete _l; + _l = l; + } + + Unit *unit() const { return _u; } + void setUnit(Unit *u) { _u = u; } + + Placeable *object() const { return _obj; } + void setObject(Placeable *p) { _obj = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + const Cell &at(Vec2 pt) const + { + return _storage[pt.x() + pt.y()*_w]; + } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/report.pdf b/8303/Parfentev_Leonid/lab6/report.pdf new file mode 100644 index 000000000..05a62b6c9 Binary files /dev/null and b/8303/Parfentev_Leonid/lab6/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab6/restorers.hpp b/8303/Parfentev_Leonid/lab6/restorers.hpp new file mode 100644 index 000000000..6fe67c4e2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/restorers.hpp @@ -0,0 +1,61 @@ +#ifndef _H_RESTORERS_HPP +#define _H_RESTORERS_HPP + +#include "storable.hpp" + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "base.hpp" +#include "game.hpp" +#include "player.hpp" + + +template +class SimpleRestorer: public Restorer { +public: + virtual Storable * + restore(std::istream &, + RestorerTable *) const override + { + return new T {}; + } +}; + + +namespace restorers { + + class GameRestorer: public Restorer { + public: + virtual Storable * + restore(std::istream &is, + RestorerTable *tab) const override + { + Storable *s = tab->restore(is); + Map *map = dynamic_cast(s); + if (!map) { + delete s; + return nullptr; + } + + return new Game {map}; + } + }; + + class MapRestorer: public Restorer { + public: + virtual Storable * + restore(std::istream &is, + RestorerTable *) const override + { + int w, h; + is >> w >> h; + + return new Map {w, h}; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab6/storable.cpp b/8303/Parfentev_Leonid/lab6/storable.cpp new file mode 100644 index 000000000..2bd50e407 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/storable.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +#include "storable.hpp" +#include "restorers.hpp" +#include "common_storables.hpp" + +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "landscape_types.hpp" +#include "neutral_object_types.hpp" + +#include "factory_table.hpp" +#include "iostream_player.hpp" + +#include "game_rules.hpp" + + +RestorerTable * +RestorerTable::defaultTable() +{ + return new RestorerTable {{ + +{"end", new SimpleRestorer {}}, +{"coords", new SimpleRestorer {}}, +{"index", new SimpleRestorer {}}, +{"at", new SimpleRestorer {}}, + +{"game", new restorers::GameRestorer {}}, +{"map", new restorers::MapRestorer {}}, + +{"base", new SimpleRestorer {}}, +{"base_w_countdown", new SimpleRestorer {}}, + +{"iostream_player", new SimpleRestorer {}}, + +{"l_normal", new SimpleRestorer {}}, +{"l_swamp", new SimpleRestorer {}}, +{"l_forest", new SimpleRestorer {}}, + +{"u_swordsman", new SimpleRestorer {}}, +{"u_spearsman", new SimpleRestorer {}}, +{"u_cavalry", new SimpleRestorer {}}, + +{"u_archer", new SimpleRestorer {}}, +{"u_slinger", new SimpleRestorer {}}, + +{"u_onager", new SimpleRestorer {}}, +{"u_boltthrower", new SimpleRestorer {}}, + +{"n_healingwell", new SimpleRestorer {}}, +{"n_tower", new SimpleRestorer {}}, +{"n_tunnelentrance", new SimpleRestorer {}}, +{"n_weaponsmiths", new SimpleRestorer {}}, + +{"uf_melee", + new SimpleRestorer {}}, +{"uf_ranged", + new SimpleRestorer {}}, +{"uf_catapult", + new SimpleRestorer {}}, + +{"mp_basic", new SimpleRestorer {}}, +{"mp_modifyiing", new SimpleRestorer {}}, + +{"dp_basic", new SimpleRestorer {}}, +{"dp_level_deco", new SimpleRestorer {}}, +{"dp_multiplier", new SimpleRestorer {}}, + +{"ap_forbidden", new SimpleRestorer {}}, +{"ap_multiplier", new SimpleRestorer {}}, +{"ap_extended", new SimpleRestorer {}}, + +{"ap_melee", new SimpleRestorer {}}, +{"ap_ranged", new SimpleRestorer {}}, +{"ap_catapult", new SimpleRestorer {}}, + + }}; +} diff --git a/8303/Parfentev_Leonid/lab6/storable.hpp b/8303/Parfentev_Leonid/lab6/storable.hpp new file mode 100644 index 000000000..d152b700c --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/storable.hpp @@ -0,0 +1,70 @@ +#ifndef _STORABLE_HPP +#define _STORABLE_HPP + +#include +#include +#include +#include + + +class RestorerTable; + +class Storable { +public: + virtual void store(std::ostream &os) const =0; + virtual bool restore(std::istream &, + RestorerTable *) { return true; }; + + virtual ~Storable() {} +}; + +#define TRIVIALLY_STORABLE(keyword) \ + public: \ + virtual void \ + store(std::ostream &os) const override \ + { \ + os << keyword "\n"; \ + } + + + +class Restorer { +public: + virtual Storable *restore(std::istream &is, + RestorerTable *tab) const =0; +}; + +class RestorerTable { + std::map _tab; + +public: + RestorerTable(std::map m) + :_tab{std::move(m)} {} + + Storable * + restore(std::istream &is) + { + std::string n; + is >> n; + + auto iter = _tab.find(n); + if (iter == _tab.end()) { + return nullptr; + } + + Storable *s = iter->second->restore(is, this); + if (!s) { + return nullptr; + } + + if (!s->restore(is, this)) { + delete s; + return nullptr; + } + return s; + } + + static RestorerTable *defaultTable(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/unit.cpp b/8303/Parfentev_Leonid/lab6/unit.cpp new file mode 100644 index 000000000..39098695f --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/unit.cpp @@ -0,0 +1,48 @@ +#include + +#include "storable.hpp" +#include "common_storables.hpp" +#include "unit.hpp" + + +std::default_random_engine global_random {}; + +void +Unit::store(std::ostream &os) const +{ + os << health() << "\n"; + + movePolicy()->store(os); + defensePolicy()->store(os); + attackPolicy()->store(os); + + os << "end\n"; +} + +bool +Unit::restore(std::istream &is, RestorerTable *tab) +{ + if (!ObjectWithHealth::restore(is, tab)) { + return false; + } + + for (;;) { + Storable *s = tab->restore(is); + + if (auto *mp = dynamic_cast(s)) { + setMovePolicy(mp); + } else if (auto *dp = dynamic_cast(s)) { + setDefensePolicy(dp); + } else if (auto *ap = dynamic_cast(s)) { + setAttackPolicy(ap); + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} diff --git a/8303/Parfentev_Leonid/lab6/unit.hpp b/8303/Parfentev_Leonid/lab6/unit.hpp new file mode 100644 index 000000000..93f745061 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/unit.hpp @@ -0,0 +1,271 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "event.hpp" +#include "event_types.hpp" +#include "object_w_health.hpp" +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy: public Storable { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + invalid, sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy: public Storable { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + void + scale(double k) + { + base_damage *= k; + damage_spread *= k; + } + + DamageSpec + scaled(double k) const + { + auto ds = *this; + ds.scale(k); + return ds; + } + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy: public Storable { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class MovePolicyContainer { + MovePolicy *_mp; + +public: + MovePolicyContainer(MovePolicy *mp) :_mp{mp} {} + + MovePolicy *movePolicy() const { return _mp; } + void setMovePolicy(MovePolicy *mp) + { + _mp = mp; + } + virtual ~MovePolicyContainer() { delete _mp; } + + MovePolicyContainer * + findMoveContainerOf(const MovePolicy *mp) + { + for (MovePolicyContainer *mpc = this; mpc; + mpc = dynamic_cast( + mpc->movePolicy())) { + if (mpc->movePolicy() == mp) { + return mpc; + } + } + return nullptr; + } +}; + +class DefensePolicyContainer { + DefensePolicy *_dp; + +public: + DefensePolicyContainer(DefensePolicy *dp) :_dp{dp} {} + + DefensePolicy *defensePolicy() const { return _dp; } + void setDefensePolicy(DefensePolicy *dp) + { + _dp = dp; + } + virtual ~DefensePolicyContainer() { delete _dp; } + + DefensePolicyContainer * + findDefenseContainerOf(const DefensePolicy *dp) + { + for (DefensePolicyContainer *dpc = this; dpc; + dpc = dynamic_cast( + dpc->defensePolicy())) { + if (dpc->defensePolicy() == dp) { + return dpc; + } + } + return nullptr; + } +}; + +class AttackPolicyContainer { + AttackPolicy *_ap; + +public: + AttackPolicyContainer(AttackPolicy *ap) :_ap{ap} {} + + AttackPolicy *attackPolicy() const { return _ap; } + void setAttackPolicy(AttackPolicy *ap) + { + _ap = ap; + } + virtual ~AttackPolicyContainer() { delete _ap; } + + AttackPolicyContainer * + findAttackContainerOf(const AttackPolicy *ap) + { + for (AttackPolicyContainer *apc = this; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (apc->attackPolicy() == ap) { + return apc; + } + } + return nullptr; + } +}; + +class Unit: public Placeable, + public ObjectWithHealth, + public EventEmitter, + public MovePolicyContainer, + public DefensePolicyContainer, + public AttackPolicyContainer { +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :ObjectWithHealth{base_health}, + MovePolicyContainer{move}, + DefensePolicyContainer{defense}, + AttackPolicyContainer{attack} {} + + virtual void + heal(int hp) + { + emit(new events::UnitGetsHealed {this, hp}); + ObjectWithHealth::heal(hp); + } + + virtual void + takeDamage(int dmg) + { + if (!alive()) { + return; + } + + emit(new events::UnitTakesDamage {this, dmg}); + + ObjectWithHealth::takeDamage(dmg); + + if (!alive()) { + emit(new events::UnitDeath {this}); + } + } + + bool + canMove(MapIter to) const + { + return movePolicy()->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return attackPolicy()->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return attackPolicy()->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return attackPolicy()->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return defensePolicy()->actualDamage(this, kind, base); + } + + // override to add type symbol! + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; + + virtual ~Unit() override + { + if (alive()) { + emit(new events::UnitLiveDeleted {this}); + } + } +}; + +#define UNIT_STORABLE_NAME(n) \ + virtual void \ + store(std::ostream &os) const override \ + { \ + os << n "\n"; \ + Unit::store(os); \ + } + +#endif diff --git a/8303/Parfentev_Leonid/lab6/unit_factory.hpp b/8303/Parfentev_Leonid/lab6/unit_factory.hpp new file mode 100644 index 000000000..5f1f475bd --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/unit_factory.hpp @@ -0,0 +1,24 @@ +#ifndef _H_UNIT_FACTORY_HPP +#define _H_UNIT_FACTORY_HPP + +#include "unit.hpp" + + +class UnitFactory { +public: + virtual Unit *create() const =0; + + virtual ~UnitFactory() {} +}; + +template +class SimpleUnitFactory: public UnitFactory { +public: + virtual Unit * + create() const override + { + return new U {}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab6/zombie_collector.hpp b/8303/Parfentev_Leonid/lab6/zombie_collector.hpp new file mode 100644 index 000000000..9de5f6981 --- /dev/null +++ b/8303/Parfentev_Leonid/lab6/zombie_collector.hpp @@ -0,0 +1,36 @@ +#ifndef _H_ZOMBIE_COLLECTOR_HPP +#define _H_ZOMBIE_COLLECTOR_HPP + +#include + +#include "unit.hpp" +#include "event.hpp" +#include "map.hpp" + + +class ZombieCollector: public EventListener { + std::vector _units {}; + +public: + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + return handle(ee->event()); + } + if (auto *ee = dynamic_cast(e)) { + _units.push_back(ee->unit()); + } + } + + void + collect(Map *m) + { + for (auto *unit: _units) { + delete m->removeUnitAt(unit->position()); + } + _units.clear(); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/Makefile b/8303/Parfentev_Leonid/lab7/Makefile new file mode 100644 index 000000000..49be692ea --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/Makefile @@ -0,0 +1,24 @@ +EXENAME = main +HEADERS = point.hpp placeable.hpp rectmap.hpp map.hpp pathfinder.hpp \ + unit.hpp common_policies.hpp melee_units.hpp ranged_units.hpp \ + catapult_units.hpp demo.hpp event.hpp base.hpp object_w_health.hpp \ + landscape.hpp landscape_types.hpp neutral_object.hpp \ + neutral_object_types.hpp unit_factory.hpp game.hpp \ + object_print.hpp event_printer.hpp iostream_player.hpp \ + mediator.hpp logging.hpp factory_table.hpp storable.hpp \ + common_storables.hpp restorers.hpp game_driver.hpp game_rules.hpp \ + exceptions.hpp +OBJFILES = point.o rectmap.o map.o pathfinder.o unit.o demo.o main.o \ + event.o base.o game.o object_print.o iostream_player.o mediator.o \ + storable.o +LDLIBS = -lm + +all: $(EXENAME) + +$(OBJFILES): $(HEADERS) + +$(EXENAME): $(OBJFILES) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) $(EXENAME) $(OBJFILES) diff --git a/8303/Parfentev_Leonid/lab7/base.cpp b/8303/Parfentev_Leonid/lab7/base.cpp new file mode 100644 index 000000000..78079331b --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/base.cpp @@ -0,0 +1,162 @@ +#include +#include +#include + +#include "unit.hpp" +#include "unit_factory.hpp" +#include "event.hpp" +#include "base.hpp" +#include "mediator.hpp" +#include "factory_table.hpp" + +#include "storable.hpp" +#include "common_storables.hpp" + + +bool +Base::canCreateUnit(const std::string &key) const +{ + if (unitsCount() == maxUnitsCount()) { + return false; + } + + if (!FactoryTable::instance()->canCreate(key)) { + return false; + } + + return true; +} + +int +Base::createUnit(const std::string &key, Mediator *m) +{ + if (!canCreateUnit(key)) { + return -1; + } + + if (m->infoAt(position()).unit()) { + return false; + } + + Unit *u = FactoryTable::instance()->create(key); + int id = addUnit(u); + + if (id < 0) { + delete u; + return -1; + } + + if (!m->spawnUnit(u, position())) { + removeUnit(u); + delete u; + return -1; + } + + return id; +} + +bool +Base::setMaxUnitsCount(int m) +{ + if (m < unitsCount()) { + return false; + } + _max_count = m; + return true; +} + +int +Base::addUnit(Unit *u) +{ + if (maxUnitsCount() >= 0 + && unitsCount() == maxUnitsCount()) { + return -1; + } + + _units[_next_idx] = u; + u->subscribe(this); + u->emit(new events::UnitAdded {u}); + + return _next_idx++; +} + +void +Base::removeUnit(Unit *u) +{ + u->unsubscribe(this); + + for (auto iter = _units.begin(); + iter != _units.end(); + ++iter) { + if (iter->second == u) { + _units.erase(iter); + break; + } + } +} + +Unit * +Base::getUnitById(int id) const +{ + auto iter = _units.find(id); + return (iter != _units.end()) + ? iter->second + : nullptr; +} + +bool +Base::becomeDestroyedBy(Unit *u) +{ + for (auto iter = unitsBegin(); + iter != unitsEnd(); + ++iter) { + if (iter.unit() == u) { + return false; + } + } + + _destroyed = true; + + for (auto iter = unitsBegin(); + iter != unitsEnd(); + ++iter) { + Unit *v = iter.unit(); + v->emit(new events::UnitDeath {v}); + } + + emit(new events::BaseDestroyed {this}); + return true; +} + +void +Base::store(std::ostream &os) const +{ + os << "base " << _max_count << " " << _destroyed << "\n"; +} + +bool +Base::restore(std::istream &is, + RestorerTable *) +{ + is >> _max_count >> _destroyed; + return !is.fail(); +} + +void +Base::handle(Event *e) +{ + EventForwarder::handle(e); + + if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } else if (auto *ee = dynamic_cast(e)) { + removeUnit(ee->unit()); + } +} + +Base::~Base() +{ + for (auto p: _units) { + p.second->unsubscribe(this); + } +} diff --git a/8303/Parfentev_Leonid/lab7/base.hpp b/8303/Parfentev_Leonid/lab7/base.hpp new file mode 100644 index 000000000..deadd93cc --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/base.hpp @@ -0,0 +1,98 @@ +#ifndef _H_BASE_HPP +#define _H_BASE_HPP + +#include +#include +#include + +#include "storable.hpp" +#include "placeable.hpp" +#include "event.hpp" +#include "unit.hpp" +#include "mediator.hpp" + + +class Base: public Placeable, + public EventForwarder, + public Storable { + + std::map _units {}; + int _next_idx = 0; + int _max_count = -1; + bool _destroyed = false; + +public: + Base() {} + + virtual bool + canCreateUnit(const std::string &key) const; + virtual int + createUnit(const std::string &key, Mediator *m); + + int + unitsCount() const { return (int)_units.size(); } + bool + setMaxUnitsCount(int m); + int + maxUnitsCount() const { return _max_count; } + + int + addUnit(Unit *u); + void + removeUnit(Unit *u); + Unit * + getUnitById(int id) const; + + virtual void spin() {} + + bool + becomeDestroyedBy(Unit *u); + bool + destroyed() const { return _destroyed; } + + class unitsIter { + using real_iter_t = std::map::const_iterator; + real_iter_t _iter; + + public: + unitsIter(real_iter_t it) + :_iter{it} {} + + int id() const { return _iter->first; } + Unit *unit() const { return _iter->second; } + unitsIter &operator++() { ++_iter; return *this; } + unitsIter + operator++(int) + { + unitsIter x{_iter}; + ++*this; + return x; + } + bool + operator==(const unitsIter &o) const + { + return _iter == o._iter; + } + bool + operator!=(const unitsIter &o) const + { + return !(*this == o); + } + }; + + unitsIter + unitsBegin() const { return unitsIter{_units.begin()}; } + unitsIter + unitsEnd() const { return unitsIter{_units.end()}; } + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &, + RestorerTable *) override; + + virtual void + handle(Event *e) override; + + virtual ~Base() override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/catapult_units.hpp b/8303/Parfentev_Leonid/lab7/catapult_units.hpp new file mode 100644 index 000000000..914c28041 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/catapult_units.hpp @@ -0,0 +1,170 @@ +#ifndef _H_CATAPULT_UNITS_HPP +#define _H_CATAPULT_UNITS_HPP + +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" +#include "ranged_units.hpp" + + +class CatapultAttack: public RangedAttack { + double _spread_tang, _spread_normal; + + struct FVec2 { + double x, y; + + explicit FVec2(const Vec2 &v) + :x{(double)v.x()}, y{(double)v.y()} {} + + FVec2(double x, double y) + :x{x}, y{y} {} + + operator Vec2() const + { + return Vec2{int(round(x)), int(round(y))}; + } + + FVec2 + orthogonal() const { return {y, -x}; } + + FVec2 & + operator*=(double a) + { + x *= a; + y *= a; + return *this; + } + FVec2 + operator*(double a) const + { + FVec2 tmp {*this}; + return tmp *= a; + } + + FVec2 + normalized() const { return (*this) * (1/sqrt(x*x + y*y)); } + + FVec2 & + operator+=(const FVec2 &dxy) + { + x += dxy.x; + y += dxy.y; + return *this; + } + FVec2 + operator+(const FVec2 &dxy) const + { + FVec2 tmp{*this}; + return tmp += dxy; + } + + FVec2 + apply(double t, double n) const + { + return normalized() * t + + orthogonal().normalized() * n; + } + }; + +public: + explicit CatapultAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0, + double min_dist=0, + double max_dist=0, + double dist_pow=0, + double spread_t=0, + double spread_n=0) + :RangedAttack{kind, base_dmg, min_dist, max_dist, dist_pow}, + _spread_tang{spread_t}, + _spread_normal{spread_n} {} + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + Vec2 dest = to.point(); + Vec2 delta = dest.delta(u->position()); + FVec2 fdelta {delta}; + + std::uniform_real_distribution<> + t_dist {-_spread_tang, _spread_tang}, + n_dist {-_spread_normal, _spread_normal}; + + double + t = t_dist(global_random), + n = n_dist(global_random); + + FVec2 result = fdelta.apply(t, n); + return to.shifted(Vec2{result}); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_catapult " << static_cast(_kind) + << " " << _base_damage << " " << _min_distance + << " " << _max_distance << " " + << _dist_pow << " " << _spread_tang << " " + << _spread_normal << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage >> _min_distance + >> _max_distance >> _dist_pow >> _spread_tang + >> _spread_normal; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicCatapultUnit: public Unit { +public: + BasicCatapultUnit(AttackKind attack_kind, + int base_dmg, + double min_dist, + double max_dist, + double dist_pow, + double spread_t, + double spread_n, + int base_health) + :Unit{new BasicMovement {1}, + new CatapultAttack {attack_kind, + base_dmg, min_dist, max_dist, + dist_pow, spread_t, spread_n}, + DefenseLevelDeco::good_defense_deco( + AttackKind::arrow, + new BasicDefense {0.75}), + base_health} {} +}; + +namespace units { + class Onager: public BasicCatapultUnit { + public: + Onager() :BasicCatapultUnit{ + AttackKind::rock, 90, + 3, 10, 0.05, + 0.2, 0.1, + 30} {} + + UNIT_STORABLE_NAME("u_onager"); + }; + + class BoltThrower: public BasicCatapultUnit { + public: + BoltThrower() :BasicCatapultUnit{ + AttackKind::bolt, 110, + 2, 6, 0.15, + 0.05, 0.05, + 20} {} + + UNIT_STORABLE_NAME("u_boltthrower"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/common_policies.hpp b/8303/Parfentev_Leonid/lab7/common_policies.hpp new file mode 100644 index 000000000..99ad30554 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/common_policies.hpp @@ -0,0 +1,308 @@ +#ifndef _H_COMMON_POLICIES_HPP +#define _H_COMMON_POLICIES_HPP + +#include + +#include "unit.hpp" +#include "map.hpp" +#include "pathfinder.hpp" + + +class BasicMovement: public MovePolicy { + int _steps_per_turn; + +public: + explicit BasicMovement(int n=0) + :_steps_per_turn{n} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _steps_per_turn}; + return pf.run(); + } + + virtual void + store(std::ostream &os) const override + { + os << "mp_basic " << _steps_per_turn << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + is >> _steps_per_turn; + return !is.fail(); + } +}; + +class NestedMovement: public MovePolicy, + public MovePolicyContainer { +public: + using MovePolicyContainer::MovePolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setMovePolicy(p); + return true; + } + delete s; + return false; + } +}; + +class ModifyingMovePolicy: public NestedMovement { + int _max; + +public: + explicit ModifyingMovePolicy(MovePolicy *p=nullptr, int max_dist=0) + :NestedMovement{p}, _max{max_dist} {} + + virtual bool + canMove(const Unit *u, MapIter to) override + { + MapIter from = to.otherAt(u->position()); + PathFinder pf {from, to, _max}; + if (!pf.run()) + return false; + + return movePolicy()->canMove(u, to); + } + + virtual void + store(std::ostream &os) const override + { + os << "mp_modifyiing " << _max << "\n"; + movePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _max; + return !is.fail() && NestedMovement::restore(is, tab); + } +}; + + + +class BasicDefense: public DefensePolicy { + double _lvl; + +public: + explicit BasicDefense(double level=1.0) + :_lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *, AttackKind, int base) override + { + return normal_defense(base); + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_basic " << _lvl << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + is >> _lvl; + return !is.fail(); + } +}; + +class NestedDefense: public DefensePolicy, + public DefensePolicyContainer { +public: + using DefensePolicyContainer::DefensePolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setDefensePolicy(p); + return true; + } + delete s; + return false; + } +}; + +class DefenseLevelDeco: public NestedDefense { + // Controls nested policy lifetime + AttackKind _kind; + double _lvl; + +public: + explicit DefenseLevelDeco(DefensePolicy *p=0, + AttackKind kind=AttackKind::invalid, + double level=0) + :NestedDefense{p}, _kind{kind}, _lvl{level} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + if (kind == _kind) + return defense_level(_lvl, base); + return defensePolicy()->actualDamage(u, kind, base); + } + + static DefenseLevelDeco * + defense_level_deco(AttackKind kind, double lvl, DefensePolicy *p) + { + return new DefenseLevelDeco {p, kind, lvl}; + } + + static DefenseLevelDeco * + good_defense_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 2.0, p); + } + + static DefenseLevelDeco * + vulnerability_deco(AttackKind kind, DefensePolicy *p) + { + return defense_level_deco(kind, 0.5, p); + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_level_deco " << static_cast(_kind) + << " " << _lvl << "\n"; + defensePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + int x; + is >> x; + _kind = static_cast(x); + is >> _lvl; + + return !is.fail() && NestedDefense::restore(is, tab); + } +}; + +class MultiplierDefensePolicy: public NestedDefense { + double _mul; + +public: + explicit MultiplierDefensePolicy(DefensePolicy *p=nullptr, + double mul=0) + :NestedDefense{p}, _mul{mul} {} + + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) override + { + DamageSpec ds = defensePolicy()->actualDamage(u, kind, base); + ds.scale(1/_mul); + return ds; + } + + virtual void + store(std::ostream &os) const override + { + os << "dp_multiplier " << _mul << "\n"; + defensePolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _mul; + return !is.fail() && NestedDefense::restore(is, tab); + } +}; + + + +class AttackForbidden: public AttackPolicy { +public: + using AttackPolicy::AttackPolicy; + + virtual bool + canAttackTo(const Unit *, MapIter) override + { + return false; + } + + virtual std::pair + baseAttack(const Unit *, MapIter) override + { + return std::make_pair(AttackKind::invalid, 0); + } + + TRIVIALLY_STORABLE("ap_forbidden"); +}; + +class NestedAttack: public AttackPolicy, + public AttackPolicyContainer { +public: + using AttackPolicyContainer::AttackPolicyContainer; + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + if (auto *p = dynamic_cast(s)) { + setAttackPolicy(p); + return true; + } + delete s; + return false; + } +}; + +class MultiplierAttackPolicy: public NestedAttack { + double _mul; + +public: + explicit MultiplierAttackPolicy(AttackPolicy *p=nullptr, + double mul=0) + :NestedAttack{p}, _mul{mul} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return attackPolicy()->canAttackTo(u, to); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + auto ba = attackPolicy()->baseAttack(u, to); + return std::make_pair(ba.first, (int)(ba.second * _mul)); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_multiplier " << _mul << "\n"; + attackPolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _mul; + return !is.fail() && NestedAttack::restore(is, tab); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab7/common_storables.hpp b/8303/Parfentev_Leonid/lab7/common_storables.hpp new file mode 100644 index 000000000..27cfd5a0a --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/common_storables.hpp @@ -0,0 +1,117 @@ +#ifndef _H_COMMON_STORABLES_HPP +#define _H_COMMON_STORABLES_HPP + +#include "storable.hpp" +#include "object_print.hpp" + + +class StorableEnd: public Storable { + TRIVIALLY_STORABLE("end"); +}; + +class StorableCoordinates: public Storable { + Vec2 _c; + +public: + StorableCoordinates() {} + + StorableCoordinates(Vec2 c) :_c{c} {} + + virtual void + store(std::ostream &os) const override + { + os << "coords " << _c.x() << " " << _c.y() << "\n"; + } + + virtual bool + restore(std::istream &is, + RestorerTable *) override + { + int x, y; + is >> x >> y; + _c = Vec2{x, y}; + return !is.fail(); + } + + Vec2 coords() const { return _c; } +}; + +class StorableWithIndex: public Storable { + int _i; + Storable *_s; + +public: + StorableWithIndex() {} + + StorableWithIndex(int idx, Storable *s) + :_i{idx}, _s{s} {} + + virtual void + store(std::ostream &os) const override + { + os << "index " << _i << " "; + _s->store(os); + } + + virtual bool + restore(std::istream &is, + RestorerTable *tab) override + { + is >> _i; + _s = tab->restore(is); + return !is.fail() && _s; + } + + int index() const { return _i; } + Storable *child() const { return _s; } + + static void + storeWithIndex(int idx, const Storable *s, + std::ostream &os) + { + os << "index " << idx << " "; + s->store(os); + } +}; + +class StorableWithCoords: public Storable { + Vec2 _c; + Storable *_s; + +public: + StorableWithCoords() {} + + StorableWithCoords(Vec2 c, Storable *s) + :_c{c}, _s{s} {} + + virtual void + store(std::ostream &os) const override + { + os << "at " << _c.x() << " " << _c.y() << " "; + _s->store(os); + } + + virtual bool + restore(std::istream &is, + RestorerTable *tab) override + { + int x, y; + is >> x >> y; + _c = Vec2{x, y}; + _s = tab->restore(is); + return !is.fail() && _s; + } + + Vec2 coords() const { return _c; } + Storable *child() const { return _s; } + + static void + storeWithCoords(Vec2 pt, const Storable *s, + std::ostream &os) + { + os << "at " << pt.x() << " " << pt.y() << " "; + s->store(os); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/demo.cpp b/8303/Parfentev_Leonid/lab7/demo.cpp new file mode 100644 index 000000000..2c438ae19 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/demo.cpp @@ -0,0 +1,520 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" + +#include "event.hpp" +#include "event_types.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "unit_factory.hpp" + +#include "game.hpp" +#include "event_printer.hpp" +#include "iostream_player.hpp" + +#include "factory_table.hpp" + + +void +demo1() +{ + // create a map + auto *map = new Map {10, 10}; + + // create a few units + auto sw1_iter = map->addUnit(new units::Swordsman {}, {3, 3}); + map->addUnit(new units::Swordsman {}, {4, 4}); + + // write the map + std::cout << map << "\n"; + + // test the pathfinder + static const std::vector pts { + {3, 4}, + {3, 3}, + {5, 5}, + {5, 4}, + {5, 3}, + }; + for (auto pt: pts) { + std::cout << sw1_iter.unit() << " " + << ((sw1_iter.unit()->canMove(map->iterAt(pt))) + ? "can" : "can't") + << " move to " << pt << "\n"; + } + + // clean up + delete map; +} + +void +demo2() +{ + auto *map = new Map {10, 10}; + + auto c_iter = map->addUnit(new units::Cavalry {}, {3, 3}); + auto *u = c_iter.unit(); + + std::cout << map << "\n"; + + static const std::vector path { + {4, 5}, {6, 5}, {9, 5}, {8, 7}, {7, 9}, + }; + for (auto pt: path) { + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + std::cout << u << " can't move to " << pt << "!\n"; + break; + } + + std::cout << "moving " << u + << " to " << pt << "...\n"; + + c_iter = map->addUnit(map->removeUnitAt(c_iter), pt); + if (c_iter.null()) { + std::cout << "failed!\n"; + } + } + + std::cout << "\n" << map; + + delete map; +} + +void +demo3() +{ + auto *map = new Map {10, 10}; + map->setMaxUnitsCount(2); + + Unit *sw = new units::Swordsman {}; + Unit *ar = new units::Archer {}; + + map->addUnit(sw, {5, 0}); + map->addUnit(ar, {5, 9}); + + Unit *x = new units::Swordsman {}; + if (!map->addUnit(x, {1, 1}).null()) { + std::cout << "Added one more unit!\n"; + delete map->removeUnitAt({1, 1}); + } else { + std::cout << "Max units: " << map->maxUnitsCount() + << ", current units: " << map->unitsCount() + << "\n"; + delete x; + } + + std::cout << map; + + while (sw->alive() && ar->alive()) { + Vec2 from = sw->position(); + Vec2 to = from.shifted({0, 1}); + + std::cout << "\nmoving " << sw << " from " << from + << " to " << to << "...\n"; + + if (!sw->canMove(map->iterAt(to))) { + std::cout << "can't move\n"; + break; + } + + if (map->addUnit(map->removeUnitAt(from), to).null()) { + std::cout << "failed\n"; + break; + } + + std::cout << ar << " shoots at " << sw->position() << "...\n"; + + auto toi = map->iterAt(sw->position()); + if (!ar->canAttackTo(toi)) { + std::cout << "can't shoot\n"; + // it’s ok + } else { + auto pos = ar->actualPosition(toi); + if (Unit *targ = pos.unit()) { + auto dam = ar->baseAttack(toi); + + std::cout << "hits " << targ << "\n"; + + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + + std::cout << "now: " << targ << "\n"; + } + } + } +} + +MapIter +doAttack(Unit *u, MapIter to) +{ + if (!u->canAttackTo(to)) { + return MapIter::makeNull(); + + } else { + auto pos = u->actualPosition(to); + + if (Unit *targ = pos.unit()) { + auto dam = u->baseAttack(pos); + targ->takeDamage( + targ->actualDamage( + dam.first, dam.second).evaluate()); + return pos; + } + + return MapIter::makeNull(); + } +} + +struct SimpleGame { + Map *map; + Base *b1, *b2; + EventPrinter *pr; + + explicit SimpleGame(int w=10, int h=10, + int x1=1, int y1=1, + int x2=9, int y2=9) + { + pr = new EventPrinter {std::cout}; + + map = new Map {w, h}; + + b1 = new Base {}; + pr->setPrefix(b1, "Base 1"); + + map->addBase(b1, {x1, y1}); + b1->subscribe(pr); + + b2 = new Base {}; + pr->setPrefix(b2, "Base 2"); + + map->addBase(b2, {x2, y2}); + b2->subscribe(pr); + } + + ~SimpleGame() + { + delete map; + delete pr; + } +}; + +void +demo4() +{ + SimpleGame g {}; + + Unit *u1 = new units::Swordsman {}; + Unit *u2 = new units::Swordsman {}; + + g.b1->addUnit(u1); + g.b2->addUnit(u2); + + g.map->addUnit(u1, {3, 3}); + g.map->addUnit(u2, {4, 3}); + + while (u1->alive() + && u2->alive()) { + doAttack(u1, g.map->iterAt(u2->position())); + if (u2->alive()) { + doAttack(u2, g.map->iterAt(u1->position())); + } + } +} + +MapIter +doMove(Map *map, const Unit *u, Vec2 pt) +{ + auto from = map->iterAt(u->position()); + auto to = map->iterAt(pt); + + if (!u->canMove(to)) { + return MapIter::makeNull(); + } + + return map->addUnit(map->removeUnitAt(from), pt); +} + +Unit * +setupUnit(Base *base, const std::string &k, Mediator *m, Vec2 pt) +{ + Unit *u = base->getUnitById(base->createUnit(k, m)); + m->teleportUnit(u, pt); + return u; +} + +void +demo5() +{ + SimpleGame g {}; + + // 2,2 .. 5,5: swamp + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + g.map->setLandscape(new landscapes::Swamp {}, + {2+i, 2+j}); + } + } + + // 1,7 .. 6,9: forest + for (int j = 0; j < 2; ++j) { + for (int i = 0; i < 5; ++i) { + g.map->setLandscape(new landscapes::Forest {}, + {1+i, 7+j}); + } + } + + auto *m = new Mediator {g.map}; + auto u1 = setupUnit(g.b1, "swordsman", m, {2, 2}); + auto u2 = setupUnit(g.b2, "swordsman", m, {3, 2}); + + if (doMove(g.map, u1, {0, 2}).null()) { + std::cout << "Can't move " << u1 << " across 2 cells\n"; + } else { + std::cout << "Moved " << u1 << " across 2 cells \n"; + } + + std::cout << "u1: " << u1 << "\n"; + + if (doAttack(u1, g.map->iterAt(u2->position())).null()) { + std::cout << "Can't attack in swamp\n"; + } else { + std::cout << "Attacked in a swamp\n"; + } + + std::cout << "u2: " << u2 << "\n"; + + doMove(g.map, u1, {1, 2}); + doMove(g.map, u2, {2, 3}); + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + doAttack(u1, g.map->iterAt(u2->position())); + + auto u3 = setupUnit(g.b1, "spearsman", m, {3, 8}); + auto u4 = setupUnit(g.b1, "spearsman", m, {7, 8}); + auto u5 = setupUnit(g.b2, "onager", m, {5, 5}); + + while (u3->alive()) + doAttack(u5, g.map->iterAt(u3->position())); + + while (u4->alive()) + doAttack(u5, g.map->iterAt(u4->position())); + + std::cout << g.map; + + delete m; +} + +void +demo6() +{ + SimpleGame g {}; + + auto *m = new Mediator {g.map}; + auto *u1 = setupUnit(g.b1, "slinger", m, {1, 5}); + auto *u2 = setupUnit(g.b2, "swordsman", m, {6, 5}); + + g.map->addNeutralObject(new objects::Tower {}, {1, 5}); + g.map->addNeutralObject(new objects::HealingWell {}, {6, 5}); + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " can't reach " << u2 << "\n"; + } else { + std::cout << u1 << " somehow reached " << u2 << "\n"; + } + + if (m->useObject(u1)) { + std::cout << u1 << " used the tower\n"; + } else { + std::cout << u1 << " can't use the tower\n"; + } + + if (m->attackTo(u1, u2->position())) { + std::cout << u1 << " still can't reach " << u2 << "\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; + + if (m->useObject(u2)) { + std::cout << u2 << " used the healing well\n"; + } + + std::cout << "u1: " << u1 << "\n"; + std::cout << "u2: " << u2 << "\n"; +} + +void +demo7() +{ + class MoverPlayer: public Player { + std::string _unit_type; + int _id = -1; + + public: + using Player::Player; + + MoverPlayer(std::string name, + std::string type) + :Player{name}, _unit_type{std::move(type)} {} + + virtual bool + takeTurn(const Game *, Mediator *m, Base *b) override + { + if (_id >= 0) { + Unit *u = b->getUnitById(_id); + m->moveUnitTo(u, u->position().shifted({1, 0})); + } else { + _id = b->createUnit(_unit_type, m); + } + return true; + } + }; + + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + auto *b = new Base {}; + map->addBase(b, {3, 3}); + int b_id = g.addBase(b); + + auto *p = new MoverPlayer {"Player 1", "swordsman"}; + g.setPlayer(b_id, p); + + for (int i = 0; i < 5; ++i) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo8() +{ + auto *map = new Map {10, 10}; + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + std::stringstream s1 {}; + s1 << "base\n" + << "create spearsman\n" + << "map 0 0 9 9\n" + << "move 0 3 5\n" + << "map 0 0 9 9\n" + << "describe 3 5\n"; + + auto *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + std::stringstream s2 {}; + s2 << "create archer\n" + << "attack 0 3 5\n"; + + auto *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} + +void +demo9() +{ + Map *map = new Map {10, 10}; + + auto *uf = (new objects + ::WeaponSmiths + ::CatapultUnitFilter {}); + auto *ws = new objects::WeaponSmiths {2.0, uf}; + map->addNeutralObject(ws, {3, 4}); + + Game g {map}; + + auto *pr = new EventPrinter {std::cout}; + g.subscribe(pr); + + Base *b1 = new Base {}; + map->addBase(b1, {3, 3}); + int b1_id = g.addBase(b1); + + Base *b2 = new Base {}; + map->addBase(b2, {7, 3}); + int b2_id = g.addBase(b2); + + std::stringstream s1 {}; + std::stringstream s2 {}; + + s1 << "create onager\n" + << "move 0 3 4\n" + << "use 0\n" + << "create onager\n" + << "move 1 3 2\n" + << "attack 0 7 4\n" + << "attack 1 7 2\n" + << "create swordsman\n" + << "move 0 3 5\n" + << "move 2 3 4\n" + << "use 2\n"; + + s2 << "create swordsman\n" + << "move 0 7 4\n" + << "create swordsman\n" + << "move 1 7 2\n" + << "skip skip skip skip skip\n"; + + auto *p1 = new IostreamPlayer {"Player 1"}; + p1->setOstream(std::cout); + p1->setIstream(s1); + g.setPlayer(b1_id, p1); + + auto *p2 = new IostreamPlayer {"Player 2"}; + p2->setOstream(std::cout); + p2->setIstream(s2); + g.setPlayer(b2_id, p2); + + while (g.playersCount()) + g.spin(); + + g.unsubscribe(pr); + delete pr; +} diff --git a/8303/Parfentev_Leonid/lab7/demo.hpp b/8303/Parfentev_Leonid/lab7/demo.hpp new file mode 100644 index 000000000..ae8bc7a95 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/demo.hpp @@ -0,0 +1,14 @@ +#ifndef _H_DEMO_HPP +#define _H_DEMO_HPP + +void demo1(); +void demo2(); +void demo3(); +void demo4(); +void demo5(); +void demo6(); +void demo7(); +void demo8(); +void demo9(); + +#endif diff --git a/8303/Parfentev_Leonid/lab7/event.cpp b/8303/Parfentev_Leonid/lab7/event.cpp new file mode 100644 index 000000000..b7ff78ebd --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/event.cpp @@ -0,0 +1,31 @@ +#include "event.hpp" + +void +EventEmitter::emit_shared(Event *e) const +{ + for (auto iter = _listeners.begin(); iter != _listeners.end();) { + auto *listener = *iter++; + // note: the listener may safely unsubscribe when handling the + // event. + listener->handle(e); + } +} + +void +EventEmitter::emit(Event *e) const +{ + emit_shared(e); + delete e; +} + +void +EventEmitter::subscribe(EventListener *l) +{ + _listeners.insert(l); +} + +void +EventEmitter::unsubscribe(EventListener *l) +{ + _listeners.erase(l); +} diff --git a/8303/Parfentev_Leonid/lab7/event.hpp b/8303/Parfentev_Leonid/lab7/event.hpp new file mode 100644 index 000000000..fa2d1f541 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/event.hpp @@ -0,0 +1,62 @@ +#ifndef _H_EVENT_HPP +#define _H_EVENT_HPP + +#include + +class EventListener; +class Event; + +class EventEmitter { + std::set _listeners {}; + +public: + void emit_shared(Event *e) const; + void emit(Event *e) const; + + void subscribe(EventListener *l); + void unsubscribe(EventListener *l); + + virtual ~EventEmitter() {} +}; + +class Event { +public: + virtual ~Event() {} +}; + +class EventListener { +public: + virtual void handle(Event *e) =0; + + virtual ~EventListener() {} +}; + +class EventForwarder; + +namespace events { + + class Forwarded: public Event { + Event *_e; + EventForwarder *_f; + + public: + Forwarded(Event *e, EventForwarder *f) + :_e{e}, _f{f} {} + + Event *event() const { return _e; } + EventForwarder *forwarder() const { return _f; } + }; + +} + +class EventForwarder: public EventEmitter, + public EventListener { +public: + virtual void + handle(Event *e) override + { + emit(new events::Forwarded {e, this}); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/event_printer.hpp b/8303/Parfentev_Leonid/lab7/event_printer.hpp new file mode 100644 index 000000000..afeca85b3 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/event_printer.hpp @@ -0,0 +1,127 @@ +#ifndef _H_EVENT_PRINTER_HPP +#define _H_EVENT_PRINTER_HPP + +#include +#include +#include +#include + +#include "event.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "player.hpp" +#include "object_print.hpp" + + +class EventPrinter: public EventListener { + std::ostream *_os; + bool _free_os; + + std::map _prefix_map {}; + + std::string + makeName(const char *base, int idx) + { + std::ostringstream oss {}; + oss << base << " " << idx; + return oss.str(); + } + +public: + EventPrinter(std::ostream &os) + :_os{&os}, _free_os{false} {} + + EventPrinter(std::ostream *os) + :_os{os}, _free_os{true} {} + + std::ostream & + ostream() const { return *_os; } + + void + setPrefix(EventForwarder *f, const std::string &s) + { + _prefix_map[f] = s; + } + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + auto iter = _prefix_map.find(ee->forwarder()); + if (iter != _prefix_map.end()) { + (*_os) << iter->second << ": "; + } + + return handle(ee->event()); + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit added: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit died: " << ee->unit() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " takes " + << ee->damage() << " health points of damage\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() << " gets healed by " + << ee->health() << " health points\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->attacker() + << " attacked another unit " << ee->target() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->target() + << " was attacked by another unit " + << ee->attacker() << "\n"; + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " moved from " << ee->sourcePos() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Unit " << ee->unit() + << " used object " << ee->neutralObject() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "(Live unit " << ((void *)ee->unit()) + << " deleted)\n"; + + } else if (dynamic_cast(e)) { + (*_os) << "Base destroyed\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << "\n"; + + } else if (auto *ee = + dynamic_cast(e)) { + (*_os) << "Turn of player " + << ee->player()->name() << " over\n"; + + } else { + (*_os) << "Unknown event\n"; + } + } + + virtual ~EventPrinter() override + { + if (_free_os) { + _os->flush(); + delete _os; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/event_types.hpp b/8303/Parfentev_Leonid/lab7/event_types.hpp new file mode 100644 index 000000000..810d3bd94 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/event_types.hpp @@ -0,0 +1,156 @@ +#ifndef _H_EVENT_TYPES_HPP +#define _H_EVENT_TYPES_HPP + +#include "point.hpp" +#include "event.hpp" + + +class Unit; +class Base; +class NeutralObject; +class Player; + + +class UnitEvent: public Event { + Unit *_u; + +public: + UnitEvent(Unit *u) :_u{u} {} + + Unit *unit() const { return _u; } +}; + +class AttackEvent: public Event { + Unit *_a, *_b; + +public: + AttackEvent(Unit *a, Unit *b) :_a{a}, _b{b} {} + + Unit *attacker() const { return _a; } + Unit *target() const { return _b; } +}; + +class BaseEvent: public Event { + Base *_b; + +public: + BaseEvent(Base *b) :_b{b} {} + + Base *base() const { return _b; } +}; + +class PlayerEvent: public Event { + Player *_p; + +public: + PlayerEvent(Player *p) :_p{p} {} + + Player *player() const { return _p; } +}; + +class UnitMoveEvent: public UnitEvent { + Vec2 _src; + +public: + UnitMoveEvent(Unit *u, Vec2 src) + :UnitEvent{u}, _src{src} {} + + Vec2 sourcePos() const { return _src; } +}; + + + +namespace events { + + class UnitDeath: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitAdded: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitLiveDeleted: public UnitEvent { + public: + using UnitEvent::UnitEvent; + }; + + class UnitTakesDamage: public UnitEvent { + int _dmg; + + public: + UnitTakesDamage(Unit *u, int dmg) + :UnitEvent{u}, _dmg{dmg} {} + + int damage() const { return _dmg; } + }; + + class UnitGetsHealed: public UnitEvent { + int _hp; + + public: + UnitGetsHealed(Unit *u, int hp) + :UnitEvent{u}, _hp{hp} {} + + int health() const { return _hp; } + }; + + class UnitWasAttacked: public AttackEvent { + public: + using AttackEvent::AttackEvent; + }; + + class UnitAttacked: public AttackEvent { + Vec2 _tc; + + public: + UnitAttacked(Unit *u, Vec2 tc, Unit *tu) + :AttackEvent{u, tu}, _tc{tc} {} + + Vec2 targetCoordinates() const { return _tc; } + }; + + class UnitMoved: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitTeleported: public UnitMoveEvent { + public: + using UnitMoveEvent::UnitMoveEvent; + }; + + class UnitUsedObject: public UnitEvent { + NeutralObject *_n; + + public: + UnitUsedObject(Unit *u, NeutralObject *n) + :UnitEvent{u}, _n{n} {} + + NeutralObject *neutralObject() const { return _n; } + }; + + + + class BaseDestroyed: public BaseEvent { + public: + using BaseEvent::BaseEvent; + }; + + + + class TurnStarted: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + + class TurnOver: public PlayerEvent { + public: + using PlayerEvent::PlayerEvent; + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/exceptions.hpp b/8303/Parfentev_Leonid/lab7/exceptions.hpp new file mode 100644 index 000000000..9822a5084 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/exceptions.hpp @@ -0,0 +1,57 @@ +#ifndef _H_EXCEPTIONS_HPP +#define _H_EXCEPTIONS_HPP + +#include +#include +#include + + +class NoSavegameFile: public std::exception { + std::string _fn; + + mutable std::string _msg {""}; + +public: + NoSavegameFile(const std::string &fn) + :_fn{fn} {} + + virtual const char * + what() const noexcept override + { + if (!_msg.length()) { + std::ostringstream oss {}; + oss << "Can't open savegame file: " << _fn; + _msg = oss.str(); + } + return _msg.c_str(); + } +}; + +class InvalidSaveFileContents: public std::exception { + using pos_t = std::istream::pos_type; + + std::string _fn; + pos_t _pos; + + mutable std::string _msg {""}; + +public: + InvalidSaveFileContents(const std::string &fn, pos_t pos) + :_fn{fn}, _pos{pos} {} + + virtual const char * + what() const noexcept override + { + if (!_msg.length()) { + std::ostringstream oss {}; + oss << "Error while reading savegame file " << _fn; + if (_pos >= 0) { + oss << " around position " << _pos; + } + _msg = oss.str(); + } + return _msg.c_str(); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/factory_table.hpp b/8303/Parfentev_Leonid/lab7/factory_table.hpp new file mode 100644 index 000000000..693031648 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/factory_table.hpp @@ -0,0 +1,81 @@ +#ifndef _H_FACTORY_TABLE_HPP +#define _H_FACTORY_TABLE_HPP + +#include +#include + +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "unit_factory.hpp" + + +class FactoryTable { + std::map _tab {}; + + void + registerFactory(const std::string &key, UnitFactory *f) + { + _tab[key] = f; + } + + FactoryTable() + { +#define REG(k, T) \ + registerFactory( \ + k, new SimpleUnitFactory {}) + REG("swordsman", Swordsman); + REG("spearsman", Spearsman); + REG("cavalry", Cavalry); + REG("archer", Archer); + REG("slinger", Slinger); + REG("onager", Onager); + REG("boltthrower", BoltThrower); +#undef REG + } + +public: + static const FactoryTable * + instance() + { + static FactoryTable *inst = new FactoryTable {}; + return inst; + } + + virtual bool + canCreate(const std::string &key) const + { + return _tab.find(key) != _tab.end(); + } + + virtual std::vector + keys() const + { + std::vector keys {}; + for (auto p: _tab) { + keys.push_back(p.first); + } + return keys; + } + + virtual Unit * + create(const std::string &key) const + { + auto iter = _tab.find(key); + if (iter == _tab.end()) { + return nullptr; + } + + return iter->second->create(); + } + + ~FactoryTable() + { + for (auto p: _tab) { + delete p.second; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/game.cpp b/8303/Parfentev_Leonid/lab7/game.cpp new file mode 100644 index 000000000..dd9272b12 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/game.cpp @@ -0,0 +1,215 @@ +#include +#include + +#include "base.hpp" +#include "game.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "storable.hpp" +#include "common_storables.hpp" + + +Game::Game(Map *map) + :_map{map}, + _med{new Mediator {map}} +{ + this->subscribe(_log_sink); +} + +int +Game::addBase(Base *base) +{ + base->subscribe(_coll); + base->subscribe(this); + + _recs.push_back(BaseRecord{base, nullptr}); + + return (int)(_recs.size() - 1); +} + +void +Game::setPlayer(int base_idx, Player *p) +{ + Player *&p_place = _recs[base_idx].player; + if (p_place) { + p_place->unsubscribe(_log_sink); + delete p_place; + --_players; + } + p_place = p; + if (p) { + p->subscribe(_log_sink); + ++_players; + } +} + +int +Game::basesCount() const +{ + return _recs.size(); +} + +int +Game::playersCount() const +{ + return _players; +} + +Base * +Game::baseByIdx(int idx) const +{ + return _recs[idx].base; +} + +void +Game::spin() +{ + auto &rec = _recs[_next]; + Player *p = rec.player; + Base *b = rec.base; + + if (p) { + if (b->destroyed()) { + setPlayer(_next, nullptr); + + } else { + emit(new events::TurnStarted {p}); + + bool res = p->takeTurn(this, _med, b); + + _coll->collect(_map); + + emit(new events::TurnOver {p}); + + if (!res) { + setPlayer(_next, nullptr); + } + } + } + + if (++_next == (int)_recs.size()) { + _next = 0; + } +} + +void +Game::setResetHandler(ResetHandler *r) +{ + _reset = r; +} + +void +Game::requestReset() const +{ + _reset->reset(); +} + +void +Game::store(std::ostream &os) const +{ + os << "game\n"; + _map->store(os); + + os << _next << "\n"; + + for (int i = 0; i < basesCount(); ++i) { + auto *b = baseByIdx(i); + + StorableWithCoords::storeWithCoords(b->position(), b, os); + + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + auto *u = iter.unit(); + auto *sc = new StorableWithCoords {u->position(), u}; + StorableWithIndex::storeWithIndex(i, sc, os); + } + } + + for (int i = 0; i < basesCount(); ++i) { + auto *p = _recs[i].player; + if (p) { + StorableWithIndex::storeWithIndex(i, p, os); + } + } + + os << "end\n"; +} + +bool +Game::restore(std::istream &is, + RestorerTable *tab) +{ + is >> _next; + + for (;;) { + Storable *s = tab->restore(is); + if (auto *sc = + dynamic_cast(s)) { + if (auto *b + = dynamic_cast(sc->child())) { + _map->addBase(b, sc->coords()); + addBase(b); + } else { + delete sc->child(); + delete sc; + return false; + } + delete sc; + } else if (auto *si = + dynamic_cast(s)) { + if (auto *p + = dynamic_cast(si->child())) { + if (si->index() < 0 + || si->index() >= basesCount()) { + delete p; + delete si; + return false; + } + setPlayer(si->index(), p); + } else if (auto *sc = + dynamic_cast( + si->child())) { + if (auto *u + = dynamic_cast(sc->child())) { + _map->addUnit(u, sc->coords()); + baseByIdx(si->index())->addUnit(u); + delete sc; + } else { + delete si; + delete sc; + delete sc->child(); + return false; + } + } else { + delete si->child(); + delete si; + return false; + } + delete si; + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} + +Game::~Game() +{ + for (int i = 0; i < (int)_recs.size(); ++i) { + setPlayer(i, nullptr); + auto &rec = _recs[i]; + rec.base->unsubscribe(this); + rec.base->unsubscribe(_coll); + } + + delete _coll; + delete _map; + + delete _log_sink; +} diff --git a/8303/Parfentev_Leonid/lab7/game.hpp b/8303/Parfentev_Leonid/lab7/game.hpp new file mode 100644 index 000000000..732271be2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/game.hpp @@ -0,0 +1,68 @@ +#ifndef _H_GAME_HPP +#define _H_GAME_HPP + +#include + +#include "event.hpp" +#include "map.hpp" +#include "base.hpp" +#include "player.hpp" +#include "zombie_collector.hpp" +#include "mediator.hpp" +#include "storable.hpp" + + +class ResetHandler { +public: + virtual void reset() =0; + + virtual ~ResetHandler() {} +}; + +class Game: public EventForwarder, + public Storable { + Map *_map; + Mediator *_med; + + struct BaseRecord { + Base *base; + Player *player; + }; + + std::vector _recs {}; + int _next = 0; + int _players = 0; + + ZombieCollector *_coll {new ZombieCollector {}}; + + EventForwarder *_log_sink {new EventForwarder {}}; + + ResetHandler *_reset; + +public: + Game(Map *map); + + int addBase(Base *b); + void setPlayer(int base_idx, Player *p); + + int basesCount() const; + int playersCount() const; + + Base *baseByIdx(int idx) const; + + EventForwarder *logSink() const { return _log_sink; } + + void spin(); + + void setResetHandler(ResetHandler *r); + void requestReset() const; + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; + + ~Game(); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab7/game_driver.hpp b/8303/Parfentev_Leonid/lab7/game_driver.hpp new file mode 100644 index 000000000..79f78c3fd --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/game_driver.hpp @@ -0,0 +1,123 @@ +#ifndef _H_GAME_DRIVER_HPP +#define _H_GAME_DRIVER_HPP + +#include "map.hpp" +#include "game.hpp" + + +class GameRules { +public: + virtual Map *makeMap() =0; + virtual void setup(Game *g, Map *m) =0; + virtual bool gameEnded(Game *g) =0; + virtual int winner(Game *g) =0; +}; + +template +class GameDriver: public ResetHandler { + + Game *_g = nullptr; + Game *_g_reset = nullptr; // required to reset game while running + Rules *_r {new Rules {}}; + + std::vector _loggers {}; + EventPrinter *_printer = nullptr; + + Game *init() + { + Map *m = _r->makeMap(); + Game *g = new Game {m}; + _r->setup(g, m); + return g; + } + + void setBasePrefixes(EventPrinter *p) + { + int n = _g->basesCount(); + for (int i = 0; i < n; ++i) { + std::ostringstream oss {}; + oss << "Base " << (i+1); + p->setPrefix(_g->baseByIdx(i), oss.str()); + } + } + +public: + void + addLogger(EventPrinter *l) + { + _loggers.push_back(l); + if (_g) { + _g->logSink()->subscribe(l); + setBasePrefixes(l); + } + } + + void + setPrinter(EventPrinter *pr) + { + if (_printer) { + if (_g) { + _g->unsubscribe(_printer); + } + delete _printer; + } + + _printer = pr; + if (_g && _printer) { + _g->subscribe(_printer); + setBasePrefixes(_printer); + } + } + + virtual void reset() override + { + resetFrom(init()); + } + + void resetFrom(Game *g) + { + _g_reset = g; + } + + void run() + { + for (;;) { + if (_g_reset) { + delete _g; + _g = _g_reset; + _g_reset = nullptr; + _g->setResetHandler(this); + + if (_printer) { + _g->subscribe(_printer); + setBasePrefixes(_printer); + } + for (auto *l: _loggers) { + _g->logSink()->subscribe(l); + setBasePrefixes(l); + } + } + if (_r->gameEnded(_g)) { + break; + } + _g->spin(); + } + } + + int winner() + { + return _r->winner(_g); + } + + virtual ~GameDriver() override + { + delete _g; + delete _g_reset; + delete _printer; + for (auto *l: _loggers) { + delete l; + } + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/game_rules.hpp b/8303/Parfentev_Leonid/lab7/game_rules.hpp new file mode 100644 index 000000000..6a43579aa --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/game_rules.hpp @@ -0,0 +1,145 @@ +#ifndef _H_GAME_RULES_HPP +#define _H_GAME_RULES_HPP + +#include +#include + +#include "map.hpp" +#include "game.hpp" +#include "iostream_player.hpp" +#include "game_driver.hpp" +#include "landscape_types.hpp" + + +class BaseWithSpawnCountdown: public Base { + int _cd_max; + int _cd = 0; + +public: + BaseWithSpawnCountdown(int cd_max=0) + :_cd_max{cd_max} {} + + virtual bool + canCreateUnit(const std::string &key) const override + { + return _cd == 0 + && Base::canCreateUnit(key); + } + + virtual int + createUnit(const std::string &key, Mediator *m) override + { + int id = Base::createUnit(key, m); + if (id < 0) { + return id; + } + + _cd = _cd_max; + return id; + } + + virtual void + store(std::ostream &os) const override + { + os << "base_w_countdown " << maxUnitsCount() + << " " << destroyed() << " " << _cd_max << " " + << _cd << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) override + { + if (!Base::restore(is, tab)) { + return false; + } + + is >> _cd_max >> _cd; + return !is.fail(); + } + + virtual void + spin() override + { + if (_cd > 0) { + --_cd; + } + } +}; + +class DefaultRules: public GameRules { +protected: + static void + addBaseAndPlayer(Game *g, Map *m, + std::string name, int x, int y) + { + Base *b = new BaseWithSpawnCountdown {5}; + m->addBase(b, {x, y}); + int id = g->addBase(b); + + auto *p = new IostreamPlayer {std::move(name)}; + p->setOstream(std::cout); + p->setIstream(std::cin); + g->setPlayer(id, p); + } + +public: + virtual Map *makeMap() override + { + return new Map {10, 10}; + } + + virtual void setup(Game *g, Map *m) override + { + addBaseAndPlayer(g, m, "Player 1", 1, 1); + addBaseAndPlayer(g, m, "Player 2", 8, 8); + } + + virtual bool gameEnded(Game *g) override + { + return g->playersCount() < 2; + } + + virtual int winner(Game *g) override + { + int n = g->basesCount(); + int only = -1; + + for (int i = 0; i < n; ++i) { + if (!g->baseByIdx(i)->destroyed()) { + if (only < 0) { + only = i; + } else { + return -1; + } + } + } + + return only; + } +}; + +class FancyRules: public DefaultRules { + virtual Map *makeMap() override + { + Map *m = new Map {15, 15}; + + for (int y = 0; y < 5; ++y) { + for (int x = 0; x < 5; ++x) { + m->setLandscape(new landscapes::Forest {}, + {5+x, 5+y}); + } + } + + return m; + } + + virtual void setup(Game *g, Map *m) override + { + addBaseAndPlayer(g, m, "Player 1", 3, 3); + addBaseAndPlayer(g, m, "Player 2", 3, 11); + addBaseAndPlayer(g, m, "Player 3", 11, 11); + addBaseAndPlayer(g, m, "Player 4", 11, 3); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/iostream_player.cpp b/8303/Parfentev_Leonid/lab7/iostream_player.cpp new file mode 100644 index 000000000..b66882c5e --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/iostream_player.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +#include "game.hpp" +#include "base.hpp" +#include "iostream_player.hpp" +#include "event.hpp" +#include "logging.hpp" + + +void +IostreamPlayer::addCommand(const std::string &str, + IostreamCommand *cmd) +{ + _cmd_tab[str] = cmd; +} + +IostreamPlayer::IostreamPlayer(std::string name) + :Player{name} +{ + addCommand("move", new iostream_commands::Move {}); + addCommand("attack", new iostream_commands::Attack {}); + addCommand("use", new iostream_commands::Use {}); + addCommand("destroy", new iostream_commands::DestroyBase {}); + addCommand("create", new iostream_commands::Create {}); + addCommand("base", new iostream_commands::FindBase {}); + addCommand("units", new iostream_commands::ListUnits {}); + addCommand("describe", new iostream_commands::DescribeAt {}); + addCommand("map", new iostream_commands::PrintMap {}); + addCommand("skip", new iostream_commands::Skip {}); + addCommand("save", new iostream_commands::Save {}); + addCommand("reset", new iostream_commands::Reset {}); +} + +bool +IostreamPlayer::takeTurn(const Game *g, Mediator *m, Base *b) +{ + for (;;) { + std::string cmd_name; + (*_is) >> cmd_name; + + if (_is->fail()) { + return false; + } + + { + std::ostringstream oss {}; + oss << "User picked action: " << cmd_name; + emit(new events::UserActionEvent {this, oss.str()}); + } + + auto iter = _cmd_tab.find(cmd_name); + if (iter == _cmd_tab.end()) { + std::ostringstream oss {}; + oss << "Unknown command: \"" << cmd_name << "\""; + emit(new events::UserActionEvent {this, oss.str()}); + (*_os) << oss.str() << "\n"; + continue; + } + + if (iter->second->execute(g, this, m, b)) { + break; + } + } + + return true; +} + +int +IostreamPlayer::readInt() +{ + int x; + (*_is) >> x; + return x; +} + +Unit * +IostreamPlayer::readUnitId(Base *b) +{ + int id = readInt(); + Unit *u = b->getUnitById(id); + if (!u) { + (*_os) << "No such unit: " << id << "\n"; + } + + return u; +} + +Vec2 +IostreamPlayer::readVec2() +{ + int x, y; + (*_is) >> x >> y; + return {x, y}; +} + +std::string +IostreamPlayer::readString() +{ + std::string s; + (*_is) >> s; + return s; +} + +void +IostreamPlayer::store(std::ostream &os) const +{ + os << "iostream_player\n" << name() << "\n"; +} + +bool +IostreamPlayer::restore(std::istream &is, + RestorerTable *tab) +{ + Player::restore(is, tab); + + // We can’t serialize/deserialize IO streams + setOstream(std::cout); + setIstream(std::cin); + return true; +} diff --git a/8303/Parfentev_Leonid/lab7/iostream_player.hpp b/8303/Parfentev_Leonid/lab7/iostream_player.hpp new file mode 100644 index 000000000..95e5a8345 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/iostream_player.hpp @@ -0,0 +1,366 @@ +#ifndef _H_STDIO_PLAYER_HPP +#define _H_STDIO_PLAYER_HPP + +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "game.hpp" +#include "object_print.hpp" + +#include "event.hpp" +#include "logging.hpp" + + +class IostreamPlayer; + +class IostreamCommand { +public: + // -> whether to end turn + virtual bool execute(const Game *g, IostreamPlayer *p, + Mediator *m, Base *b) =0; + + virtual ~IostreamCommand() {} +}; + +class IostreamPlayer: public Player { + std::ostream *_os = nullptr; + std::istream *_is = nullptr; + bool _free_os, _free_is; + + std::map _cmd_tab {}; + + void + addCommand(const std::string &str, + IostreamCommand *cmd); + + +public: + IostreamPlayer(std::string name=""); + + void + setOstream(std::ostream *os) { _os = os; _free_is = true; } + void + setOstream(std::ostream &os) { _os = &os; _free_os = false; } + + std::ostream & + ostream() const { return *_os; } + + void + setIstream(std::istream *is) { _is = is; _free_is = true; } + void + setIstream(std::istream &is) { _is = &is; _free_is = false; } + + std::istream & + istream() const { return *_is; } + + virtual bool + takeTurn(const Game *g, Mediator *m, Base *b) override; + + int + readInt(); + + Unit * + readUnitId(Base *b); + + Vec2 + readVec2(); + + std::string + readString(); + + virtual void store(std::ostream &os) const override; + + virtual bool restore(std::istream &is, + RestorerTable *tab) override; +}; + +namespace iostream_commands { + + class Move: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested moving " << u << " to " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->moveUnitTo(u, to)) { + p->ostream() << "Can't move unit " << u + << " to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Attack: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + Vec2 to = p->readVec2(); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u << " attacks " << to; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->attackTo(u, to)) { + p->ostream() << "Unit " << u + << " can't attack to " << to << "\n"; + return false; + } + + return true; + } + }; + + class Use: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u + << " uses an object"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->useObject(u)) { + p->ostream() << "Unit " << u + << " can't use any object there\n"; + return false; + } + + return true; + } + }; + + class DestroyBase: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + Unit *u = p->readUnitId(b); + if (!u) + return false; + + { + std::ostringstream oss {}; + oss << "User requested that " << u + << " destroys a base"; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!m->destroyBase(u)) { + p->ostream() << "Unit " << u + << " cannot destroy any bases there\n"; + return false; + } + + return true; + } + }; + + class Create: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *b) override + { + std::string s = p->readString(); + + { + std::ostringstream oss {}; + oss << "User requested creation of unit " << s; + p->emit(new events::UserActionEvent {p, oss.str()}); + } + + if (!b->canCreateUnit(s)) { + p->ostream() << "Can't create unit of type " + << s << "\n"; + return false; + } + + int id = b->createUnit(s, m); + if (id < 0) { + p->ostream() << "Failed to create a unit of type " + << s << "\n"; + return false; + } + + p->ostream() << "New unit of type " << s + << ": " << id << "\n"; + return true; + } + }; + + class FindBase: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *b) override + { + p->ostream() << "Base: " << b << "\n"; + return false; + } + }; + + class ListUnits: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *b) override + { + p->ostream() << "Units:"; + for (auto iter = b->unitsBegin(); + iter != b->unitsEnd(); + ++iter) { + p->ostream() << "- " << iter.id() + << ": " << iter.unit() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class DescribeAt: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *) override + { + auto pos = p->readVec2(); + auto info = m->infoAt(pos); + + p->ostream() << "At " << pos << "\n"; + p->ostream() + << "- Landscape: " << info.landscape() << "\n"; + + if (auto *b = info.base()) { + p->ostream() << "- Base: " << b << "\n"; + } + + if (auto *u = info.unit()) { + p->ostream() << "- Unit: " << u << "\n"; + } + + if (auto *n = info.neutralObject()) { + p->ostream() << "- Object: " << n << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class PrintMap: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *m, Base *) override + { + auto from = p->readVec2(); + auto to = p->readVec2(); + + p->ostream() << "From " << from + << " to " << to << ":\n"; + + for (int y = from.y(); y < to.y(); ++y) { + for (int x = from.x(); x < to.x(); ++x) { + if (x != from.x()) { + p->ostream() << " "; + } + displayMapInfo(p->ostream(), m->infoAt({x, y})); + } + p->ostream() << "\n"; + } + + p->ostream() << std::endl; + return false; + } + }; + + class Skip: public IostreamCommand { + public: + virtual bool + execute(const Game *, IostreamPlayer *p, + Mediator *, Base *) override + { + std::ostringstream oss {}; + oss << "User decided to skip the turn"; + p->emit(new events::UserActionEvent {p, oss.str()}); + + return true; + } + }; + + class Save: public IostreamCommand { + public: + virtual bool + execute(const Game *g, IostreamPlayer *p, + Mediator *, Base *) override + { + std::string fn = p->readString(); + std::ofstream of {fn}; + if (!of) { + p->ostream() << "Failed to open file\n"; + return false; + } + + g->store(of); + p->ostream() << "Save complete\n"; + + std::ostringstream oss {}; + oss << "Saved current game to " << fn; + p->emit(new events::UserActionEvent {p, oss.str()}); + + return false; + } + }; + + class Reset: public IostreamCommand { + public: + virtual bool + execute(const Game *g, IostreamPlayer *p, + Mediator *, Base *) override + { + std::ostringstream oss {}; + oss << "Requesting reset"; + p->emit(new events::UserActionEvent {p, oss.str()}); + + g->requestReset(); + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/landscape.hpp b/8303/Parfentev_Leonid/lab7/landscape.hpp new file mode 100644 index 000000000..3df7e506d --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/landscape.hpp @@ -0,0 +1,29 @@ +#ifndef _H_LANDSCAPE_HPP +#define _H_LANDSCAPE_HPP + + +#include "storable.hpp" + +class Unit; + +class Landscape: public Storable { +public: + virtual void onEnter(Unit *u) =0; + virtual void onLeave(Unit *u) =0; + + virtual ~Landscape() {} +}; + +namespace landscapes { + + class Normal: public Landscape { + public: + virtual void onEnter(Unit *) override {} + virtual void onLeave(Unit *) override {} + + TRIVIALLY_STORABLE("l_normal"); + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/landscape_types.hpp b/8303/Parfentev_Leonid/lab7/landscape_types.hpp new file mode 100644 index 000000000..c53d2b99a --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/landscape_types.hpp @@ -0,0 +1,75 @@ +#ifndef _H_LANDSCAPE_TYPES_HPP +#define _H_LANDSCAPE_TYPES_HPP + +#include "landscape.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "common_policies.hpp" +#include "storable.hpp" + + +namespace landscapes { + + // Swamp: max speed is 1; attacking is forbidden + class Swamp: public Landscape { + ModifyingMovePolicy *_p; + AttackPolicy *_prev, *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _p = new ModifyingMovePolicy {u->movePolicy(), 1}; + u->setMovePolicy(_p); + + _prev = u->attackPolicy(); + _cur = new AttackForbidden {}; + u->setAttackPolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *mpc = u->findMoveContainerOf(_p)) { + mpc->setMovePolicy(_p->movePolicy()); + _p->setMovePolicy(nullptr); + delete _p; + _p = nullptr; + } + + // our policy might’ve been wrapped into something + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("l_swamp"); + }; + + class Forest: public Landscape { + DefensePolicy *_prev; + MultiplierDefensePolicy *_cur; + + public: + virtual void onEnter(Unit *u) override + { + _prev = u->defensePolicy(); + _cur = new MultiplierDefensePolicy {_prev, 2.0}; + u->setDefensePolicy(_cur); + } + + virtual void onLeave(Unit *u) override + { + if (auto *dpc = u->findDefenseContainerOf(_cur)) { + dpc->setDefensePolicy(_prev); + _cur->setDefensePolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("l_forest"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/logging.hpp b/8303/Parfentev_Leonid/lab7/logging.hpp new file mode 100644 index 000000000..72b272969 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/logging.hpp @@ -0,0 +1,46 @@ +#ifndef _H_LOGGING_HPP +#define _H_LOGGING_HPP + +#include +#include + +#include "event.hpp" +#include "event_printer.hpp" + + +namespace events { + + class UserActionEvent: public Event { + Player *_p; + std::string _s; + + public: + UserActionEvent(Player *p, std::string s) + :_p{p}, _s{std::move(s)} {} + + const std::string & + message() const { return _s; } + + Player *player() const { return _p;} + }; + +} + +class LoggingEventPrinter: public EventPrinter { +public: + using EventPrinter::EventPrinter; + + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + ostream() << ee->player()->name() + << ": " << ee->message() << "\n"; + return; + } + + EventPrinter::handle(e); + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/main.cpp b/8303/Parfentev_Leonid/lab7/main.cpp new file mode 100644 index 000000000..c9f4240dc --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/main.cpp @@ -0,0 +1,132 @@ +#include + +#include +#include +#include + +#include "demo.hpp" + +#include "event_printer.hpp" +#include "game_driver.hpp" +#include "game_rules.hpp" +#include "exceptions.hpp" + + +void +run_demos(void) +{ + std::cout << "Demo 1\n"; + demo1(); + + std::cout << "\nDemo 2\n"; + demo2(); + + std::cout << "\nDemo 3\n"; + demo3(); + + std::cout << "\nDemo 4\n"; + demo4(); + + std::cout << "\nDemo 5\n"; + demo5(); + + std::cout << "\nDemo 6\n"; + demo6(); + + std::cout << "\nDemo 7\n"; + demo7(); + + std::cout << "\nDemo 8\n"; + demo8(); + + std::cout << "\nDemo 9\n"; + demo9(); +} + +Game * +loadGame(const std::string &load_fn) +{ + std::ifstream f {load_fn}; + if (!f) { + throw NoSavegameFile {load_fn}; + } + auto *tab = RestorerTable::defaultTable(); + Storable *s = tab->restore(f); + delete tab; + + if (auto *lg = dynamic_cast(s)) { + return lg; + } else { + delete s; + throw InvalidSaveFileContents {load_fn, f.tellg()}; + } +} + +int +run_game(int argc, char **argv) +{ + std::vector loggers {}; + bool have_stdout = false; + + const char *load_fn = nullptr; + + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "-log")) { + char *fn = argv[++i]; + if (!strcmp(fn, "-")) { + loggers.push_back(new LoggingEventPrinter {std::cout}); + have_stdout = true; + } else { + auto *of = new std::ofstream {fn}; + if (!*of) { + std::cerr << "Failed to open file: " << fn << "\n"; + return 1; + } + loggers.push_back(new LoggingEventPrinter {of}); + } + } else if (!strcmp(argv[i], "-load")) { + load_fn = argv[++i]; + } else { + std::cerr << "Unknown option: " << argv[i] << "\n"; + return 1; + } + } + + GameDriver drv {}; + + for (auto *logger: loggers) { + drv.addLogger(logger); + } + + if (!have_stdout) { + drv.setPrinter(new EventPrinter {std::cout}); + } + + if (load_fn) { + try { + drv.resetFrom(loadGame(load_fn)); + } catch (std::exception &e) { + std::cerr << "An error occurred while loading savegame:\n" + << e.what() << "\n"; + return 1; + } + } else { + drv.reset(); + } + + drv.run(); + + return 0; +} + +int +main(int argc, char **argv) +{ + if (argc == 2 + && !strcmp(argv[1], "-demo")) { + run_demos(); + return 0; + } + + return run_game(argc, argv); +} diff --git a/8303/Parfentev_Leonid/lab7/map.cpp b/8303/Parfentev_Leonid/lab7/map.cpp new file mode 100644 index 000000000..66eddff6e --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/map.cpp @@ -0,0 +1,222 @@ +#include + +#include "point.hpp" +#include "unit.hpp" +#include "placeable.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "landscape.hpp" +#include "map.hpp" +#include "storable.hpp" +#include "common_storables.hpp" + + +Map::Map(int w, int h) + :_rm{w, h} +{ + for (auto rmiter = _rm.iterAt({0, 0}); + rmiter.y() < _rm.height(); + rmiter.advance(1)) { + rmiter.cell().setLandscape(new landscapes::Normal {}); + } +} + +MapIter +Map::addUnit(Unit *u, Vec2 pt) +{ + if (u->hasPosition()) + return MapIter::makeNull(); + + if (_units_max >= 0 + && _units_count == _units_max) + return MapIter::makeNull(); + + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.unit()) + return MapIter::makeNull(); + + cell.setUnit(u); + u->setPosition(pt); + + ++_units_count; + + cell.landscape()->onEnter(u); + + return MapIter{rmiter}; +} + +Unit * +Map::removeUnitAt(Vec2 at) +{ + RectMapIter rmiter = _rm.iterAt(at); + Cell &cell = rmiter.cell(); + Unit *u = dynamic_cast(cell.unit()); + + if (u) { + --_units_count; + cell.landscape()->onLeave(u); + if (auto *n = dynamic_cast(cell.object())) { + n->onLeave(u); + } + + cell.setUnit(nullptr); + u->unsetPosition(); + } + + return u; +} + +MapIter +Map::addPlaceable(Placeable *p, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + + if (cell.object()) { + return MapIter::makeNull(); + } + + cell.setObject(p); + p->setPosition(pt); + + return MapIter{rmiter}; +} + +MapIter +Map::addBase(Base *b, Vec2 pt) +{ + return addPlaceable(b, pt); +} + +MapIter +Map::addNeutralObject(NeutralObject *n, Vec2 pt) +{ + return addPlaceable(n, pt); +} + +void +Map::setLandscape(Landscape *l, Vec2 pt) +{ + RectMapIter rmiter = _rm.iterAt(pt); + Cell &cell = rmiter.cell(); + cell.setLandscape(l); +} + +void +Map::store(std::ostream &os) const +{ + os << "map " << width() << " " << height() << "\n"; + os << _units_max << "\n"; + + for (int y = 0; y < height(); ++y) { + for (int x = 0; x < width(); ++x) { + auto info = infoAt({x, y}); + + const auto *l = info.landscape(); + if (!dynamic_cast(l)) { + StorableWithCoords::storeWithCoords({x, y}, l, os); + } else if (const auto *n = info.neutralObject()) { + StorableWithCoords::storeWithCoords({x, y}, n, os); + } + + // bases and units are restored in Game::restore + } + } + + os << "end\n"; +} + +bool +Map::restore(std::istream &is, + RestorerTable *tab) +{ + is >> _units_max; + if (is.fail()) { + return false; + } + + for (;;) { + Storable *s = tab->restore(is); + + if (auto *sc = + dynamic_cast(s)) { + if (auto *l = + dynamic_cast(sc->child())) { + setLandscape(l, sc->coords()); + delete sc; + } else if (auto *n = + dynamic_cast(sc->child())) { + addNeutralObject(n, sc->coords()); + delete sc; + } else { + delete sc->child(); + delete sc; + return false; + } + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} + +MapInfo +Map::infoAt(Vec2 pt) const +{ + return MapInfo{&_rm.at(pt)}; +} + +Unit * +MapIter::unit() const +{ + return _it.cell().unit(); +} + +Base * +MapIter::base() const +{ + return dynamic_cast(_it.cell().object()); +} + +NeutralObject * +MapIter::neutralObject() const +{ + return dynamic_cast(_it.cell().object()); +} + +Landscape * +MapIter::landscape() const +{ + return _it.cell().landscape(); +} + +const Landscape * +MapInfo::landscape() const +{ + return _cell->landscape(); +} + +const Unit * +MapInfo::unit() const +{ + return _cell->unit(); +} + +const Base * +MapInfo::base() const +{ + return dynamic_cast(_cell->object()); +} + +const NeutralObject * +MapInfo::neutralObject() const +{ + return dynamic_cast(_cell->object()); +} diff --git a/8303/Parfentev_Leonid/lab7/map.hpp b/8303/Parfentev_Leonid/lab7/map.hpp new file mode 100644 index 000000000..d8831d7cf --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/map.hpp @@ -0,0 +1,126 @@ +#ifndef _H_MAP_HPP +#define _H_MAP_HPP + +#include + +#include "rectmap.hpp" +#include "storable.hpp" + +// Map interface doesn’t know about cells -- instead, it only cares +// about certain kinds of Placeables + + +class Map; + +class Unit; +class Base; +class NeutralObject; + +class MapIter { + RectMapIter _it; + + friend class Map; + + MapIter(RectMapIter r) + :_it{r} {} + +public: + static MapIter makeNull() + { + return MapIter{RectMapIter::makeNull()}; + } + + bool operator==(const MapIter &o) const { return _it == o._it; } + bool operator!=(const MapIter &o) const { return _it != o._it; } + + int x() const { return _it.x(); } + int y() const { return _it.y(); } + Vec2 point() const { return _it.point(); } + + bool null() const { return _it.null(); } + bool valid() const { return _it.valid(); } + + void shift(Vec2 dxy) { _it.moveTo(point().shifted(dxy)); } + MapIter shifted(Vec2 dxy) const + { + return MapIter{_it.otherAt(point().shifted(dxy))}; + } + + void moveTo(Vec2 xy) { _it.moveTo(xy); } + MapIter otherAt(Vec2 xy) const + { + return MapIter{_it.otherAt(xy)}; + } + + void advance(int d) { _it.advance(d); } + MapIter advanced(int d) const + { + return MapIter{_it.advanced(d)}; + } + + Unit *unit() const; + Base *base() const; + NeutralObject *neutralObject() const; + Landscape *landscape() const; +}; + +class MapInfo { + const Cell *_cell; + +public: + MapInfo(const Cell *c) + :_cell{c} {} + + const Landscape *landscape() const; + const Unit *unit() const; + const Base *base() const; + const NeutralObject *neutralObject() const; +}; + +class Map: public Storable { + RectMap _rm; + int _units_count = 0; + int _units_max = -1; + + MapIter addPlaceable(Placeable *p, Vec2 pt); + +public: + Map(int w, int h); + + int width() const { return _rm.width(); } + int height() const { return _rm.height(); } + MapIter iterAt(Vec2 pt) { return MapIter{_rm.iterAt(pt)}; } + MapIter iterAt(int x, int y) { return iterAt({x, y}); } + + MapIter begin() { return iterAt(0, 0); } + MapIter end() { return iterAt(0, height()); } + + MapInfo infoAt(Vec2 pt) const; + + MapIter addUnit(Unit *u, Vec2 pt); + Unit *removeUnitAt(Vec2 at); + Unit *removeUnitAt(MapIter iter) + { + return removeUnitAt(iter.point()); + } + + MapIter addBase(Base *b, Vec2 pt); + MapIter addNeutralObject(NeutralObject *n, Vec2 pt); + void setLandscape(Landscape *l, Vec2 pt); + + int maxUnitsCount() const { return _units_max; } + bool setMaxUnitsCount(int x) + { + if (_units_count > x) + return false; + _units_max = x; + return true; + } + int unitsCount() const { return _units_count; } + + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/mediator.cpp b/8303/Parfentev_Leonid/lab7/mediator.cpp new file mode 100644 index 000000000..d7e6e9ff1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/mediator.cpp @@ -0,0 +1,127 @@ +#include "point.hpp" +#include "map.hpp" +#include "event.hpp" +#include "event_types.hpp" +#include "neutral_object.hpp" +#include "base.hpp" +#include "mediator.hpp" + + +MapInfo +Mediator::infoAt(Vec2 pt) +{ + return _map->infoAt(pt); +} + +Vec2 +Mediator::mapSize() +{ + return {_map->width(), + _map->height()}; +} + +bool +Mediator::moveUnitTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit() + || !u->canMove(ito)) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitMoved {u, from}); + return true; +} + +bool +Mediator::attackTo(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (!ito.unit() + || !u->canAttackTo(ito)) { + return false; + } + + auto pos = u->actualPosition(ito); + Unit *t = pos.unit(); + + u->emit(new events::UnitAttacked {u, pos.point(), t}); + + if (t) { + t->emit(new events::UnitWasAttacked {u, t}); + + auto damage_pair = u->baseAttack(pos); + auto damage_spec = t->actualDamage(damage_pair.first, + damage_pair.second); + int dmg = damage_spec.evaluate(); + + t->takeDamage(dmg); + } + + return true; +} + +bool +Mediator::useObject(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + NeutralObject *n = iter.neutralObject(); + + if (!n + || !n->canUse(u)) { + return false; + } + + n->onUse(u, this); + + u->emit(new events::UnitUsedObject {u, n}); + + return true; +} + +bool +Mediator::destroyBase(Unit *u) +{ + auto iter = _map->iterAt(u->position()); + + Base *b = iter.base(); + + if (!b + || !b->becomeDestroyedBy(u)) { + return false; + } + + return true; +} + +bool +Mediator::spawnUnit(Unit *u, Vec2 at) +{ + return !_map->addUnit(u, at).null(); +} + +bool +Mediator::teleportUnit(Unit *u, Vec2 to) +{ + auto ito = _map->iterAt(to); + + if (ito.unit()) { + return false; + } + + Vec2 from = u->position(); + + _map->removeUnitAt(from); + _map->addUnit(u, to); + + u->emit(new events::UnitTeleported {u, from}); + return true; +} diff --git a/8303/Parfentev_Leonid/lab7/mediator.hpp b/8303/Parfentev_Leonid/lab7/mediator.hpp new file mode 100644 index 000000000..31a0d545a --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/mediator.hpp @@ -0,0 +1,32 @@ +#ifndef _H_MEDIATOR_HPP +#define _H_MEDIATOR_HPP + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + + +class Mediator { + Map *_map; + +public: + Mediator(Map *map) + :_map{map} {} + + MapInfo infoAt(Vec2 pt); + + Vec2 mapSize(); + + bool moveUnitTo(Unit *u, Vec2 to); + + bool attackTo(Unit *u, Vec2 to); + + bool useObject(Unit *u); + bool destroyBase(Unit *u); + + bool spawnUnit(Unit *u, Vec2 at); + bool teleportUnit(Unit *u, Vec2 to); +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab7/melee_units.hpp b/8303/Parfentev_Leonid/lab7/melee_units.hpp new file mode 100644 index 000000000..6409e96cb --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/melee_units.hpp @@ -0,0 +1,111 @@ +#ifndef _H_MELEE_UNITS_HPP +#define _H_MELEE_UNITS_HPP + +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class MeleeAttack: public AttackPolicy { + AttackKind _kind; + int _base_damage; + +public: + explicit MeleeAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0) + :_kind{kind}, _base_damage{base_dmg} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + return to.unit() != nullptr + && to.point().adjacent(u->position()); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter) + { + return std::make_pair( + _kind, + int(_base_damage * u->relativeHealth())); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_melee " << static_cast(_kind) + << " " << _base_damage << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicMeleeUnit: public Unit { +public: + BasicMeleeUnit(int speed, + AttackKind attack_kind, + int base_dmg, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new MeleeAttack {attack_kind, base_dmg}, + def, base_health} {} +}; + +namespace units { + class Swordsman: public BasicMeleeUnit { + public: + Swordsman() :BasicMeleeUnit{ + 2, + AttackKind::sword, 40, + DefenseLevelDeco::good_defense_deco( + AttackKind::spear, + DefenseLevelDeco::vulnerability_deco( + AttackKind::cavalry, + new BasicDefense {})), + 100} {} + + UNIT_STORABLE_NAME("u_swordsman"); + }; + + class Spearsman: public BasicMeleeUnit { + public: + Spearsman() :BasicMeleeUnit{ + 2, + AttackKind::spear, 75, + DefenseLevelDeco::good_defense_deco( + AttackKind::cavalry, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + + UNIT_STORABLE_NAME("u_spearsman"); + }; + + class Cavalry: public BasicMeleeUnit { + public: + Cavalry() :BasicMeleeUnit{ + 3, + AttackKind::cavalry, 50, + DefenseLevelDeco::good_defense_deco( + AttackKind::sword, + DefenseLevelDeco::vulnerability_deco( + AttackKind::spear, + new BasicDefense {})), + 75} {} + + UNIT_STORABLE_NAME("u_cavalry"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/neutral_object.hpp b/8303/Parfentev_Leonid/lab7/neutral_object.hpp new file mode 100644 index 000000000..95e11d9c7 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/neutral_object.hpp @@ -0,0 +1,24 @@ +#ifndef _H_NEUTRAL_OBJECT_HPP +#define _H_NEUTRAL_OBJECT_HPP + +#include "placeable.hpp" +#include "unit.hpp" +#include "map.hpp" +#include "mediator.hpp" +#include "storable.hpp" + + +class NeutralObject: public Placeable, + public Storable { +public: + virtual bool canUse(const Unit *) { return true; } + virtual void onUse(Unit *u, Mediator *m) =0; + + // It’s the object’s job to determine whether it was used by the + // leaving unit. + virtual void onLeave(Unit *) {}; + + virtual ~NeutralObject() {}; +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/neutral_object_types.hpp b/8303/Parfentev_Leonid/lab7/neutral_object_types.hpp new file mode 100644 index 000000000..e6fc373c1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/neutral_object_types.hpp @@ -0,0 +1,255 @@ +#ifndef _H_NEUTRAL_OBJECT_TYPES_HPP +#define _H_NEUTRAL_OBJECT_TYPES_HPP + +#include + +#include "neutral_object.hpp" +#include "mediator.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "ranged_units.hpp" +#include "common_policies.hpp" + +#include "storable.hpp" +#include "common_storables.hpp" + + +class ExtendedShootingRange: public NestedAttack { + double _delta; + +public: + explicit ExtendedShootingRange(AttackPolicy *p=nullptr, + double delta=0) + :NestedAttack{p}, _delta{delta} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = to.point().distance(u->position()); + auto *a = dynamic_cast(attackPolicy()); + return dist >= a->minRange() + && dist <= (a->maxRange() + _delta); + } + + virtual MapIter + actualPosition(const Unit *u, MapIter to) override + { + return attackPolicy()->actualPosition(u, to); + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + return attackPolicy()->baseAttack(u, to); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_extended " << _delta << "\n"; + attackPolicy()->store(os); + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + is >> _delta; + return !is.fail() && NestedAttack::restore(is, tab); + } +}; + + + +namespace objects { + + class HealingWell: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *) override + { + u->heal(25); + } + + TRIVIALLY_STORABLE("n_healingwell"); + }; + + class Tower: public NeutralObject { + AttackPolicy *_prev; + ExtendedShootingRange *_cur = nullptr; + + public: + virtual bool + canUse(const Unit *u) override + { + return dynamic_cast(u); + } + + virtual void + onUse(Unit *u, Mediator *) override + { + _prev = u->attackPolicy(); + _cur = new ExtendedShootingRange {_prev, 5}; + u->setAttackPolicy(_cur); + } + + virtual void + onLeave(Unit *u) override + { + if (_cur == nullptr) { + return; + } + if (auto *apc = u->findAttackContainerOf(_cur)) { + apc->setAttackPolicy(_prev); + _cur->setAttackPolicy(nullptr); + delete _cur; + _cur = nullptr; + } + } + + TRIVIALLY_STORABLE("n_tower"); + }; + + class TunnelsEntrance: public NeutralObject { + public: + virtual void + onUse(Unit *u, Mediator *m) override + { + static const int w = 5; + + Vec2 at = u->position(); + + int max_n = 0; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() == nullptr) { + ++max_n; + } + } + } + + std::uniform_int_distribution<> distr {0, max_n-1}; + int n = distr(global_random); + + Vec2 dest; + for (int j = -w; j <= w; ++j) { + for (int i = -w; i <= w; ++i) { + auto iter = at.shifted({i, j}); + if (m->infoAt(iter).unit() != nullptr) { + continue; + } + if (!--n) { + dest = iter; + break; + } + } + } + + m->teleportUnit(u, dest); + } + + TRIVIALLY_STORABLE("n_tunnelentrance"); + }; + + class WeaponSmiths: public NeutralObject { + public: + class UnitFilter: public Storable { + public: + virtual bool + applicable(const Unit *u) =0; + }; + + template + class SimpleUnitFilter: public UnitFilter{ + public: + virtual bool + applicable(const Unit *u) override + { + return dynamic_cast(u); + } + }; + + class MeleeUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_melee"); + }; + + class RangedUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_ranged"); + }; + + class CatapultUnitFilter: + public SimpleUnitFilter { + TRIVIALLY_STORABLE("uf_catapult"); + }; + + private: + double _mul; + UnitFilter *_filter; + + public: + explicit WeaponSmiths(double mul=0, UnitFilter *filter=nullptr) + :_mul{mul}, _filter{filter} {} + + virtual bool + canUse(const Unit *u) override + { + if (_filter + && !_filter->applicable(u)) { + return false; + } + + for (const AttackPolicyContainer *apc = u; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (dynamic_cast(apc)) { + return false; + } + } + + return true; + } + + virtual void + onUse(Unit *u, Mediator *) override + { + auto *prev = u->attackPolicy(); + auto *new_p = new MultiplierAttackPolicy {prev, _mul}; + u->setAttackPolicy(new_p); + } + + virtual void + store(std::ostream &os) const override + { + os << "n_weaponsmiths " << _mul << "\n"; + if (_filter) { + _filter->store(os); + } else { + os << "end\n"; + } + } + + virtual bool + restore(std::istream &is, RestorerTable *tab) + { + Storable *s = tab->restore(is); + + if (auto *uf = dynamic_cast(s)) { + _filter = uf; + } else if (dynamic_cast(s)) { + _filter = nullptr; + delete s; + } else { + delete s; + return false; + } + + return true; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/object_print.cpp b/8303/Parfentev_Leonid/lab7/object_print.cpp new file mode 100644 index 000000000..da59e1054 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/object_print.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "base.hpp" +#include "neutral_object.hpp" +#include "neutral_object_types.hpp" +#include "landscape.hpp" +#include "landscape_types.hpp" +#include "object_print.hpp" + + +#define UNIT_ENTRY(T) {std::type_index{typeid(units::T)}, #T} +static const std::map unit_names { + UNIT_ENTRY(Swordsman), + UNIT_ENTRY(Spearsman), + UNIT_ENTRY(Cavalry), + UNIT_ENTRY(Archer), + UNIT_ENTRY(Slinger), + UNIT_ENTRY(Onager), + UNIT_ENTRY(BoltThrower), +}; +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T) {std::type_index{typeid(landscapes::T)}, #T} +static const std::map landscape_names { + LANDSCAPE_ENTRY(Normal), + LANDSCAPE_ENTRY(Swamp), + LANDSCAPE_ENTRY(Forest), +}; +#undef LANDSCAPE_ENTRY + +#define N_OBJECT_ENTRY(T, n) {std::type_index{typeid(objects::T)}, n} +static const std::map objects_names { + N_OBJECT_ENTRY(HealingWell, "Healing Well"), + N_OBJECT_ENTRY(Tower, "Tower"), + N_OBJECT_ENTRY(TunnelsEntrance, "Tunnels Entrance"), + N_OBJECT_ENTRY(WeaponSmiths, "Weapon Smiths"), +}; +#undef N_OBJECT_ENTRY + + +std::ostream & +operator<<(std::ostream &os, Vec2 pt) +{ + return os << "{" << pt.x() << "," << pt.y() << "}"; +} + +std::ostream & +operator<<(std::ostream &os, const Unit *u) +{ + return os << "a " + << unit_names.at(std::type_index{typeid(*u)}) + << " with " << u->health() << " HP at " + << u->position(); +} + +std::ostream & +operator<<(std::ostream &os, const Landscape *l) +{ + return os << landscape_names.at(std::type_index{typeid(*l)}); +} + +std::ostream & +operator<<(std::ostream &os, const Base *b) +{ + os << "a Base with " << b->unitsCount(); + int m = b->maxUnitsCount(); + if (m >= 0) { + os << "/" << m; + } + + return os << " units at " << b->position(); +} + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n) +{ + return os << "a " + << objects_names.at(std::type_index{typeid(*n)}) + << " at " << n->position(); +} + + + +static const std::map class_chars { +#define UNIT_ENTRY(T, x) {std::type_index{typeid(units::T)}, x} + UNIT_ENTRY(Swordsman, 'S'), + UNIT_ENTRY(Spearsman, 'P'), + UNIT_ENTRY(Cavalry, 'C'), + UNIT_ENTRY(Archer, 'A'), + UNIT_ENTRY(Slinger, 's'), + UNIT_ENTRY(Onager, 'O'), + UNIT_ENTRY(BoltThrower, 'B'), +#undef UNIT_ENTRY + +#define LANDSCAPE_ENTRY(T, x) \ + {std::type_index{typeid(landscapes::T)}, x} + LANDSCAPE_ENTRY(Normal, '.'), + LANDSCAPE_ENTRY(Swamp, '='), + LANDSCAPE_ENTRY(Forest, '*'), +#undef LANDSCAPE_ENTRY +}; + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info) +{ + if (const Unit *u = info.unit()) { + os << class_chars.at(std::type_index{typeid(*u)}); + } else if (info.base()) { + os << "+"; + } else if (info.neutralObject()) { + os << '#'; + } else { + auto *l = info.landscape(); + os << class_chars.at(std::type_index{typeid(*l)}); + } + + return os; +} + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1) +{ + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + displayMapInfo(os, map->infoAt({x, y})); + } + os << "\n"; + } + return os; +} + +std::ostream & +operator<<(std::ostream &os, const Map *map) +{ + return displayMap(os, map, 0, 0, map->width(), map->height()); +} diff --git a/8303/Parfentev_Leonid/lab7/object_print.hpp b/8303/Parfentev_Leonid/lab7/object_print.hpp new file mode 100644 index 000000000..8df10d966 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/object_print.hpp @@ -0,0 +1,40 @@ +#ifndef _H_OBJECT_PRINT_HPP +#define _H_OBJECT_PRINT_HPP + +#include +#include +#include + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" +#include "base.hpp" +#include "landscape.hpp" +#include "neutral_object.hpp" + +std::ostream & +operator<<(std::ostream &os, Vec2 pt); + +std::ostream & +operator<<(std::ostream &os, const Unit *u); + +std::ostream & +operator<<(std::ostream &os, const Landscape *l); + +std::ostream & +operator<<(std::ostream &os, const Base *b); + +std::ostream & +operator<<(std::ostream &os, const NeutralObject *n); + +std::ostream & +displayMapInfo(std::ostream &os, const MapInfo &info); + +std::ostream & +displayMap(std::ostream &os, const Map *map, + int x0, int y0, int x1, int y1); + +std::ostream & +operator<<(std::ostream &os, const Map *map); + +#endif diff --git a/8303/Parfentev_Leonid/lab7/object_w_health.hpp b/8303/Parfentev_Leonid/lab7/object_w_health.hpp new file mode 100644 index 000000000..e9c925221 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/object_w_health.hpp @@ -0,0 +1,43 @@ +#ifndef _H_OBJECT_W_HEALTH_HPP +#define _H_OBJECT_W_HEALTH_HPP + +#include "storable.hpp" + + +class ObjectWithHealth: public Storable { + int _health, _base_health; + +public: + ObjectWithHealth(int base_health) + :_health{base_health}, + _base_health{base_health} {} + + int + health() const { return _health; } + int + baseHealth() const { return _base_health; } + double + relativeHealth() const { return _health / (double)_base_health; } + bool + alive() const { return health() > 0; } + + virtual void + heal(int hp) { _health += hp; } + + virtual void + takeDamage(int dmg) { _health -= dmg; } + + virtual void store(std::ostream &os) const override + { + os << health(); + } + + virtual bool restore(std::istream &is, RestorerTable *) override + { + is >> _health; + return !is.fail(); + } +}; + + +#endif diff --git a/8303/Parfentev_Leonid/lab7/pathfinder.cpp b/8303/Parfentev_Leonid/lab7/pathfinder.cpp new file mode 100644 index 000000000..865f34de2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/pathfinder.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "map.hpp" +#include "point.hpp" +#include "pathfinder.hpp" + + +PathFinder::Pt2 +PathFinder::Pt2::toDirection(int dir) const +{ + // assert(dir >= 0 && dir < 8); + + int d1 = (dir + 1) % 8; + int dx = (d1 % 4 == 3) ? 0 : (d1 < 4) ? 1 : -1, + dy = (dir % 4 == 0) ? 0 : (dir < 4) ? 1 : -1; + + return Pt2{pt.shifted({dx, dy}), depth + 1}; +} + +bool +PathFinder::run() +{ + Vec2 start_pt = _start.point(); + + while (!_frontier.empty()) { + Pt2 current = _frontier.front(); + _frontier.pop(); + + if (start_pt.shifted(current.pt) == _end) + return true; + + if (_max >= 0 + && current.depth >= _max) + continue; + + Vec2WithCmp cur_v2wc {current.pt}; + auto iter = _dirs.find(cur_v2wc); + if (iter == _dirs.end()) + _dirs[cur_v2wc] = -1; + + for (int i = 0; i < 8; ++i) { + Pt2 shifted_delta = current.toDirection(i); + + Vec2WithCmp sh_v2wc {shifted_delta.pt}; + auto iter = _dirs.find(sh_v2wc); + if (iter != _dirs.end()) + continue; + + MapIter shifted = _start.shifted(shifted_delta.pt); + if (!shifted.valid() + || shifted.unit()) + continue; + + _frontier.push(shifted_delta); + } + } + + return false; +} diff --git a/8303/Parfentev_Leonid/lab7/pathfinder.hpp b/8303/Parfentev_Leonid/lab7/pathfinder.hpp new file mode 100644 index 000000000..d4c614aae --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/pathfinder.hpp @@ -0,0 +1,56 @@ +#ifndef _H_PATHFINDER_HPP +#define _H_PATHFINDER_HPP + +#include +#include + +#include "point.hpp" +#include "map.hpp" + + +class PathFinder { + MapIter _start; + Vec2 _end; + int _max; + + struct Vec2WithCmp { + Vec2 v; + + bool operator<(Vec2WithCmp o) const + { + if (v.y() == o.v.y()) + return v.x() < o.v.x(); + return v.y() < o.v.y(); + } + }; + + struct Pt2 { + Vec2 pt; + int depth; + + static Pt2 zero() { return {Vec2{0, 0}, 0}; } + + Pt2(Vec2 pt, int depth=0) + :pt{pt}, depth{depth} {} + + Pt2 toDirection(int dir) const; + + bool pt_equal(Vec2 pt2) + { + return pt == pt2; + } + }; + + std::map _dirs {}; + std::queue _frontier {{Pt2::zero()}}; + +public: + PathFinder(MapIter from, MapIter to, int max_steps) + :_start{from}, + _end{to.point()}, + _max{max_steps} {} + + bool run(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/placeable.hpp b/8303/Parfentev_Leonid/lab7/placeable.hpp new file mode 100644 index 000000000..bd7f0ef74 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/placeable.hpp @@ -0,0 +1,36 @@ +#ifndef _H_PLACEABLE_HPP +#define _H_PLACEABLE_HPP + +#include "point.hpp" + +class Placeable { + bool _placed = false; + Vec2 _pos; + +public: + bool + hasPosition() const { return _placed; } + + const Vec2 & + position() const + { + return _pos; + } + + void + setPosition(const Vec2 &pos) + { + _pos = pos; + _placed = true; + } + + void + unsetPosition() + { + _placed = false; + } + + virtual ~Placeable() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/player.hpp b/8303/Parfentev_Leonid/lab7/player.hpp new file mode 100644 index 000000000..58c15829d --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/player.hpp @@ -0,0 +1,44 @@ +#ifndef _H_PLAYER_HPP +#define _H_PLAYER_HPP + +#include +#include + +#include "mediator.hpp" +#include "base.hpp" +#include "event.hpp" +#include "storable.hpp" + + +class Game; + +class Player: public EventEmitter, + public Storable { + std::string _name; + +public: + explicit Player(std::string name="") + :_name{std::move(name)} {} + + const std::string &name() const { return _name; } + + virtual bool takeTurn(const Game *g, Mediator *m, Base *b) =0; + + virtual void + store(std::ostream &os) const + { + os << name() << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + std::ws(is); + std::getline(is, _name); + return !is.fail(); + } + + virtual ~Player() {} +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/point.cpp b/8303/Parfentev_Leonid/lab7/point.cpp new file mode 100644 index 000000000..bce7b3325 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/point.cpp @@ -0,0 +1,29 @@ +#include + +#include "point.hpp" + +double +Vec2::length() const +{ + return sqrt(_x*_x + _y*_y); +} + +double +Vec2::distance(const Vec2 &pt) const +{ + return delta(pt).length(); +} + +bool +Vec2::unit() const +{ + return (_x || _y) + && abs(_x) <= 1 + && abs(_y) <= 1; +} + +bool +Vec2::adjacent(const Vec2 &pt) const +{ + return delta(pt).unit(); +} diff --git a/8303/Parfentev_Leonid/lab7/point.hpp b/8303/Parfentev_Leonid/lab7/point.hpp new file mode 100644 index 000000000..8eec01d76 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/point.hpp @@ -0,0 +1,40 @@ +#ifndef _H_POINT_HPP +#define _H_POINT_HPP + +class Vec2 { + int _x, _y; + +public: + Vec2() :Vec2{0, 0} {} + Vec2(int x, int y) :_x{x}, _y{y} {} + + int x() const { return _x; } + int y() const { return _y; } + + bool operator==(const Vec2 &pt) const + { + return _x == pt._x && _y == pt._y; + } + bool operator!=(const Vec2 &pt) const + { + return !(*this == pt); + } + + Vec2 delta(const Vec2 &o) const + { + return Vec2{_x - o._x, _y - o._y}; + } + + double length() const; + double distance(const Vec2 &pt) const; + + bool unit() const; + bool adjacent(const Vec2 &pt) const; + + Vec2 shifted(const Vec2 &dxy) const + { + return Vec2{_x + dxy._x, _y + dxy._y}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/ranged_units.hpp b/8303/Parfentev_Leonid/lab7/ranged_units.hpp new file mode 100644 index 000000000..2a41e3ecb --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/ranged_units.hpp @@ -0,0 +1,118 @@ +#ifndef _H_RANGED_UNITS_HPP +#define _H_RANGED_UNITS_HPP + +#include +#include + +#include "point.hpp" +#include "unit.hpp" +#include "common_policies.hpp" + + +class RangedAttack: public AttackPolicy { +protected: + AttackKind _kind; + int _base_damage; + double _min_distance, _max_distance; + double _dist_pow; + + static double + distance(const Unit *u, MapIter to) + { + return to.point().distance(u->position()); + } + +public: + double minRange() const { return _min_distance; } + double maxRange() const { return _max_distance; } + + explicit RangedAttack(AttackKind kind=AttackKind::invalid, + int base_dmg=0, + double min_dist=0, + double max_dist=0, + double dist_pow=0) + :_kind{kind}, + _base_damage{base_dmg}, + _min_distance{min_dist}, + _max_distance{max_dist}, + _dist_pow{dist_pow} {} + + virtual bool + canAttackTo(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return dist >= _min_distance + && dist <= _max_distance; + } + + virtual std::pair + baseAttack(const Unit *u, MapIter to) override + { + double dist = distance(u, to); + return std::make_pair( + _kind, + int(_base_damage + * u->relativeHealth() + / pow(dist, _dist_pow))); + } + + virtual void + store(std::ostream &os) const override + { + os << "ap_ranged " << static_cast(_kind) + << " " << _base_damage << " " << _min_distance + << " " << _max_distance << " " + << _dist_pow << "\n"; + } + + virtual bool + restore(std::istream &is, RestorerTable *) + { + int x; + is >> x >> _base_damage >> _min_distance + >> _max_distance >> _dist_pow; + _kind = static_cast(x); + return !is.fail(); + } +}; + +class BasicRangedUnit: public Unit { +public: + BasicRangedUnit(int speed, + AttackKind attack_kind, + int base_dmg, + double max_dist, + double dist_pow, + DefensePolicy *def, + int base_health) + :Unit{new BasicMovement {speed}, + new RangedAttack {attack_kind, base_dmg, + 1., max_dist, dist_pow}, + def, base_health} {} +}; + +namespace units { + class Archer: public BasicRangedUnit { + public: + Archer() :BasicRangedUnit{ + 2, + AttackKind::arrow, 50, 5., .20, + new BasicDefense {0.9}, + 40} {} + + UNIT_STORABLE_NAME("u_archer"); + }; + + class Slinger: public BasicRangedUnit { + public: + Slinger() :BasicRangedUnit{ + 2, + AttackKind::stone, 60, 3., .30, + new BasicDefense {.09}, + 50} {} + + UNIT_STORABLE_NAME("u_slinger"); + }; +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/rectmap.cpp b/8303/Parfentev_Leonid/lab7/rectmap.cpp new file mode 100644 index 000000000..b82667c40 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/rectmap.cpp @@ -0,0 +1,55 @@ +#include "rectmap.hpp" + +#include "unit.hpp" + +Cell::~Cell() +{ + delete _u; + delete _obj; + delete _l; +} + +bool +RectMapIter::valid() const +{ + return x() >= 0 + && x() < _map->width() + && y() >= 0 + && y() < _map->height(); +} + +Cell & +RectMapIter::cell() const +{ + return _map->at(point()); +} + +void +RectMapIter::moveTo(Vec2 xy) +{ + _pt = xy; +} + +RectMapIter +RectMapIter::otherAt(Vec2 xy) const +{ + RectMapIter other = *this; + other.moveTo(xy); + return other; +} + +void +RectMapIter::advance(int d) +{ + int nx = x() + d, + w = _map->width(); + _pt = Vec2{nx % w, y() + nx / w}; +} + +RectMapIter +RectMapIter::advanced(int d) const +{ + RectMapIter other = *this; + other.advance(d); + return other; +} diff --git a/8303/Parfentev_Leonid/lab7/rectmap.hpp b/8303/Parfentev_Leonid/lab7/rectmap.hpp new file mode 100644 index 000000000..a3119ffb1 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/rectmap.hpp @@ -0,0 +1,99 @@ +#ifndef _H_RECTMAP_HPP +#define _H_RECTMAP_HPP + +#include "point.hpp" +#include "placeable.hpp" +#include "landscape.hpp" + + +class Unit; + +class Cell { + Landscape *_l = nullptr; + Unit *_u = nullptr; + Placeable *_obj = nullptr; + +public: + Cell() {} + + ~Cell(); + + Landscape *landscape() const { return _l; } + void setLandscape(Landscape *l) + { + delete _l; + _l = l; + } + + Unit *unit() const { return _u; } + void setUnit(Unit *u) { _u = u; } + + Placeable *object() const { return _obj; } + void setObject(Placeable *p) { _obj = p; } +}; + +class RectMap; + +class RectMapIter { + RectMap *_map; + Vec2 _pt; + +public: + RectMapIter(RectMap *map, Vec2 pt) + :_map{map}, _pt{pt} {} + RectMapIter(RectMap *map, int x, int y) + :_map{map}, _pt{x, y} {} + + static RectMapIter makeNull() { return {nullptr, {0, 0}}; } + + bool operator==(const RectMapIter &o) const + { + return _map == o._map + && _pt == o._pt; + } + bool operator!=(const RectMapIter &o) const + { + return !(*this == o); + } + + int x() const { return _pt.x(); } + int y() const { return _pt.y(); } + Vec2 point() const { return _pt; } + + Cell &cell() const; + + bool null() const { return _map == nullptr; } + bool valid() const; + + void moveTo(Vec2 xy); + RectMapIter otherAt(Vec2 xy) const; + + void advance(int d); + RectMapIter advanced(int d) const; +}; + +class RectMap { + const int _w, _h; + Cell * const _storage; + +public: + RectMap(int w, int h) + :_w{w}, _h{h}, _storage{new Cell [w * h]} {} + + int width() const { return _w; } + int height() const { return _h; } + + Cell &at(Vec2 pt) { return _storage[pt.x() + pt.y()*_w]; } + const Cell &at(Vec2 pt) const + { + return _storage[pt.x() + pt.y()*_w]; + } + RectMapIter iterAt(Vec2 pt) { return RectMapIter{this, pt}; } + + ~RectMap() + { + delete[] _storage; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/report.pdf b/8303/Parfentev_Leonid/lab7/report.pdf new file mode 100644 index 000000000..e3919e8f8 Binary files /dev/null and b/8303/Parfentev_Leonid/lab7/report.pdf differ diff --git a/8303/Parfentev_Leonid/lab7/restorers.hpp b/8303/Parfentev_Leonid/lab7/restorers.hpp new file mode 100644 index 000000000..6fe67c4e2 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/restorers.hpp @@ -0,0 +1,61 @@ +#ifndef _H_RESTORERS_HPP +#define _H_RESTORERS_HPP + +#include "storable.hpp" + +#include "point.hpp" +#include "map.hpp" +#include "unit.hpp" + +#include "base.hpp" +#include "game.hpp" +#include "player.hpp" + + +template +class SimpleRestorer: public Restorer { +public: + virtual Storable * + restore(std::istream &, + RestorerTable *) const override + { + return new T {}; + } +}; + + +namespace restorers { + + class GameRestorer: public Restorer { + public: + virtual Storable * + restore(std::istream &is, + RestorerTable *tab) const override + { + Storable *s = tab->restore(is); + Map *map = dynamic_cast(s); + if (!map) { + delete s; + return nullptr; + } + + return new Game {map}; + } + }; + + class MapRestorer: public Restorer { + public: + virtual Storable * + restore(std::istream &is, + RestorerTable *) const override + { + int w, h; + is >> w >> h; + + return new Map {w, h}; + } + }; + +} + +#endif diff --git a/8303/Parfentev_Leonid/lab7/storable.cpp b/8303/Parfentev_Leonid/lab7/storable.cpp new file mode 100644 index 000000000..2bd50e407 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/storable.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +#include "storable.hpp" +#include "restorers.hpp" +#include "common_storables.hpp" + +#include "melee_units.hpp" +#include "ranged_units.hpp" +#include "catapult_units.hpp" +#include "landscape_types.hpp" +#include "neutral_object_types.hpp" + +#include "factory_table.hpp" +#include "iostream_player.hpp" + +#include "game_rules.hpp" + + +RestorerTable * +RestorerTable::defaultTable() +{ + return new RestorerTable {{ + +{"end", new SimpleRestorer {}}, +{"coords", new SimpleRestorer {}}, +{"index", new SimpleRestorer {}}, +{"at", new SimpleRestorer {}}, + +{"game", new restorers::GameRestorer {}}, +{"map", new restorers::MapRestorer {}}, + +{"base", new SimpleRestorer {}}, +{"base_w_countdown", new SimpleRestorer {}}, + +{"iostream_player", new SimpleRestorer {}}, + +{"l_normal", new SimpleRestorer {}}, +{"l_swamp", new SimpleRestorer {}}, +{"l_forest", new SimpleRestorer {}}, + +{"u_swordsman", new SimpleRestorer {}}, +{"u_spearsman", new SimpleRestorer {}}, +{"u_cavalry", new SimpleRestorer {}}, + +{"u_archer", new SimpleRestorer {}}, +{"u_slinger", new SimpleRestorer {}}, + +{"u_onager", new SimpleRestorer {}}, +{"u_boltthrower", new SimpleRestorer {}}, + +{"n_healingwell", new SimpleRestorer {}}, +{"n_tower", new SimpleRestorer {}}, +{"n_tunnelentrance", new SimpleRestorer {}}, +{"n_weaponsmiths", new SimpleRestorer {}}, + +{"uf_melee", + new SimpleRestorer {}}, +{"uf_ranged", + new SimpleRestorer {}}, +{"uf_catapult", + new SimpleRestorer {}}, + +{"mp_basic", new SimpleRestorer {}}, +{"mp_modifyiing", new SimpleRestorer {}}, + +{"dp_basic", new SimpleRestorer {}}, +{"dp_level_deco", new SimpleRestorer {}}, +{"dp_multiplier", new SimpleRestorer {}}, + +{"ap_forbidden", new SimpleRestorer {}}, +{"ap_multiplier", new SimpleRestorer {}}, +{"ap_extended", new SimpleRestorer {}}, + +{"ap_melee", new SimpleRestorer {}}, +{"ap_ranged", new SimpleRestorer {}}, +{"ap_catapult", new SimpleRestorer {}}, + + }}; +} diff --git a/8303/Parfentev_Leonid/lab7/storable.hpp b/8303/Parfentev_Leonid/lab7/storable.hpp new file mode 100644 index 000000000..d152b700c --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/storable.hpp @@ -0,0 +1,70 @@ +#ifndef _STORABLE_HPP +#define _STORABLE_HPP + +#include +#include +#include +#include + + +class RestorerTable; + +class Storable { +public: + virtual void store(std::ostream &os) const =0; + virtual bool restore(std::istream &, + RestorerTable *) { return true; }; + + virtual ~Storable() {} +}; + +#define TRIVIALLY_STORABLE(keyword) \ + public: \ + virtual void \ + store(std::ostream &os) const override \ + { \ + os << keyword "\n"; \ + } + + + +class Restorer { +public: + virtual Storable *restore(std::istream &is, + RestorerTable *tab) const =0; +}; + +class RestorerTable { + std::map _tab; + +public: + RestorerTable(std::map m) + :_tab{std::move(m)} {} + + Storable * + restore(std::istream &is) + { + std::string n; + is >> n; + + auto iter = _tab.find(n); + if (iter == _tab.end()) { + return nullptr; + } + + Storable *s = iter->second->restore(is, this); + if (!s) { + return nullptr; + } + + if (!s->restore(is, this)) { + delete s; + return nullptr; + } + return s; + } + + static RestorerTable *defaultTable(); +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/unit.cpp b/8303/Parfentev_Leonid/lab7/unit.cpp new file mode 100644 index 000000000..39098695f --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/unit.cpp @@ -0,0 +1,48 @@ +#include + +#include "storable.hpp" +#include "common_storables.hpp" +#include "unit.hpp" + + +std::default_random_engine global_random {}; + +void +Unit::store(std::ostream &os) const +{ + os << health() << "\n"; + + movePolicy()->store(os); + defensePolicy()->store(os); + attackPolicy()->store(os); + + os << "end\n"; +} + +bool +Unit::restore(std::istream &is, RestorerTable *tab) +{ + if (!ObjectWithHealth::restore(is, tab)) { + return false; + } + + for (;;) { + Storable *s = tab->restore(is); + + if (auto *mp = dynamic_cast(s)) { + setMovePolicy(mp); + } else if (auto *dp = dynamic_cast(s)) { + setDefensePolicy(dp); + } else if (auto *ap = dynamic_cast(s)) { + setAttackPolicy(ap); + } else if (dynamic_cast(s)) { + delete s; + break; + } else { + delete s; + return false; + } + } + + return true; +} diff --git a/8303/Parfentev_Leonid/lab7/unit.hpp b/8303/Parfentev_Leonid/lab7/unit.hpp new file mode 100644 index 000000000..93f745061 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/unit.hpp @@ -0,0 +1,271 @@ +#ifndef _H_UNIT_HPP +#define _H_UNIT_HPP + +#include +#include +#include + +#include "event.hpp" +#include "event_types.hpp" +#include "object_w_health.hpp" +#include "map.hpp" + + +extern std::default_random_engine global_random; + + +class MovePolicy: public Storable { +public: + virtual bool canMove(const Unit *u, MapIter to) =0; + virtual ~MovePolicy() {} +}; + +enum class AttackKind { + invalid, sword, spear, cavalry, arrow, stone, rock, bolt, +}; + +// NOTE: can’t do area damage +class AttackPolicy: public Storable { +public: + virtual bool canAttackTo(const Unit *u, MapIter to) =0; + + virtual MapIter actualPosition(const Unit *, MapIter to) + { + return to; + } + + // returns kind and base damage + virtual std::pair + baseAttack(const Unit *u, MapIter to) =0; + + virtual ~AttackPolicy() {} +}; + +struct DamageSpec { + int base_damage, damage_spread; + + void + scale(double k) + { + base_damage *= k; + damage_spread *= k; + } + + DamageSpec + scaled(double k) const + { + auto ds = *this; + ds.scale(k); + return ds; + } + + int evaluate() const + { + std::uniform_int_distribution<> + dist {-damage_spread, damage_spread}; + + return base_damage + dist(global_random); + } +}; + +class DefensePolicy: public Storable { +protected: + static DamageSpec + make_spec(double base, double spread) + { + return DamageSpec{(int)base, (int)spread}; + } + + static DamageSpec + defense_level(double k, int dmg) + { + return make_spec(round(1.0*dmg/k), + round(0.25*dmg/k)); + } + + static DamageSpec + normal_defense(double dmg) + { + return defense_level(1.0, dmg); + } + +public: + // returns base damage and spread + virtual DamageSpec + actualDamage(const Unit *u, AttackKind kind, int base) =0; + + virtual ~DefensePolicy() {} +}; + +class MovePolicyContainer { + MovePolicy *_mp; + +public: + MovePolicyContainer(MovePolicy *mp) :_mp{mp} {} + + MovePolicy *movePolicy() const { return _mp; } + void setMovePolicy(MovePolicy *mp) + { + _mp = mp; + } + virtual ~MovePolicyContainer() { delete _mp; } + + MovePolicyContainer * + findMoveContainerOf(const MovePolicy *mp) + { + for (MovePolicyContainer *mpc = this; mpc; + mpc = dynamic_cast( + mpc->movePolicy())) { + if (mpc->movePolicy() == mp) { + return mpc; + } + } + return nullptr; + } +}; + +class DefensePolicyContainer { + DefensePolicy *_dp; + +public: + DefensePolicyContainer(DefensePolicy *dp) :_dp{dp} {} + + DefensePolicy *defensePolicy() const { return _dp; } + void setDefensePolicy(DefensePolicy *dp) + { + _dp = dp; + } + virtual ~DefensePolicyContainer() { delete _dp; } + + DefensePolicyContainer * + findDefenseContainerOf(const DefensePolicy *dp) + { + for (DefensePolicyContainer *dpc = this; dpc; + dpc = dynamic_cast( + dpc->defensePolicy())) { + if (dpc->defensePolicy() == dp) { + return dpc; + } + } + return nullptr; + } +}; + +class AttackPolicyContainer { + AttackPolicy *_ap; + +public: + AttackPolicyContainer(AttackPolicy *ap) :_ap{ap} {} + + AttackPolicy *attackPolicy() const { return _ap; } + void setAttackPolicy(AttackPolicy *ap) + { + _ap = ap; + } + virtual ~AttackPolicyContainer() { delete _ap; } + + AttackPolicyContainer * + findAttackContainerOf(const AttackPolicy *ap) + { + for (AttackPolicyContainer *apc = this; apc; + apc = dynamic_cast( + apc->attackPolicy())) { + if (apc->attackPolicy() == ap) { + return apc; + } + } + return nullptr; + } +}; + +class Unit: public Placeable, + public ObjectWithHealth, + public EventEmitter, + public MovePolicyContainer, + public DefensePolicyContainer, + public AttackPolicyContainer { +public: + Unit(MovePolicy *move, + AttackPolicy *attack, + DefensePolicy *defense, + int base_health) + :ObjectWithHealth{base_health}, + MovePolicyContainer{move}, + DefensePolicyContainer{defense}, + AttackPolicyContainer{attack} {} + + virtual void + heal(int hp) + { + emit(new events::UnitGetsHealed {this, hp}); + ObjectWithHealth::heal(hp); + } + + virtual void + takeDamage(int dmg) + { + if (!alive()) { + return; + } + + emit(new events::UnitTakesDamage {this, dmg}); + + ObjectWithHealth::takeDamage(dmg); + + if (!alive()) { + emit(new events::UnitDeath {this}); + } + } + + bool + canMove(MapIter to) const + { + return movePolicy()->canMove(this, to); + } + + bool + canAttackTo(MapIter to) const + { + return attackPolicy()->canAttackTo(this, to); + } + + MapIter + actualPosition(MapIter to) const + { + return attackPolicy()->actualPosition(this, to); + } + + std::pair + baseAttack(MapIter to) const + { + return attackPolicy()->baseAttack(this, to); + } + + DamageSpec + actualDamage(AttackKind kind, int base) const + { + return defensePolicy()->actualDamage(this, kind, base); + } + + // override to add type symbol! + virtual void store(std::ostream &os) const override; + virtual bool restore(std::istream &is, + RestorerTable *tab) override; + + virtual ~Unit() override + { + if (alive()) { + emit(new events::UnitLiveDeleted {this}); + } + } +}; + +#define UNIT_STORABLE_NAME(n) \ + virtual void \ + store(std::ostream &os) const override \ + { \ + os << n "\n"; \ + Unit::store(os); \ + } + +#endif diff --git a/8303/Parfentev_Leonid/lab7/unit_factory.hpp b/8303/Parfentev_Leonid/lab7/unit_factory.hpp new file mode 100644 index 000000000..5f1f475bd --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/unit_factory.hpp @@ -0,0 +1,24 @@ +#ifndef _H_UNIT_FACTORY_HPP +#define _H_UNIT_FACTORY_HPP + +#include "unit.hpp" + + +class UnitFactory { +public: + virtual Unit *create() const =0; + + virtual ~UnitFactory() {} +}; + +template +class SimpleUnitFactory: public UnitFactory { +public: + virtual Unit * + create() const override + { + return new U {}; + } +}; + +#endif diff --git a/8303/Parfentev_Leonid/lab7/zombie_collector.hpp b/8303/Parfentev_Leonid/lab7/zombie_collector.hpp new file mode 100644 index 000000000..9de5f6981 --- /dev/null +++ b/8303/Parfentev_Leonid/lab7/zombie_collector.hpp @@ -0,0 +1,36 @@ +#ifndef _H_ZOMBIE_COLLECTOR_HPP +#define _H_ZOMBIE_COLLECTOR_HPP + +#include + +#include "unit.hpp" +#include "event.hpp" +#include "map.hpp" + + +class ZombieCollector: public EventListener { + std::vector _units {}; + +public: + virtual void + handle(Event *e) override + { + if (auto *ee = dynamic_cast(e)) { + return handle(ee->event()); + } + if (auto *ee = dynamic_cast(e)) { + _units.push_back(ee->unit()); + } + } + + void + collect(Map *m) + { + for (auto *unit: _units) { + delete m->removeUnitAt(unit->position()); + } + _units.clear(); + } +}; + +#endif