diff --git a/README.md b/README.md index 7b0b62e..38fc801 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ make ### Available values: -* `maxPlayers` - how many players can play at once [default: 16] * `millis` - duration of one game step [default: 200] +* `maxPlayers` - how many players can play at once [default: 16] * `boardX` - x dimention of the board [default: 256] * `boardY` - y dimention of the board[default: 256] -* `startResources` - how many resources to spawn at the start of the game [default: 25] * `unitsToWin` - how many units player has to aquire to win the game [default: 50] +* `startResources` - how many resources to spawn at the start of the game [default: 25] * `resourceHp` - starting hit points of every new resource [default: 100] * `unitHp` - starting hit points of every new unit [default: 100] * `unitDamage` - how much damage do units deal on every attack [default: 10] @@ -68,24 +68,32 @@ Some messages consist of only type character others contain more data. ### From client - `n` `` `\n` - set name or rename self -- `j` `\n` - request join (player will be sent to game room or queue) -- `q` `\n` - request quit (player will be removed from game room or queue, can still rejoin with `j`) +- `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 move (`` 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) ### From server -- `g` `` ` ` `` ` ` `` `\n` - player was sent to game room (in response to: `j`), ``, `` and `` same as in `[config file]` +- `c` `` ` ` `` ` ` `` ` ` `` ` ` `` ` ` `` ` ` `` ` ` `` ` ` `` ` ` `` `\n` - whole server configuration sent to newly joined clients +- `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 +- `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) -### Board state update +### Board state message -Board state update is sent to all players in the game room in regular time intervals +Board state update is sent to every player that joins the game room Structure as follows: @@ -107,3 +115,19 @@ Structure as follows: ... Numbers are represented as strings of characters (97 ---> "97" not 'a'). + +### Communication order (client`s perspective) + +**1:** server sends `c` +**2:** client sends `n` +**3:** if server responds `n` => go to step **2** +**3:** else server responds `y` +**4:** if client sends `n` => go to step **3** +**4:** else client sends `j` +**5:** if server responds `q` => wait until server sends `p`, then `r` +**5:** else server reponds `p`, then `r` +**6:** server can send multiple: `j` `l` `m` `a` `d` `u` `f` messages +**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** +**7:** server sends `t` => go to step **6** diff --git a/src/net/server.cpp b/src/net/server.cpp index 3eaba2d..2434679 100644 --- a/src/net/server.cpp +++ b/src/net/server.cpp @@ -81,8 +81,7 @@ void server::loop(const int& millis){ else { client* client_ = (client*)(ee.data.ptr); if (ee.events & EPOLLIN) { - std::vector data = client_->receive(); - write(0, data.data(), data.size()); + client_->receive(); } if (ee.events & EPOLLOUT) { client_->sendFromBuffer(); diff --git a/src/rts/board.cpp b/src/rts/board.cpp index 69cd5e5..039cb0f 100644 --- a/src/rts/board.cpp +++ b/src/rts/board.cpp @@ -12,12 +12,12 @@ rts::board::board(unsigned int X, unsigned int Y) : gen(std::random_device()()) } } -rts::field* rts::board::getField(const unsigned int& xpos, const unsigned int& ypos) { +rts::field* rts::board::getField(unsigned int xpos, unsigned int ypos) { if (xpos < getXdim() && ypos < getYdim()) return &fields[xpos][ypos]; else return nullptr; } -const rts::field* rts::board::getConstField(const unsigned int& xpos, const unsigned int& ypos) const { +const rts::field* rts::board::getConstField(unsigned int xpos, unsigned int ypos) const { if (xpos < getXdim() && ypos < getYdim()) return &fields[xpos][ypos]; else return nullptr; @@ -33,6 +33,16 @@ std::vector rts::board::resourceFields(bool resource) { return out; } +std::vector rts::board::constResourceFields(bool resource) const { + std::vector out; + for (const std::vector& row : fields){ + for (const field& f : row) { + if (f.hasResource() == resource) out.push_back(&f); + } + } + return out; +} + std::vector rts::board::emptyFields(bool empty) { std::vector out; for (std::vector& row : fields){ @@ -73,13 +83,19 @@ rts::field* rts::board::closestEmptyField(const field* source) { return candidate; } -void rts::board::spawnResource(unsigned int hp) { - randomResourceField(false)->spawnResource(hp); +rts::field* rts::board::spawnResource(unsigned int hp) { + return randomResourceField(false)->spawnResource(hp); } void rts::board::spawnResources(unsigned int amount, unsigned int hp) { + std::vector resFields = resourceFields(false); for (unsigned int i = 0; i < amount; ++i) { - spawnResource(hp); + if (resFields.empty()) return; + std::uniform_int_distribution<> distrib(0, resFields.size() - 1); + int fid = distrib(gen); + field* f = resFields[fid]; + f->spawnResource(hp); + resFields.erase(resFields.begin() + fid); } } diff --git a/src/rts/board.hpp b/src/rts/board.hpp index 9dbcb00..88e4dd5 100644 --- a/src/rts/board.hpp +++ b/src/rts/board.hpp @@ -14,17 +14,18 @@ namespace rts { public: board(unsigned int x = 256, unsigned int y = 256); - field* getField(const unsigned int& xpos, const unsigned int& ypos); - const field* getConstField(const unsigned int& xpos, const unsigned int& ypos) const; - + field* getField(unsigned int xpos, unsigned int ypos); + const field* getConstField(unsigned int xpos, unsigned int ypos) const; + std::vector resourceFields(bool resource); + std::vector constResourceFields(bool resource) const; std::vector emptyFields(bool empty); field* randomField(); field* randomEmptyField(bool empty); field* randomResourceField(bool resource); field* closestEmptyField(const field* source); - void spawnResource(unsigned int hp); + field* spawnResource(unsigned int hp); void spawnResources(unsigned int amount, unsigned int hp); unsigned int getXdim() const; diff --git a/src/rts/field.cpp b/src/rts/field.cpp index d0e31c4..f7017d0 100644 --- a/src/rts/field.cpp +++ b/src/rts/field.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -16,13 +17,16 @@ bool rts::field::hasResource() const { return (resourceHp > 0); } -void rts::field::spawnResource(unsigned int hp) { +rts::field* rts::field::spawnResource(unsigned int hp) { assert(!hasResource()); resourceHp = (int)hp; + printf("spawned resource at: %d, %d\n", x, y); + return this; } -void rts::field::mine(int dmg) { +rts::field* rts::field::mine(int dmg) { resourceHp -= dmg; + return this; } int rts::field::getHp() const { diff --git a/src/rts/field.hpp b/src/rts/field.hpp index 3da9359..3c42cd3 100644 --- a/src/rts/field.hpp +++ b/src/rts/field.hpp @@ -11,8 +11,12 @@ namespace rts { bool empty() const; bool hasResource() const; - void spawnResource(unsigned int hp); - void mine(int dmg); + + // @returns this field + rts::field* spawnResource(unsigned int hp); + + // @returns this field + rts::field* mine(int dmg); int getHp() const; diff --git a/src/rts/game.cpp b/src/rts/game.cpp index be0d651..ac29ac2 100644 --- a/src/rts/game.cpp +++ b/src/rts/game.cpp @@ -13,13 +13,13 @@ std::unordered_map> {"maxPlayers", [](rts::game* g, std::ifstream& f){ f >> g->maxPlayers; }}, {"boardX", [](rts::game* g, std::ifstream& f){ f >> g->boardX; }}, {"boardY", [](rts::game* g, std::ifstream& f){ f >> g->boardY; }}, + {"unitsToWin", [](rts::game* g, std::ifstream& f){ f >> g->unitsToWin; }}, {"startResources", [](rts::game* g, std::ifstream& f){ f >> g->startResources; }}, {"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; }}, - {"unitsToWin", [](rts::game* g, std::ifstream& f){ f >> g->unitsToWin; }}, {"allowedNameCharacters", [](rts::game* g, std::ifstream& f){ f >> g->allowedNameCharacters; }} -}; // i can only dream of reflection system in c++ +}; // i wish there was reflection system in c++ rts::game::game(const char *port, const char* configFile) : _server(port) { _server.onNewClient = std::bind(&rts::game::handleNewClient, this, std::placeholders::_1); @@ -38,17 +38,24 @@ rts::game::game(const char *port, const char* configFile) : _server(port) { } } -void rts::game::handleNewClient(client* client_) { - player* pl = new player(this, client_); - allPlayers.insert(pl); -} - -void rts::game::loopLogic(){ - // spawn resource - if (rand() % 10 == 0) _board.spawnResource(resourceHp); +// ========== MESSAGES ========= +std::vector rts::game::configMessage() const { + std::vector buff = {'c'}; + message::appendNumberWDelim(buff, millis, ' '); + message::appendNumberWDelim(buff, maxPlayers, ' '); + message::appendNumberWDelim(buff, boardX, ' '); + message::appendNumberWDelim(buff, boardY, ' '); + message::appendNumberWDelim(buff, unitsToWin, ' '); + message::appendNumberWDelim(buff, startResources, ' '); + message::appendNumberWDelim(buff, resourceHp, ' '); + message::appendNumberWDelim(buff, unitHp, ' '); + message::appendNumberWDelim(buff, unitDamage, ' '); + message::appendStringWDelim(buff, allowedNameCharacters, '\n'); + return buff; +} - // sent updates to clients +std::vector rts::game::boardStateMessage() const { std::vector buff = {'p'}; message::appendNumberWDelim(buff, activePlayers.size(), ';'); // amount of players @@ -67,22 +74,54 @@ void rts::game::loopLogic(){ buff.push_back(';'); } buff.push_back('\n'); - // resources buff.push_back('r'); - std::vector resources = _board.resourceFields(true); + std::vector resources = _board.constResourceFields(true); message::appendNumberWDelim(buff, resources.size(), ';'); // amount of resources - for (field* f : resources) { + for (const field* f : resources) { message::appendNumberWDelim(buff, f->x, ' '); message::appendNumberWDelim(buff, f->y, ' '); message::appendNumberWDelim(buff, f->getHp(), ';'); } buff.push_back('\n'); + return buff; +} - for (player* p : activePlayers){ - p->getClient()->sendToClient(buff); - } +std::vector rts::game::newPlayerMessage(const player* p) const{ + std::vector buff = {'j'}; + message::appendStringWDelim(buff, p->getName(), '\n'); // player name + + return buff; +} + +std::vector rts::game::playerLeftMessage(const player* p) const{ + std::vector buff = {'l'}; + + message::appendStringWDelim(buff, p->getName(), '\n'); // player name + + return buff; +} + +std::vector rts::game::newResourceMessage(const field* f) const{ + std::vector buff = {'f'}; + message::appendNumberWDelim(buff, f->x, ' '); + message::appendNumberWDelim(buff, f->y, ' '); + message::appendNumberWDelim(buff, f->getHp(), '\n'); + return buff; +} + +// ========== GAME LOGIC ========== + +void rts::game::handleNewClient(client* client_) { + player* pl = new player(this, client_); + allPlayers.insert(pl); + pl->getClient()->sendToClient(configMessage()); +} + +void rts::game::loopLogic(){ + // spawn resource and inform players + if (rand() % 10 == 0) sendToPlayers(activePlayers, newResourceMessage(_board.spawnResource(resourceHp))); // allow units to move again for (player* p : activePlayers) { @@ -90,6 +129,9 @@ void rts::game::loopLogic(){ u->movedThisRound = false; } } + + // send next tick message to room players + sendToPlayers(activePlayers, {'t', '\n'}); // next game tick } void rts::game::clearRoom() { @@ -97,11 +139,14 @@ void rts::game::clearRoom() { pl->removeAllUnits(); } activePlayers.clear(); + nextUnitId = 0; _server.loopLogic = [](){}; + printf("room cleared\n"); } void rts::game::startGame() { _board = board(boardX, boardY); // reset board + printf("game start\n"); _board.spawnResources(startResources, resourceHp); while(!queuedPlayers.empty() && activePlayers.size() < maxPlayers){ moveQueuedPlayerToRoom(); @@ -111,13 +156,10 @@ void rts::game::startGame() { void rts::game::addPlayerToRoom(player* pl) { assert(activePlayers.size() < maxPlayers); - activePlayers.insert(pl); + sendToPlayers(activePlayers, newPlayerMessage(pl)); pl->newUnit(_board.randomEmptyField(true)); // add first unit for the player to control - std::vector buff = {'g'}; - message::appendNumberWDelim(buff, _board.getXdim(), ' '); - message::appendNumberWDelim(buff, _board.getYdim(), ' '); - message::appendNumberWDelim(buff, unitsToWin, '\n'); - pl->getClient()->sendToClient(buff); // joined active group + activePlayers.insert(pl); + pl->getClient()->sendToClient(boardStateMessage()); } void rts::game::addPlayerToQueue(player* pl) { @@ -136,6 +178,7 @@ void rts::game::removePlayerFromRoomOrQueue(player* pl) { if (activePlayers.find(pl) != activePlayers.end()) { activePlayers.erase(pl); pl->removeAllUnits(); + sendToPlayers(activePlayers, playerLeftMessage(pl)); if (!queuedPlayers.empty()) moveQueuedPlayerToRoom(); if (activePlayers.empty()) clearRoom(); } @@ -149,6 +192,16 @@ void rts::game::removePlayerFromRoomOrQueue(player* pl) { } } +void rts::game::sendToPlayers(const std::unordered_set& players, const std::vector& message) { + for (player* p : players){ + p->getClient()->sendToClient(message); + } +} + +void rts::game::sendToPlayers(const std::vector& message) const { + sendToPlayers(activePlayers, message); +} + void rts::game::run() { _server.loop(millis); } @@ -195,6 +248,8 @@ void rts::game::tryWin(player* pl){ } } +// ========== GETTERS ========== + unsigned int rts::game::getUnitDamage() const {return unitDamage;} unsigned int rts::game::getUnitHp() const {return unitHp;} diff --git a/src/rts/game.hpp b/src/rts/game.hpp index fdad88b..adeb639 100644 --- a/src/rts/game.hpp +++ b/src/rts/game.hpp @@ -12,23 +12,29 @@ namespace rts { class game { private: server _server; - unsigned int maxPlayers = 16; std::unordered_set allPlayers; std::unordered_set activePlayers; std::deque queuedPlayers; unsigned int millis = 1000; - unsigned int startResources = 25; + unsigned int maxPlayers = 16; unsigned int boardX = 256; unsigned int boardY = 256; + unsigned int unitsToWin = 50; + unsigned int startResources = 25; unsigned int resourceHp = 100; unsigned int unitHp = 100; unsigned int unitDamage = 10; - unsigned int unitsToWin = 50; std::string allowedNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; unsigned int nextUnitId = 0; + std::vector configMessage() const; + std::vector boardStateMessage() const; + std::vector newPlayerMessage(const player* p) const; + std::vector playerLeftMessage(const player* p) const; + std::vector newResourceMessage(const field* f) const; + void handleNewClient(client* client_); void loopLogic(); void clearRoom(); @@ -39,11 +45,14 @@ namespace rts { void moveQueuedPlayerToRoom(); static std::unordered_map> configValueHandlers; + static void sendToPlayers(const std::unordered_set& players, const std::vector& message); public: board _board; game(const char *port, const char* configFile); + + void sendToPlayers(const std::vector& message) const; void run(); diff --git a/src/rts/player.cpp b/src/rts/player.cpp index 71b40a8..fceb5ae 100644 --- a/src/rts/player.cpp +++ b/src/rts/player.cpp @@ -7,6 +7,7 @@ #include #include #include +#include std::unordered_map rts::player::playersByName; @@ -29,7 +30,7 @@ void rts::player::handleNewMessage(const message::base* msg) { } else if (const message::state* cmsg = dynamic_cast(msg)) { if (cmsg->act == message::state::action::disconnect) { - _game->deletePlayer(this); + _game->deletePlayer(this); } else if (cmsg->act == message::state::action::joinRequest) { _game->tryJoin(this); @@ -96,7 +97,16 @@ void rts::player::removeAllUnits(){ } void rts::player::newUnit(field* field_){ - units.insert(new unit(this, field_, _game->getNextUnitId())); + unit* u = new unit(this, field_, _game->getNextUnitId()); + units.insert(u); + + std::vector buff = {'u'}; + message::appendStringWDelim(buff, _name, ' '); + message::appendNumberWDelim(buff, u->id, ' '); + message::appendNumberWDelim(buff, u->f->x, ' '); + message::appendNumberWDelim(buff, u->f->y, '\n'); + _game->sendToPlayers(buff); + _game->tryWin(this); } diff --git a/src/rts/unit.cpp b/src/rts/unit.cpp index 1632354..a47f08d 100644 --- a/src/rts/unit.cpp +++ b/src/rts/unit.cpp @@ -6,11 +6,13 @@ #include #include #include +#include rts::unit::unit(player* owner_, field* field_, unsigned int id_) : - owner(owner_), - f(field_), id(id_), - hp(owner_->getGame()->getUnitHp()) + id(id_), + hp(owner_->getGame()->getUnitHp()), + f(field_), + owner(owner_) { printf("%s got new unit\n", owner->getName().c_str()); assert(f->empty()); @@ -19,6 +21,11 @@ rts::unit::unit(player* owner_, field* field_, unsigned int id_) : void rts::unit::mine(){ if (!movedThisRound && f->hasResource()) { + + std::vector buff = {'d'}; + message::appendNumberWDelim(buff, id, '\n'); + owner->getGame()->sendToPlayers(buff); + f->mine(owner->getGame()->getUnitDamage()); if (f->getHp() <= 0) { field* nf = owner->getGame()->_board.closestEmptyField(f); @@ -29,6 +36,14 @@ void rts::unit::mine(){ } void rts::unit::move(field* field_){ if (!movedThisRound && f->distance(*field_) <= 1 && field_->empty()) { + + std::vector buff = {'m'}; + message::appendNumberWDelim(buff, id, ' '); + message::appendNumberWDelim(buff, field_->x, ' '); + message::appendNumberWDelim(buff, field_->y, '\n'); + owner->getGame()->sendToPlayers(buff); + + this->f->_unit = nullptr; this->f = field_; field_->_unit = this; @@ -38,6 +53,12 @@ void rts::unit::move(field* field_){ void rts::unit::attack(unit* target){ if (target == nullptr) return; if (!movedThisRound && f->distance(*(target->f)) <= 1) { + + std::vector buff = {'a'}; + message::appendNumberWDelim(buff, id, ' '); + message::appendNumberWDelim(buff, target->id, '\n'); + owner->getGame()->sendToPlayers(buff); + target->recvDamage(owner->getGame()->getUnitDamage()); movedThisRound = true; } diff --git a/src/rts/unit.hpp b/src/rts/unit.hpp index b3f8f82..4e8e1e3 100644 --- a/src/rts/unit.hpp +++ b/src/rts/unit.hpp @@ -6,11 +6,11 @@ namespace rts { class unit { public: - bool movedThisRound = false; - player* const owner; - field* f; const unsigned int id; unsigned int hp; + field* f; + player* const owner; + bool movedThisRound = false; unit(player* owner_, field* field_, unsigned int id_);