Some progress on the server code
This commit is contained in:
commit
3b1136e7e0
8 changed files with 654 additions and 0 deletions
74
.gitignore
vendored
Normal file
74
.gitignore
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
20
CMakeLists.txt
Normal file
20
CMakeLists.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(mislaborate LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(Protobuf REQUIRED)
|
||||
include_directories(${Protobuf_INCLUDE_DIRS})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS generic.proto)
|
||||
|
||||
add_executable(mislaborate-server
|
||||
server.cpp
|
||||
server/Connection.hpp server/World.hpp
|
||||
server/Connection.cpp server/World.cpp
|
||||
${PROTO_SRCS} ${PROTO_HDRS}
|
||||
)
|
||||
target_link_libraries(mislaborate-server PRIVATE ${Protobuf_LIBRARIES})
|
||||
target_compile_definitions(mislaborate-server PUBLIC BOOST_ASIO_HAS_CO_AWAIT)
|
74
generic.proto
Normal file
74
generic.proto
Normal file
|
@ -0,0 +1,74 @@
|
|||
syntax = "proto3";
|
||||
package Generic;
|
||||
|
||||
|
||||
|
||||
message Error {
|
||||
string description = 1;
|
||||
enum Code {
|
||||
Unknown = 0;
|
||||
BadString = 1;
|
||||
TooLarge = 2;
|
||||
BadUsername = 3;
|
||||
UsernameAlreadyTaken = 4;
|
||||
InvalidOpcode = 5;
|
||||
AuthenticationRequired = 6;
|
||||
IllegalOperation = 7;
|
||||
OutOfRange = 8;
|
||||
}
|
||||
Code code = 2;
|
||||
}
|
||||
|
||||
message SimpleAuth {
|
||||
string username = 1;
|
||||
}
|
||||
|
||||
message Operation {
|
||||
enum Code {
|
||||
Disconnect = 0;
|
||||
Error = 1;
|
||||
OK = 2;
|
||||
SimpleAuth = 3;
|
||||
_MinAuth = 4;
|
||||
SettingsSync = 5;
|
||||
PlayfieldSync = 6;
|
||||
RobotUpdate = 7;
|
||||
}
|
||||
Code code = 1;
|
||||
}
|
||||
|
||||
|
||||
message Vector2 {
|
||||
float x = 1;
|
||||
float y = 2;
|
||||
}
|
||||
|
||||
|
||||
message Settings {
|
||||
optional uint32 robotsPerClient = 1;
|
||||
optional float maxDistancePerTurn = 2;
|
||||
optional uint32 maxRobotHealth = 3;
|
||||
optional uint32 maxRobotDamage = 4;
|
||||
optional uint32 protectCooldown = 5;
|
||||
optional uint32 healCooldown = 6;
|
||||
optional uint32 healPerTurn = 7;
|
||||
}
|
||||
|
||||
message Robot {
|
||||
uint32 id = 1;
|
||||
uint32 client = 2;
|
||||
Vector2 position = 3;
|
||||
enum State {
|
||||
Idle = 0;
|
||||
Attack = 1;
|
||||
Protect = 2;
|
||||
Heal = 3;
|
||||
Dead = 4;
|
||||
}
|
||||
State state = 4;
|
||||
uint32 health = 5;
|
||||
}
|
||||
|
||||
message PlayfieldSync {
|
||||
repeated Robot robots = 1;
|
||||
}
|
21
server.cpp
Normal file
21
server.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "generic.pb.h"
|
||||
#include "server/Connection.hpp"
|
||||
#include "server/World.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
|
||||
|
||||
int main() {
|
||||
try {
|
||||
boost::asio::io_context ioc;
|
||||
Connection::Server server(ioc);
|
||||
int port = 78432;
|
||||
boost::asio::co_spawn(ioc, server.listen(port), boost::asio::detached);
|
||||
std::cout << "Server has initialized and will now start listening on port " << port << '.' << std::endl;
|
||||
ioc.run();
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "Failed to launch: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
127
server/Connection.cpp
Normal file
127
server/Connection.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include "Connection.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
|
||||
|
||||
namespace Connection {
|
||||
asio::awaitable<Packet> Client::receivePacket() {
|
||||
Packet packet;
|
||||
// Receive operation
|
||||
packet.op = co_await receiveProtobuf<Generic::Operation>();
|
||||
// Receive data
|
||||
switch (packet.op.code()) {
|
||||
case Generic::Operation::Error: {
|
||||
packet.data = std::make_unique<Generic::Error>(co_await receiveProtobuf<Generic::Error>());
|
||||
} break;
|
||||
case Generic::Operation::SimpleAuth: {
|
||||
packet.data = std::make_unique<Generic::SimpleAuth>(co_await receiveProtobuf<Generic::SimpleAuth>(30));
|
||||
} break;
|
||||
case Generic::Operation::Disconnect:
|
||||
case Generic::Operation::OK:
|
||||
break;
|
||||
default: throw FatalError("Invalid opcode: "+std::to_string(packet.op.code()), Generic::Error::InvalidOpcode);
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> Client::sendProtobuf(const google::protobuf::Message& buffer) {
|
||||
// Send size
|
||||
uint16_t size = buffer.ByteSizeLong();
|
||||
co_await asio::async_write(socket, asio::buffer(&size, sizeof(size)), asio::use_awaitable);
|
||||
// Send buffer
|
||||
std::vector<char> rawBuffer(size);
|
||||
buffer.SerializeToArray(rawBuffer.data(), rawBuffer.size());
|
||||
co_await asio::async_write(socket, asio::buffer(rawBuffer.data(), rawBuffer.size()), asio::use_awaitable);
|
||||
}
|
||||
|
||||
asio::awaitable<void> Client::sendPacket(const Packet& packet) {
|
||||
// Send operation
|
||||
co_await sendProtobuf(packet.op);
|
||||
// Send data
|
||||
if (packet.data) {
|
||||
co_await sendProtobuf(*packet.data);
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> Client::handlePacket(const Packet& packet) {
|
||||
// Check if authentication is required and completed
|
||||
if (packet.op.code() >= Generic::Operation::_MinAuth && !isAuthed()) {
|
||||
throw FatalError("Authentication required", Generic::Error::AuthenticationRequired);
|
||||
}
|
||||
// Handle the packet
|
||||
switch (packet.op.code()) {
|
||||
case Generic::Operation::Disconnect: {
|
||||
good = 0;
|
||||
} break;
|
||||
case Generic::Operation::SimpleAuth: {
|
||||
const Generic::SimpleAuth& authData = *static_cast<Generic::SimpleAuth*>(packet.data.get());
|
||||
// Check that username is not already taken
|
||||
if (std::find_if(clients.begin(), clients.end(), [&, this] (const auto& o) {return o->getUsername() == authData.username();}) != clients.end()) {
|
||||
throw Error("Username has already been taken", Generic::Error::UsernameAlreadyTaken);
|
||||
}
|
||||
// Check that username is "good" (no unprintable characters and stuff like that)
|
||||
//TODO...
|
||||
// Set username
|
||||
username = std::move(authData.username());
|
||||
// Send OK
|
||||
co_await sendOK();
|
||||
} break;
|
||||
default: {}
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
asio::awaitable<void> Client::connect() {
|
||||
// Send initial "OK"
|
||||
try {
|
||||
co_await sendOK();
|
||||
} catch (...) {
|
||||
good = false;
|
||||
}
|
||||
// Run while connection is good
|
||||
while (good) {
|
||||
std::optional<Error> error;
|
||||
try {
|
||||
co_await handlePacket(co_await receivePacket());
|
||||
} catch (FatalError& e) {
|
||||
error = std::move(e);
|
||||
good = false;
|
||||
} catch (Error& e) {
|
||||
error = std::move(e);
|
||||
} catch (std::exception& e) {
|
||||
error = Error("Internal server error: "+std::string(e.what()), Generic::Error::Unknown);
|
||||
good = false;
|
||||
}
|
||||
if (error.has_value()) {
|
||||
co_await sendPacket(error.value().packet);
|
||||
}
|
||||
}
|
||||
// Try to send disconnect
|
||||
try {
|
||||
Packet packet;
|
||||
packet.op.set_code(Generic::Operation::Disconnect);
|
||||
co_await sendPacket(packet);
|
||||
} catch (...) {}
|
||||
// Close socket
|
||||
boost::system::error_code ec;
|
||||
socket.close(ec);
|
||||
}
|
||||
|
||||
asio::awaitable<void> Server::listen(int port) {
|
||||
try {
|
||||
// Create acceptor
|
||||
asio::ip::tcp::acceptor acceptor(ioc, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port));
|
||||
// Handle all incoming connections
|
||||
while (true) {
|
||||
auto client = std::make_shared<Client>(ioc, clients);
|
||||
co_await acceptor.async_accept(client->getSocket(), asio::use_awaitable);
|
||||
asio::co_spawn(ioc, client->connect(), asio::detached);
|
||||
clients.push_back(std::move(client));
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "Failed to stay alive: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
111
server/Connection.hpp
Normal file
111
server/Connection.hpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#ifndef _CONNECTION_HPP
|
||||
#define _CONNECTION_HPP
|
||||
#include "generic.pb.h"
|
||||
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
|
||||
|
||||
|
||||
namespace Connection {
|
||||
using namespace boost;
|
||||
|
||||
|
||||
struct Packet {
|
||||
Generic::Operation op;
|
||||
std::unique_ptr<google::protobuf::Message> data = nullptr;
|
||||
};
|
||||
|
||||
struct Error : public std::runtime_error {
|
||||
Packet packet;
|
||||
|
||||
Error(const std::string& description, Generic::Error::Code code) : std::runtime_error(description) {
|
||||
packet.op.set_code(Generic::Operation::Error);
|
||||
auto error = std::make_unique<Generic::Error>();
|
||||
error->set_description(description);
|
||||
error->set_code(code);
|
||||
packet.data = std::move(error);
|
||||
}
|
||||
};
|
||||
|
||||
struct FatalError : public Error {
|
||||
using Error::Error;
|
||||
};
|
||||
|
||||
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
std::vector<std::shared_ptr<Client>>& clients;
|
||||
asio::ip::tcp::socket socket;
|
||||
std::string username;
|
||||
bool good = true;
|
||||
|
||||
public:
|
||||
Client(asio::io_context& ioc, std::vector<std::shared_ptr<Client>>& clients) : socket(ioc), clients(clients) {}
|
||||
|
||||
asio::awaitable<Packet> receivePacket();
|
||||
asio::awaitable<void> sendProtobuf(const google::protobuf::Message& buffer);
|
||||
asio::awaitable<void> sendPacket(const Packet& packet);
|
||||
asio::awaitable<void> handlePacket(const Packet& packet);
|
||||
asio::awaitable<void> connect();
|
||||
|
||||
template<class Buffer>
|
||||
asio::awaitable<Buffer> receiveProtobuf(uint16_t maxSize = 1024) {
|
||||
// Receive size
|
||||
uint16_t size;
|
||||
co_await asio::async_read(socket, asio::buffer(&size, sizeof(size)), asio::use_awaitable);
|
||||
// Check size
|
||||
if (size > maxSize) {
|
||||
throw FatalError("Buffer too large ("+std::to_string(size)+" > "+std::to_string(maxSize)+')', Generic::Error::TooLarge);
|
||||
}
|
||||
// Receive protobuf
|
||||
Buffer protobuf;
|
||||
if (size) {
|
||||
std::vector<char> rawProtobuf(size);
|
||||
co_await asio::async_read(socket, asio::buffer(rawProtobuf.data(), rawProtobuf.size()), asio::use_awaitable);
|
||||
// Parse protobuf
|
||||
protobuf.ParseFromArray(rawProtobuf.data(), static_cast<int>(size));
|
||||
}
|
||||
// Return it
|
||||
co_return protobuf;
|
||||
}
|
||||
|
||||
asio::awaitable<void> sendOK() {
|
||||
Packet packet;
|
||||
packet.op.set_code(Generic::Operation::OK);
|
||||
co_await sendPacket(packet);
|
||||
}
|
||||
|
||||
auto& getSocket() {
|
||||
return socket;
|
||||
}
|
||||
const auto& getUsername() const {
|
||||
return username;
|
||||
}
|
||||
bool isGood() const {
|
||||
return good;
|
||||
}
|
||||
bool isAuthed() const {
|
||||
return good && !username.empty();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Server {
|
||||
asio::io_context& ioc;
|
||||
std::vector<std::shared_ptr<Client>> clients;
|
||||
|
||||
public:
|
||||
Server(asio::io_context& ioc) : ioc(ioc) {}
|
||||
|
||||
asio::awaitable<void> listen(int port);
|
||||
};
|
||||
}
|
||||
#endif
|
130
server/World.cpp
Normal file
130
server/World.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include "World.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
|
||||
namespace World {
|
||||
void fillSettings(Generic::Settings& settings) {
|
||||
if (!settings.has_maxdistanceperturn()) {
|
||||
settings.set_maxdistanceperturn(0.02f);
|
||||
}
|
||||
if (!settings.has_robotsperclient()) {
|
||||
settings.set_robotsperclient(50);
|
||||
}
|
||||
if (!settings.has_maxrobothealth()) {
|
||||
settings.set_maxrobothealth(20);
|
||||
}
|
||||
if (!settings.has_maxrobotdamage()) {
|
||||
settings.set_maxrobotdamage(4);
|
||||
}
|
||||
if (!settings.has_protectcooldown()) {
|
||||
settings.set_protectcooldown(50);
|
||||
}
|
||||
if (!settings.has_healcooldown()) {
|
||||
settings.set_healcooldown(20);
|
||||
}
|
||||
if (!settings.has_healperturn()) {
|
||||
settings.set_healperturn(2);
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::awaitable<void> Playfield::reset() {
|
||||
robots.clear();
|
||||
const std::vector<std::vector<Vector2>> robotsPlacementMap = {
|
||||
{{0.5f, 0.5f}},
|
||||
{{0.0f, 0.0f}, {1.0f, 1.0f}},
|
||||
{{0.0f, 0.0f}, {0.5f, 0.5f}, {1.0f, 1.0f}},
|
||||
{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}}
|
||||
};
|
||||
const auto& robotsPlacement = robotsPlacementMap.at(clients.size());
|
||||
for (unsigned client = 0; client != clients.size(); client++) {
|
||||
const auto genericTeamRobotsPlacement = robotsPlacement[client].toGeneric();
|
||||
for (unsigned robot = 0; robot != settings.robotsperclient(); robot++) {
|
||||
// Create robot
|
||||
Generic::Robot genericRobot;
|
||||
genericRobot.set_id(robot);
|
||||
genericRobot.set_client(client);
|
||||
genericRobot.set_state(Generic::Robot::Idle);
|
||||
genericRobot.set_health(20);
|
||||
*genericRobot.mutable_position() = genericTeamRobotsPlacement;
|
||||
// Add to list
|
||||
robots.push_back({genericRobot});
|
||||
}
|
||||
}
|
||||
// Broadcast new playfield
|
||||
auto playfieldSync = getPlayfieldSync();
|
||||
for (auto& client : clients) {
|
||||
Connection::Packet packet;
|
||||
packet.op.set_code(Generic::Operation::RobotUpdate);
|
||||
packet.data = std::make_unique<Generic::PlayfieldSync>(playfieldSync);
|
||||
co_await client->sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::awaitable<void> Playfield::updateRobot(const Connection::Client *sender, Generic::Robot& newRobot) {
|
||||
turn++;
|
||||
// Get local robot
|
||||
auto& localRobot = getLocalRobot(sender, newRobot.id());
|
||||
// Get distance travelled
|
||||
auto distanceTravelled = getDistanceBetween(*localRobot, newRobot);
|
||||
if (distanceTravelled != 0.0f) {
|
||||
// Check that robot is able to travel
|
||||
if (newRobot.state() != Generic::Robot::Idle) {
|
||||
throw Connection::Error("Can't travel right now", Generic::Error::IllegalOperation);
|
||||
}
|
||||
// Check that robot didn't travel too far
|
||||
if (distanceTravelled > settings.maxdistanceperturn()) {
|
||||
throw Connection::Error("Can't travel that far ("+std::to_string(distanceTravelled)+" > "+std::to_string(settings.maxdistanceperturn())+')', Generic::Error::IllegalOperation);
|
||||
}
|
||||
}
|
||||
// Check for illegal changes
|
||||
if (localRobot->health() != newRobot.health()) {
|
||||
throw Connection::Error("Can't change value", Generic::Error::IllegalOperation);
|
||||
}
|
||||
// Apply heal
|
||||
if (newRobot.state() == Generic::Robot::Heal) {
|
||||
newRobot.set_health(localRobot->health() + settings.healperturn());
|
||||
if (newRobot.health() > settings.maxrobothealth()) {
|
||||
newRobot.set_health(settings.maxrobothealth());
|
||||
newRobot.set_state(Generic::Robot::Idle);
|
||||
}
|
||||
}
|
||||
// Check for state change
|
||||
if (localRobot->state() != newRobot.state()) {
|
||||
// Enforce cooldowns
|
||||
if (newRobot.state() == Generic::Robot::Protect && turn - localRobot.stateTimer < settings.protectcooldown()
|
||||
|| newRobot.state() == Generic::Robot::Heal && turn - localRobot.stateTimer < settings.healcooldown()) {
|
||||
throw Connection::Error("Can't change state yet (cooldown still in progress)", Generic::Error::IllegalOperation);
|
||||
}
|
||||
// Reset timer
|
||||
localRobot.stateTimer = turn;
|
||||
}
|
||||
// Apply changes to local robot
|
||||
*localRobot = newRobot;
|
||||
// Broadcast new robot
|
||||
for (auto& client : clients) {
|
||||
Connection::Packet packet;
|
||||
packet.op.set_code(Generic::Operation::RobotUpdate);
|
||||
packet.data = std::make_unique<Generic::Robot>(*localRobot);
|
||||
co_await client->sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
LocalRobot& Playfield::getLocalRobot(const Connection::Client *sender, unsigned robot) {
|
||||
// Check that new robot is in range
|
||||
if (robot >= robots.size()) {
|
||||
throw Connection::Error("Robot is out of range ("+std::to_string(robot)+" >= "+std::to_string(robots.size())+')', Generic::Error::OutOfRange);
|
||||
}
|
||||
// Get local robot
|
||||
auto& localRobot = robots[robot];
|
||||
// Check that robot belongs to sender
|
||||
if (clients[localRobot->client()].get() == sender) {
|
||||
throw Connection::Error("Robot does not belong to you", Generic::Error::IllegalOperation);
|
||||
}
|
||||
// Return local robot
|
||||
return localRobot;
|
||||
}
|
||||
}
|
97
server/World.hpp
Normal file
97
server/World.hpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
#ifndef _WORLD_HPP
|
||||
#define _WORLD_HPP
|
||||
#include "generic.pb.h"
|
||||
#include "Connection.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
|
||||
|
||||
|
||||
namespace World {
|
||||
void fillSettings(Generic::Settings& settings);
|
||||
|
||||
|
||||
struct Vector2 {
|
||||
float x, y;
|
||||
|
||||
inline static Vector2 makeFromGeneric(const Generic::Vector2& generic) {
|
||||
return {generic.x(), generic.y()};
|
||||
}
|
||||
void fromGeneric(const Generic::Vector2& generic) {
|
||||
x = generic.x();
|
||||
y = generic.y();
|
||||
}
|
||||
auto toGeneric() const {
|
||||
Generic::Vector2 fres;
|
||||
fres.set_x(x);
|
||||
fres.set_y(y);
|
||||
return fres;
|
||||
}
|
||||
|
||||
float getDistanceTo(const Vector2& o) const {
|
||||
return fabs(x - o.x) + fabs(y - o.y);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct LocalRobot {
|
||||
Generic::Robot generic;
|
||||
uint64_t stateTimer = 0;
|
||||
|
||||
auto& operator *() {
|
||||
return generic;
|
||||
}
|
||||
auto operator ->() {
|
||||
return &generic;
|
||||
}
|
||||
const auto& operator *() const {
|
||||
return generic;
|
||||
}
|
||||
const auto operator ->() const {
|
||||
return &generic;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Playfield {
|
||||
std::vector<LocalRobot> robots;
|
||||
Generic::Settings settings;
|
||||
uint64_t turn = 0;
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<Connection::Client>> clients;
|
||||
|
||||
Playfield(const std::vector<std::shared_ptr<Connection::Client>>& clients, const Generic::Settings& settings)
|
||||
: clients(clients), settings(settings) {
|
||||
fillSettings(this->settings);
|
||||
}
|
||||
|
||||
boost::asio::awaitable<void> reset();
|
||||
boost::asio::awaitable<void> updateRobot(const Connection::Client *sender, Generic::Robot& newRobot);
|
||||
LocalRobot& getLocalRobot(const Connection::Client *sender, unsigned robot);
|
||||
|
||||
float getDistanceBetween(const Generic::Robot& a, const Generic::Robot& b) {
|
||||
return Vector2::makeFromGeneric(a.position()).getDistanceTo(Vector2::makeFromGeneric(b.position()));
|
||||
}
|
||||
|
||||
auto getPlayfieldSync() const {
|
||||
Generic::PlayfieldSync fres;
|
||||
for (const auto& robot : robots) {
|
||||
*fres.add_robots() = robot.generic;
|
||||
}
|
||||
return fres;
|
||||
}
|
||||
bool isMasterClient(const Connection::Client *client) const {
|
||||
return client == clients.front().get();
|
||||
}
|
||||
const auto& getSettings() const {
|
||||
return settings;
|
||||
}
|
||||
auto getTurn() const {
|
||||
return turn;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
Loading…
Add table
Reference in a new issue