Skip to content

Commit

Permalink
Merge pull request #26 from pwalig/dev
Browse files Browse the repository at this point in the history
Send updates instead of board state; Fix a major bug
  • Loading branch information
pwalig authored Dec 21, 2024
2 parents da97fa3 + 0c3aeef commit 48dc336
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 56 deletions.
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -68,24 +68,32 @@ Some messages consist of only type character others contain more data.
### From client

- `n` `<name>` `\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` `<x1>` ` ` `<y1>` ` ` `<x2>` ` ` `<y2>` `\n` - request unit to move (`<x1><y1>` are coordinates of the unit, `<x2><y2>` designate destination)
- `a` `<x1>` ` ` `<y1>` ` ` `<x2>` ` ` `<y2>` `\n` - request unit to move (`<x1><y1>` are coordinates of the unit, `<x2><y2>` coordinates of the target unit) (possible to attack own units)
- `d` `<x1>` ` ` `<y1>` `\n` - request unit to mine the resource (`<x1><y1>` are coordinates of the unit) (unit can only mine resource that it is standing on)

### From server

- `g` `<boardX>` ` ` `<boardY>` ` ` `<unitsToWin>` `\n` - player was sent to game room (in response to: `j`), `<board x dim>`, `<board y dim>` and `<unitsToWin>` same as in `[config file]`
- `c` `<millis>` ` ` `<maxPlayers>` ` ` `<boardX>` ` ` `<boardY>` ` ` `<unitsToWin>` ` ` `<startResources>` ` ` `<resourceHp>` ` ` `<unitHp>` ` ` `<unitDamage>` ` ` `<allowedNameCharacters>` `\n` - whole server configuration sent to newly joined clients
- `j` `<player name>` `\n` - new player has joined the game room
- `l` `<player name>` `\n` - player `<player name>` has either left or lost the game
- `m` `<id>` ` ` `<x>` ` ` `<y>` `\n` - unit of id `<id>` has moved to `<x>;<y>`
- `a` `<id1>` ` ` `<id2>` `\n` - unit of id `<id1>` attacked unit of id `<id2>`
- `d` `<id>` `\n` - unit of id `<id>` mined a resource
- `u` `<player name>` ` ` `<id>` ` ` `<x>` ` ` `<y>` - player `<player name>` has aquired unit of id `<id>` on field `<x>;<y>`
- `f` `<x>` ` ` `<y>` ` ` `<hp>` `\n` - new resource spawned on field `<x>;<y>`
- `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:

Expand All @@ -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**
3 changes: 1 addition & 2 deletions src/net/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ void server::loop(const int& millis){
else {
client* client_ = (client*)(ee.data.ptr);
if (ee.events & EPOLLIN) {
std::vector<char> data = client_->receive();
write(0, data.data(), data.size());
client_->receive();
}
if (ee.events & EPOLLOUT) {
client_->sendFromBuffer();
Expand Down
26 changes: 21 additions & 5 deletions src/rts/board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +33,16 @@ std::vector<rts::field*> rts::board::resourceFields(bool resource) {
return out;
}

std::vector<const rts::field*> rts::board::constResourceFields(bool resource) const {
std::vector<const field*> out;
for (const std::vector<field>& row : fields){
for (const field& f : row) {
if (f.hasResource() == resource) out.push_back(&f);
}
}
return out;
}

std::vector<rts::field*> rts::board::emptyFields(bool empty) {
std::vector<field*> out;
for (std::vector<field>& row : fields){
Expand Down Expand Up @@ -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<field*> 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);
}
}

Expand Down
9 changes: 5 additions & 4 deletions src/rts/board.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<field*> resourceFields(bool resource);
std::vector<const field*> constResourceFields(bool resource) const;
std::vector<field*> 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;
Expand Down
8 changes: 6 additions & 2 deletions src/rts/field.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cassert>
#include <limits>
#include <algorithm>
#include <stdio.h>

#include <rts/unit.hpp>

Expand All @@ -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 {
Expand Down
8 changes: 6 additions & 2 deletions src/rts/field.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
101 changes: 78 additions & 23 deletions src/rts/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ std::unordered_map<std::string, std::function<void(rts::game*, std::ifstream&)>>
{"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);
Expand All @@ -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<char> rts::game::configMessage() const {
std::vector<char> 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<char> rts::game::boardStateMessage() const {
std::vector<char> buff = {'p'};
message::appendNumberWDelim(buff, activePlayers.size(), ';'); // amount of players

Expand All @@ -67,41 +74,79 @@ void rts::game::loopLogic(){
buff.push_back(';');
}
buff.push_back('\n');

// resources
buff.push_back('r');
std::vector<rts::field*> resources = _board.resourceFields(true);
std::vector<const rts::field*> 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<char> rts::game::newPlayerMessage(const player* p) const{
std::vector<char> buff = {'j'};

message::appendStringWDelim(buff, p->getName(), '\n'); // player name

return buff;
}

std::vector<char> rts::game::playerLeftMessage(const player* p) const{
std::vector<char> buff = {'l'};

message::appendStringWDelim(buff, p->getName(), '\n'); // player name

return buff;
}

std::vector<char> rts::game::newResourceMessage(const field* f) const{
std::vector<char> 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) {
for (unit* u : p->units){
u->movedThisRound = false;
}
}

// send next tick message to room players
sendToPlayers(activePlayers, {'t', '\n'}); // next game tick
}

void rts::game::clearRoom() {
for (player* pl : activePlayers) { // remove all player from game room
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();
Expand All @@ -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<char> 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) {
Expand All @@ -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();
}
Expand All @@ -149,6 +192,16 @@ void rts::game::removePlayerFromRoomOrQueue(player* pl) {
}
}

void rts::game::sendToPlayers(const std::unordered_set<rts::player*>& players, const std::vector<char>& message) {
for (player* p : players){
p->getClient()->sendToClient(message);
}
}

void rts::game::sendToPlayers(const std::vector<char>& message) const {
sendToPlayers(activePlayers, message);
}

void rts::game::run() {
_server.loop(millis);
}
Expand Down Expand Up @@ -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;}

Expand Down
Loading

0 comments on commit 48dc336

Please sign in to comment.