feat(server): Implement game state broadcasting.

This commit is contained in:
Ritchie Cunningham 2025-09-13 17:49:09 +01:00
parent bd6281c9bc
commit 39a06147c8
8 changed files with 117 additions and 38 deletions

View File

@ -0,0 +1,15 @@
#pragma once
#include "message.h"
#include "player_state_message.h"
namespace BettolaLib {
namespace Network {
struct GameStateMessage {
unsigned int num_players;
PlayerStateMessage players[32]; /* Max 32 players for now. */
};
} /* namespace Network. */
} /* namespace BettolaLib. */

View File

@ -5,6 +5,8 @@ namespace Network {
enum class MessageType : unsigned char { enum class MessageType : unsigned char {
PlayerPosition, PlayerPosition,
PlayerState,
GameState,
}; };
struct MessageHeader { struct MessageHeader {

View File

@ -0,0 +1,15 @@
#pragma once
#include "message.h"
namespace BettolaLib {
namespace Network {
struct PlayerStateMessage {
unsigned int player_id;
float x;
float y;
};
} /* namespace Network. */
} /* namespace BettolaLib. */

View File

@ -1,33 +1,53 @@
#include <algorithm> #include <algorithm>
#include "game.h" #include "game.h"
#include "network/game_state_message.h"
#include "network/socket.h" #include "network/socket.h"
#include "network/message.h"
void Game::add_player(BettolaLib::Network::Socket* socket) { Player* Game::add_player(BettolaLib::Network::Socket* socket) {
_players.emplace_back(socket); _players.emplace_back(socket);
return &_players.back();
} }
void Game::remove_player(BettolaLib::Network::Socket* socket) { void Game::remove_player(unsigned int player_id) {
/*
* TODO: We'll need a better way to ident players other than
* their socket. We'll match sockets for now though.. -- Falco
*/
_players.erase( _players.erase(
std::remove_if(_players.begin(), _players.end(), std::remove_if(_players.begin(), _players.end(),
[socket](const Player& player) { [player_id](const Player& player) {
/* this is a sh.t way to compare players. */ /* this is a sh.t way to compare players. */
return &player.get_socket() == socket; return player.get_id() == player_id;
}), }),
_players.end()); _players.end());
} }
void Game::update_player_pos(BettolaLib::Network::Socket* socket, float x, float y) { void Game::update_player_pos(unsigned int player_id, float x, float y) {
auto it = std::find_if(_players.begin(), _players.end(), auto it = std::find_if(_players.begin(), _players.end(),
[socket](const Player& player) { [player_id](const Player& player) {
return &player.get_socket() == socket; return player.get_id() == player_id;
}); });
if(it != _players.end()) { if(it != _players.end()) {
it->set_position(x, y); it->set_position(x, y);
} }
} }
void Game::broadcast_game_state(void) {
BettolaLib::Network::GameStateMessage msg;
msg.num_players = _players.size();
for(size_t i = 0; i < _players.size(); ++i) {
msg.players[i].player_id = _players[i].get_id();
msg.players[i].x = _players[i].get_x();
msg.players[i].y = _players[i].get_y();
}
BettolaLib::Network::MessageHeader header;
header.type = BettolaLib::Network::MessageType::GameState;
header.size = sizeof(msg);
for(const auto& player : _players) {
BettolaLib::Network::Socket& socket = player.get_socket();
socket.send(&header, sizeof(header));
socket.send(&msg, sizeof(msg));
}
}

View File

@ -6,9 +6,10 @@
class Game { class Game {
public: public:
void add_player(BettolaLib::Network::Socket* socket); Player* add_player(BettolaLib::Network::Socket* socket);
void remove_player(BettolaLib::Network::Socket* socket); void remove_player(unsigned int player_id);
void update_player_pos(BettolaLib::Network::Socket* socket, float x, float y); void update_player_pos(unsigned int player_id, float x, float y);
void broadcast_game_state(void);
private: private:
std::vector<Player> _players; std::vector<Player> _players;

View File

@ -1,6 +1,8 @@
#include "player.h" #include "player.h"
#include "network/socket.h" #include "network/socket.h"
Player::Player(BettolaLib::Network::Socket* socket) : unsigned int Player::_next_player_id = 0;
_x(0.0f), _y(0.0f), _socket(socket) {}
Player::Player(BettolaLib::Network::Socket* socket) :
_id(_next_player_id++), _x(0.0f), _y(0.0f), _socket(socket) {}

View File

@ -8,9 +8,15 @@ public:
void set_position(float x, float y) { _x = x; _y = y; } void set_position(float x, float y) { _x = x; _y = y; }
unsigned int get_id(void) const { return _id; }
float get_x(void) const { return _x; }
float get_y(void) const { return _y; }
BettolaLib::Network::Socket& get_socket(void) const { return *_socket; } BettolaLib::Network::Socket& get_socket(void) const { return *_socket; }
private: private:
static unsigned int _next_player_id;
unsigned int _id;
float _x; float _x;
float _y; float _y;
BettolaLib::Network::Socket* _socket; BettolaLib::Network::Socket* _socket;

View File

@ -1,4 +1,7 @@
#include <cstddef>
#include <cstdio> #include <cstdio>
#include <chrono>
#include <thread>
#include "bettola/network/socket.h" #include "bettola/network/socket.h"
#include "bettola/network/net_common.h" #include "bettola/network/net_common.h"
@ -33,25 +36,35 @@ int main(void) {
printf("Bettola Server: Listening on port %hu...\n", BettolaLib::Network::DEFAULT_PORT); printf("Bettola Server: Listening on port %hu...\n", BettolaLib::Network::DEFAULT_PORT);
auto last_time = std::chrono::high_resolution_clock::now();
/* Main server loop. */ /* Main server loop. */
while(true) { while(true) {
BettolaLib::Network::Socket* client_socket = server_socket.accept(); auto current_time = std::chrono::high_resolution_clock::now();
auto delta_time = std::chrono::duration_cast<std::chrono::duration<double>>
(current_time-last_time).count();
if(client_socket == nullptr) { /* TODO:
printf("Bettola Server: Failed to accept client connection.\n"); * Accept new connections.
continue; /* try accepting again. */ * This is blocking.. Will fix later.
*/
BettolaLib::Network::Socket* client_socket = server_socket.accept();
if(client_socket != nullptr) {
Player* new_player = game.add_player(client_socket);
printf("Bettola Server: Client connected! Player ID: %u\n", new_player->get_id());
} }
game.add_player(client_socket); /* TODO:
* Process messages from clients.
printf("Bettola Server: Client connected!\n"); * this is also blocking and only handles one client at a time. fix later
*/
if(client_socket != nullptr) {
while(true) { while(true) {
BettolaLib::Network::MessageHeader header; BettolaLib::Network::MessageHeader header;
ssize_t bytes_received = client_socket->recv(&header, sizeof(header)); ssize_t bytes_received = client_socket->recv(&header, sizeof(header));
if(bytes_received <= 0) { if(bytes_received <= 0) {
game.remove_player(client_socket); //game.remove_player(new_player->get_id());
break; break;
} }
@ -59,14 +72,19 @@ int main(void) {
BettolaLib::Network::PlayerPosMessage msg; BettolaLib::Network::PlayerPosMessage msg;
bytes_received = client_socket->recv(&msg, sizeof(msg)); bytes_received = client_socket->recv(&msg, sizeof(msg));
if(bytes_received > 0) { if(bytes_received > 0) {
printf("Bettola Server: Received PlayerPosition messgae: x=%.2f, y=%.2f\n", //game.update_player_pos(new_player->get_id(), msg.x, msg.y);
msg.x, msg.y);
game.update_player_pos(client_socket, msg.x, msg.y);
} }
} }
} }
} }
/* Broadcase game state. */
game.broadcast_game_state();
/* Sleep for a short time to avoid busy-waiting. */
std::this_thread::sleep_for(std::chrono::milliseconds(1000/60));
}
server_socket.close(); /* Shouldn't reach here. */ server_socket.close(); /* Shouldn't reach here. */
printf("=== Bettola Server: Shutting Down ===\n"); printf("=== Bettola Server: Shutting Down ===\n");
return 0; return 0;