diff --git a/.gitignore b/.gitignore index 01f9cb9..bdb48a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ -.vscode/ \ No newline at end of file +.vscode/ +config.txt \ No newline at end of file diff --git a/README.md b/README.md index b50441f..f46d905 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ make * `unitHp` - starting hit points of every new unit [default: 100] * `unitDamage` - how much damage do units deal on every attack [default: 10] * `allowedNameCharacters` - string of characters that can be used in player names [default: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_] +* `maxResourceSpawn` - amount of times server will try to spawn a resource in one game tick / maximum amount of resources that can spawn in one tick [default: 1] +* `resourceChance` - chance of resource spawning in one attempt [default: 0.1] ### Example: @@ -54,6 +56,8 @@ boardY 5 resourceHp 20 unitHp 20 unitDamage 5 +maxResourceSpawn 2 +resourceChance 0.2 ``` Pair can appear in any order. If pair not listed default value will be used. @@ -70,9 +74,9 @@ Some messages consist of only type character others contain more data. - `n` `` `\n` - set name or rename self - `j` - request join (player will be sent to game room or queue) - `q` - request quit (player will be removed from game room or queue, can still rejoin with `j`) -- `m` `` ` ` `` ` ` `` ` ` `` `\n` - request unit to move (`` are coordinates of the unit, `` designate destination) -- `a` `` ` ` `` ` ` `` ` ` `` `\n` - request unit to attack another unit (`` are coordinates of the unit, `` coordinates of the target unit) (possible to attack own units) -- `d` `` ` ` `` `\n` - request unit to mine the resource (`` are coordinates of the unit) (unit can only mine resource that it is standing on) +- `m` `` ` ` `` ` ` `` `\n` - request unit of id `` to move to field of coordinates: `` +- `a` `` ` ` `` `\n` - request unit to attack another unit (`` is id of the controlled unit, `` id id of the target unit) (possible to attack own units) +- `d` `` `\n` - request unit to mine the resource (`` is an id of the controlled unit) (unit can only mine resource that it is standing on) ### From server @@ -80,16 +84,16 @@ Some messages consist of only type character others contain more data. - `j` `` `\n` - new player has joined the game room - `l` `` `\n` - player `` has either left or lost the game - `m` `` ` ` `` ` ` `` `\n` - unit of id `` has moved to `;` -- `a` `` ` ` `` `\n` - unit of id `` attacked unit of id `` -- `d` `` `\n` - unit of id `` mined a resource +- `a` `` ` ` `` ` ` `` `\n` - unit of id `` attacked unit of id `` and target unit has now `` hp left +- `d` `` ` ` `` `\n` - unit of id `` mined a resource and resource has now `` hp left - `u` `` ` ` `` ` ` `` ` ` `` - player `` has aquired unit of id `` on field `;` - `f` `` ` ` `` ` ` `` `\n` - new resource spawned on field `;` - `t` `\n` - sent to all players in game room in regular time intervals, marks the and of each tick and a start of the next one - `q` `\n` - player was sent to queue (in response to: `j`) - `y` `\n` - client request accepted (in response to: `n`) - `n` `\n` - client request denied (in response to: `j` or `n`) -- `L` `\n` - client lost the game (and was moved out of game room) -- `W` `\n` - client won the game (and was moved out of game room) +- `L` `` `\n` - client lost the game (and was moved out of game room), `` is the name of the player who won +- `W` `` `\n` - client won the game (and was moved out of game room), `` is the name of the player who won ### Board state message @@ -130,4 +134,5 @@ Numbers are represented as strings of characters (97 ---> "97" not 'a'). **6:** client can send multiple: `m` `a` `d` messages **6:** if client sends `q` => go to step **4** **6:** if server sends `W` or `L` => go to step **4** +**6:** if server sends `l` with clients name => go to step **4** **7:** server sends `t` => go to step **6** diff --git a/config.txt b/config.txt deleted file mode 100644 index e5e951a..0000000 --- a/config.txt +++ /dev/null @@ -1,8 +0,0 @@ -millis 3000 -maxPlayers 2 -unitsToWin 2 -startResources 8 -boardX 5 -boardY 5 -resourceHp 20 -unitHp 20 diff --git a/src/msg/addressUnit.cpp b/src/msg/addressUnit.cpp index 19af8fe..14709a3 100644 --- a/src/msg/addressUnit.cpp +++ b/src/msg/addressUnit.cpp @@ -14,4 +14,9 @@ rts::unit* message::addressUnitByCoordinates::getUnit(const rts::game* g) const{ message::addressUnitById::addressUnitById( unsigned int id - ) : addressUnit(), unitId(id) {} \ No newline at end of file + ) : addressUnit(), unitId(id) {} + +rts::unit* message::addressUnitById::getUnit(const rts::game* g) const{ + if (g->unitsById.find(unitId) == g->unitsById.end()) return nullptr; + return g->unitsById.at(unitId); +} \ No newline at end of file diff --git a/src/msg/addressUnit.hpp b/src/msg/addressUnit.hpp index 80f2085..446691d 100644 --- a/src/msg/addressUnit.hpp +++ b/src/msg/addressUnit.hpp @@ -9,9 +9,10 @@ namespace rts { namespace message { - class addressUnit : public base { + class addressUnit { public: virtual rts::unit* getUnit(const rts::game* g) const = 0; + virtual ~addressUnit() = default; }; class addressUnitByCoordinates : public addressUnit { @@ -27,6 +28,6 @@ namespace message { const unsigned int unitId; addressUnitById(unsigned int id); - // rts::unit* getUnit(const rts::game* g) const override; ... TO DO ... + rts::unit* getUnit(const rts::game* g) const override; }; } \ No newline at end of file diff --git a/src/msg/handler.cpp b/src/msg/handler.cpp index e3207cb..1c2981d 100644 --- a/src/msg/handler.cpp +++ b/src/msg/handler.cpp @@ -104,42 +104,41 @@ void message::handler::init(){ }; messageProcessors['m'] = [](message::handler* mh){ - int sx, sy, dx, dy; + int a, b, c; auto it = mh->buffer.begin(); - bool success = mh->tryGetInt(sx, it, ' '); - if (success) success &= mh->tryGetInt(sy, it, ' '); - if (success) success &= mh->tryGetInt(dx, it, ' '); - if (success) success &= mh->tryGetInt(dy, it, '\n'); - if (success) { - message::move msg(dx, dy, sx, sy); - mh->onNewMessage(&msg); - mh->buffer.erase(mh->buffer.begin(), it); - mh->msgType = '?'; + + if (mh->tryGetInt(a, it, ' ')){ + if (mh->tryGetInt(b, it, ' ')){ + if (mh->tryGetInt(c, it, '\n')) { + message::move msg(a, b, c); + mh->onNewMessage(&msg); + mh->buffer.erase(mh->buffer.begin(), it); + mh->msgType = '?'; + } + } } }; messageProcessors['a'] = [](message::handler* mh){ - int sx, sy, dx, dy; + int a, b; auto it = mh->buffer.begin(); - bool success = mh->tryGetInt(sx, it, ' '); - if (success) success &= mh->tryGetInt(sy, it, ' '); - if (success) success &= mh->tryGetInt(dx, it, ' '); - if (success) success &= mh->tryGetInt(dy, it, '\n'); - if (success) { - message::attack msg(dx, dy, sx, sy); - mh->onNewMessage(&msg); - mh->buffer.erase(mh->buffer.begin(), it); - mh->msgType = '?'; + + if (mh->tryGetInt(a, it, ' ')){ + if (mh->tryGetInt(b, it, '\n')){ + message::attack msg(a, b); + mh->onNewMessage(&msg); + mh->buffer.erase(mh->buffer.begin(), it); + mh->msgType = '?'; + } } }; messageProcessors['d'] = [](message::handler* mh){ - int sx, sy; + int a; auto it = mh->buffer.begin(); - bool success = mh->tryGetInt(sx, it, ' '); - if (success) success &= mh->tryGetInt(sy, it, '\n'); - if (success) { - message::mine msg(sx, sy); + + if (mh->tryGetInt(a, it, '\n')){ + message::mine msg(a); mh->onNewMessage(&msg); mh->buffer.erase(mh->buffer.begin(), it); mh->msgType = '?'; diff --git a/src/msg/unitCommands.cpp b/src/msg/unitCommands.cpp new file mode 100644 index 0000000..8554141 --- /dev/null +++ b/src/msg/unitCommands.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +rts::unit* message::command::getUnit(rts::game* game) const{ + return source->getUnit(game); +} + +message::command::command( + unsigned int sourceId +) : source(new addressUnitById(sourceId)) {} + +message::command::command( + unsigned int sourceX, unsigned int sourceY +) : source(new addressUnitByCoordinates(sourceX, sourceY)) {} + +message::command::~command() { + delete source; +} + + +rts::field* message::move::getDestinationField(rts::game* game) const { + return game->_board.getField(destX, destY); +} + +message::move::move( + unsigned int sx, unsigned int sy, + unsigned int dx, unsigned int dy +) : command(sx, sy), destX(dx), destY(dy) {} + +message::move::move( + unsigned int si, + unsigned int dx, unsigned int dy +) : command(si), destX(dx), destY(dy) {} + + +rts::unit* message::attack::getTarget(rts::game* game) const { + return target->getUnit(game); +} + +message::attack::attack( + unsigned int sx, unsigned int sy, + unsigned int tx, unsigned int ty +) : command(sx, sy), target(new addressUnitByCoordinates(tx, ty)) {} + +message::attack::attack( + unsigned int si, + unsigned int ti +) : command(si), target(new addressUnitById(ti)) {} + +message::attack::~attack(){ + delete target; +} + + +message::mine::mine( + unsigned int sx, unsigned int sy +) : command(sx, sy) {} + +message::mine::mine( + unsigned int si +) : command(si) {} diff --git a/src/msg/unitCommands.hpp b/src/msg/unitCommands.hpp index 904655e..28c6b07 100644 --- a/src/msg/unitCommands.hpp +++ b/src/msg/unitCommands.hpp @@ -2,34 +2,68 @@ #include +namespace rts { + class field; +} + namespace message { - class move : public addressUnitByCoordinates { + class command : public base { + addressUnit* source; public: + rts::unit* getUnit(rts::game* game) const; + + command(unsigned int sourceId); + command(unsigned int sourceX, unsigned int sourceY); + + ~command(); + }; + + class move : public command { const unsigned int destX; const unsigned int destY; + public: + rts::field* getDestinationField(rts::game* game) const; + move( - unsigned int dx, unsigned int dy, - unsigned int sx, unsigned int sy - ) : addressUnitByCoordinates(sx, sy), destX(dx), destY(dy) {} + unsigned int sx, unsigned int sy, + unsigned int dx, unsigned int dy + ); + + move( + unsigned int si, + unsigned int dx, unsigned int dy + ); }; - class attack : public addressUnitByCoordinates { + class attack : public command { + addressUnit* target; + public: - const unsigned int destX; - const unsigned int destY; + rts::unit* getTarget(rts::game* game) const; attack( - unsigned int dx, unsigned int dy, - unsigned int sx, unsigned int sy - ) : addressUnitByCoordinates(sx, sy), destX(dx), destY(dy) {} + unsigned int sx, unsigned int sy, + unsigned int tx, unsigned int ty + ); + + attack( + unsigned int si, + unsigned int ti + ); + + ~attack(); }; - class mine : public addressUnitByCoordinates { + class mine : public command { public: mine( unsigned int sx, unsigned int sy - ) : addressUnitByCoordinates(sx, sy) {} + ); + + mine( + unsigned int si + ); }; } \ No newline at end of file diff --git a/src/rts/board.hpp b/src/rts/board.hpp index 1d5d4a9..33a957a 100644 --- a/src/rts/board.hpp +++ b/src/rts/board.hpp @@ -9,7 +9,7 @@ namespace rts { class board { std::vector> fields; - std::mt19937 gen; // mersenne_twister_engine seeded with rd() + std::mt19937 gen; public: board(unsigned int x = 256, unsigned int y = 256); diff --git a/src/rts/game.cpp b/src/rts/game.cpp index 5441933..f056e75 100644 --- a/src/rts/game.cpp +++ b/src/rts/game.cpp @@ -18,10 +18,12 @@ std::unordered_map> {"resourceHp", [](rts::game* g, std::ifstream& f){ f >> g->resourceHp; }}, {"unitHp", [](rts::game* g, std::ifstream& f){ f >> g->unitHp; }}, {"unitDamage", [](rts::game* g, std::ifstream& f){ f >> g->unitDamage; }}, - {"allowedNameCharacters", [](rts::game* g, std::ifstream& f){ f >> g->allowedNameCharacters; }} + {"allowedNameCharacters", [](rts::game* g, std::ifstream& f){ f >> g->allowedNameCharacters; }}, + {"maxResourceSpawn", [](rts::game* g, std::ifstream& f){ f >> g->maxResourceSpawn; }}, + {"resourceChance", [](rts::game* g, std::ifstream& f){ f >> g->resourceChance; }} }; // i wish there was reflection system in c++ -rts::game::game(const char *port, const char* configFile) : _server(port) { +rts::game::game(const char *port, const char* configFile) : _server(port), gen(std::random_device()()) { _server.onNewClient = std::bind(&rts::game::handleNewClient, this, std::placeholders::_1); if (configFile != nullptr) { @@ -121,10 +123,13 @@ void rts::game::handleNewClient(client* client_) { void rts::game::loopLogic(){ // spawn resource and inform players - if (rand() % 10 == 0) { - field* f = _board.spawnResource(resourceHp); - if (f) { - sendToPlayers(activePlayers, newResourceMessage(f)); + std::uniform_real_distribution distrib(0.0, 1.0); + for (unsigned int i = 0; i < maxResourceSpawn; ++i){ + if (distrib(gen) < resourceChance){ + field* f = _board.spawnResource(resourceHp); + if (f) { + sendToPlayers(activePlayers, newResourceMessage(f)); + } } } @@ -132,6 +137,7 @@ void rts::game::loopLogic(){ for (player* p : activePlayers) { for (unit* u : p->units){ u->movedThisRound = false; + u->lastField = u->f; } } @@ -236,15 +242,20 @@ void rts::game::deletePlayer(player* pl){ void rts::game::playerLostAllUnits(player* pl) { assert(pl); assert(activePlayers.find(pl) != activePlayers.end()); - pl->getClient()->sendToClient({'L','\n'}); + std::vector buff = {'l'}; + message::appendStringWDelim(buff, pl->getName(), '\n'); + pl->getClient()->sendToClient(buff); removePlayerFromRoomOrQueue(pl); } void rts::game::tryWin(player* pl){ if (pl->units.size() >= unitsToWin) { - pl->getClient()->sendToClient({'W','\n'}); + std::vector buff = {'W'}; + message::appendStringWDelim(buff, pl->getName(), '\n'); + pl->getClient()->sendToClient(buff); + buff[0] = 'L'; for (player* p : activePlayers){ - if (p != pl) p->getClient()->sendToClient({'L','\n'}); + if (p != pl) p->getClient()->sendToClient(buff); } clearRoom(); diff --git a/src/rts/game.hpp b/src/rts/game.hpp index adeb639..e351d47 100644 --- a/src/rts/game.hpp +++ b/src/rts/game.hpp @@ -8,6 +8,7 @@ namespace rts { class player; + class unit; class game { private: @@ -15,6 +16,7 @@ namespace rts { std::unordered_set allPlayers; std::unordered_set activePlayers; std::deque queuedPlayers; + std::mt19937 gen; unsigned int millis = 1000; unsigned int maxPlayers = 16; @@ -25,6 +27,8 @@ namespace rts { unsigned int resourceHp = 100; unsigned int unitHp = 100; unsigned int unitDamage = 10; + unsigned int maxResourceSpawn = 1; + double resourceChance = 0.2; std::string allowedNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; unsigned int nextUnitId = 0; @@ -48,6 +52,7 @@ namespace rts { static void sendToPlayers(const std::unordered_set& players, const std::vector& message); public: + std::unordered_map unitsById; board _board; game(const char *port, const char* configFile); diff --git a/src/rts/player.cpp b/src/rts/player.cpp index fceb5ae..ce4ece3 100644 --- a/src/rts/player.cpp +++ b/src/rts/player.cpp @@ -39,25 +39,22 @@ void rts::player::handleNewMessage(const message::base* msg) { _game->removePlayerFromRoomOrQueue(this); } } - else if (const message::move* cmsg = dynamic_cast(msg)) { - if (unit* u = cmsg->getUnit(_game)) { - if (field* f = _game->_board.getField(cmsg->destX, cmsg->destY)){ - if (u->owner == this) u->move(f); + else if (const message::command* cmsg = dynamic_cast(msg)) { + if (unit* u = cmsg->getUnit(_game)){ + if (u->owner == this) { + + if (const message::move* mmsg = dynamic_cast(cmsg)) { + if (field* f = mmsg->getDestinationField(_game)) u->move(f); + } + else if (const message::attack* amsg = dynamic_cast(cmsg)) { + if (unit* t = amsg->getTarget(_game)) u->attack(t); + } + else if (dynamic_cast(cmsg)) { + u->mine(); + } } } } - else if (const message::attack* cmsg = dynamic_cast(msg)) { - if (unit* u = cmsg->getUnit(_game)) { - if (field* f = _game->_board.getField(cmsg->destX, cmsg->destY)) { - if (u->owner == this) u->attack(f->_unit); - } - } - } - else if (const message::mine* cmsg = dynamic_cast(msg)) { - if (unit* u = cmsg->getUnit(_game)) { - if (u->owner == this) u->mine(); - } - } } void rts::player::setName(const std::string& name) { diff --git a/src/rts/unit.cpp b/src/rts/unit.cpp index 8af647c..658162a 100644 --- a/src/rts/unit.cpp +++ b/src/rts/unit.cpp @@ -11,22 +11,24 @@ rts::unit::unit(player* owner_, field* field_, unsigned int id_) : id(id_), hp(owner_->getGame()->getUnitHp()), - f(field_), + f(field_), lastField(field_), owner(owner_) { printf("%s got new unit\n", owner->getName().c_str()); assert(f->empty()); f->_unit = this; + owner_->getGame()->unitsById.insert({id, this}); } void rts::unit::mine(){ if (!movedThisRound && f->hasResource()) { + f->mine(owner->getGame()->getUnitDamage()); std::vector buff = {'d'}; - message::appendNumberWDelim(buff, id, '\n'); + message::appendNumberWDelim(buff, id, ' '); + message::appendNumberWDelim(buff, f->getHp(), '\n'); owner->getGame()->sendToPlayers(buff); - f->mine(owner->getGame()->getUnitDamage()); if (f->getHp() <= 0) { field* nf = owner->getGame()->_board.closestEmptyField(f); if (nf) owner->newUnit(nf); @@ -52,11 +54,12 @@ void rts::unit::move(field* field_){ } void rts::unit::attack(unit* target){ if (target == nullptr) return; - if (!movedThisRound && f->distance(*(target->f)) <= 1) { + if (!movedThisRound && (f->distance(*(target->f)) <= 1 || f->distance(*(target->lastField)) <= 1)) { std::vector buff = {'a'}; message::appendNumberWDelim(buff, id, ' '); - message::appendNumberWDelim(buff, target->id, '\n'); + message::appendNumberWDelim(buff, target->id, ' '); + message::appendNumberWDelim(buff, target->hp - owner->getGame()->getUnitDamage(), '\n'); owner->getGame()->sendToPlayers(buff); target->recvDamage(owner->getGame()->getUnitDamage()); @@ -79,4 +82,5 @@ void rts::unit::recvDamage(unsigned int dmg){ rts::unit::~unit() { printf("%s lost a unit\n", owner->getName().c_str()); f->_unit = nullptr; + owner->getGame()->unitsById.erase(id); } \ No newline at end of file diff --git a/src/rts/unit.hpp b/src/rts/unit.hpp index 4e8e1e3..beab3c4 100644 --- a/src/rts/unit.hpp +++ b/src/rts/unit.hpp @@ -9,6 +9,7 @@ namespace rts { const unsigned int id; unsigned int hp; field* f; + field* lastField; player* const owner; bool movedThisRound = false;