From 6432e3af8d26685d16a7a48fda528b3fdf2a4c6d Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Sat, 27 Dec 2025 20:16:57 +0800 Subject: [PATCH] Added NetworkManager Client and GameServer class --- CMakeLists.txt | 15 ++- README.md | 1 + src/network/NetData.h | 26 ++++++ src/network/NetworkManager.cpp | 74 +++++++++++++++ src/network/NetworkManager.h | 32 +++++++ src/network/client/Client.cpp | 99 ++++++++++++++++++++ src/network/client/Client.h | 42 +++++++++ src/network/server/GameServer.cpp | 116 ++++++++++++++++++++++++ src/network/server/GameServer.h | 30 ++++++ src/ui/managers/OnlineGameUIManager.cpp | 51 +++++++++++ 10 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 src/network/NetData.h create mode 100644 src/network/NetworkManager.cpp create mode 100644 src/network/NetworkManager.h create mode 100644 src/network/client/Client.cpp create mode 100644 src/network/client/Client.h create mode 100644 src/network/server/GameServer.cpp create mode 100644 src/network/server/GameServer.h create mode 100644 src/ui/managers/OnlineGameUIManager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 815a7cd..0366f1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,11 @@ set(SOURCE_FILES src/core/DebugManager.cpp src/ui/managers/debug/DebugOverlay.cpp src/core/Time.cpp + src/scenes/gameplay/OnlineGameScene.cpp + src/ui/managers/OnlineGameUIManager.cpp + src/network/client/Client.cpp + src/network/server/GameServer.cpp + src/network/NetworkManager.cpp ) @@ -56,10 +61,11 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${asio_SOURCE_DIR}/asio/include ) - +# ========== Windows: 定义宏 & 链接库 ========== +if (WIN32) target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE - + _WIN32_WINNT=0x0A00 ) target_link_libraries(${PROJECT_NAME} @@ -68,8 +74,11 @@ target_link_libraries(${PROJECT_NAME} SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image nlohmann_json::nlohmann_json + ws2_32 + mswsock + advapi32 ) - +endif() # ========== Windows: 复制 DLL ========== if (WIN32) # 查找 MinGW 运行时库 diff --git a/README.md b/README.md index 8f1b46d..0a1f2a9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ 一个棋类游戏,与孢子分裂有关,本为**cpp重构版**,游戏核心主要玩法由**cold1840**制定的,可以看他的[python版本](https://github.com/cold1840/SporeBG),这个版本旨在用**cpp**完全重写这个游戏,**注意本项目并未使用到里面的代码,核心逻辑实现是不一样的**,且本项目打算加入许多功能,**整体玩法也是完全不一样的**,同时**cold1840**也在一定程度上参与项目的开发与维护 ## 构建指南 +**mingw老版本有重大问题,导致asio内部状态损坏,引起段错误,如果是mingw请使用最新版本** 确保电脑安装了`cmake`和`ninja`,使用`gcc`与`g++`: ```bash git clone https://github.com/zhenyan121/SporeBG-Conid.git diff --git a/src/network/NetData.h b/src/network/NetData.h new file mode 100644 index 0000000..5c5d570 --- /dev/null +++ b/src/network/NetData.h @@ -0,0 +1,26 @@ +#pragma once +#include +struct NetData { + std::pair clickPosition = {0, 0}; + // 序列化,转换成字节数组 + void serialize(char* buffer) const { + // 暴力转换 + int* ptr = reinterpret_cast(buffer); + ptr[0] = clickPosition.first; + ptr[1] = clickPosition.second; + } + // 反序列化,从字节数组恢复 + static NetData deserialize(const char* buffer) { + // 暴力转换 + const int* ptr = reinterpret_cast(buffer); + return NetData{{ptr[0], ptr[1]}}; + } + // 大小(固定16字节:4个int) + static constexpr size_t size() { return 4 * sizeof(int); } +}; + +enum class NetType { + HOST, + CLIENT + +}; \ No newline at end of file diff --git a/src/network/NetworkManager.cpp b/src/network/NetworkManager.cpp new file mode 100644 index 0000000..a58800e --- /dev/null +++ b/src/network/NetworkManager.cpp @@ -0,0 +1,74 @@ +#include "NetworkManager.h" +#include + + +NetworkManager::~NetworkManager() { + +} + +void NetworkManager::init(NetType type) { + m_netType = type; + if (type == NetType::HOST) { + m_gameServer = std::make_shared(m_ioContext); + //std::cout << "try to start server\n"; + startServer(); + + } + m_client = std::make_shared(m_ioContext); +} + +/* +void NetworkManager::init(NetType type) { + // 先启动 io_context 线程 + m_ioThread = std::thread([this]() { + std::cout << "Network thread starting: " + << std::this_thread::get_id() << std::endl; + m_ioContext.run(); + }); + std::cout << "Main thread after starting network thread: " + << std::this_thread::get_id() << std::endl; + // 等待网络线程启动 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::cout << "Initializing NetworkManager on thread: " + << std::this_thread::get_id() << std::endl; + // 在网络线程中初始化 + asio::post(m_ioContext, [this, type]() { + std::cout << "NetworkManager init running on thread: " + << std::this_thread::get_id() << std::endl; + // 在这里创建 GameServer 和调用 async_accept + if (type == NetType::HOST) { + m_gameServer = std::make_shared(m_ioContext); + m_gameServer->startServer(52025); + } + }); + }*/ +void NetworkManager::startServer() { + + if (!m_gameServer) { + std::cerr << "gameServer is not esist\n"; + } + m_gameServer->startServer(52025); + + std::cout << "start server success\n"; + m_ioContext.run(); + +} + +void NetworkManager::startClient() { + m_client->setCallbackes( + [](const NetData& click) { + /* 处理对手棋步 */ + }, + []() { + /* 提示用户走棋 */ + std::cout << "It's your turn now!\n"; + } + ); + if (m_netType == NetType::HOST) { + m_client->connect("127.0.0.1", 52025, true); + } + if (m_netType == NetType::CLIENT) { + m_client->connect("127.0.0.1", 52025, false); + m_ioContext.run(); + } +} \ No newline at end of file diff --git a/src/network/NetworkManager.h b/src/network/NetworkManager.h new file mode 100644 index 0000000..60cac53 --- /dev/null +++ b/src/network/NetworkManager.h @@ -0,0 +1,32 @@ +#pragma once + +#include "network/client/Client.h" +#include "network/server/GameServer.h" + +class NetworkManager { +public: + NetworkManager() = default; + + + ~NetworkManager(); + + + void init(NetType type); + + + +private: + + //asio::ip::tcp::acceptor m_acceptor; + + std::shared_ptr m_gameServer = nullptr; + std::shared_ptr m_client = nullptr; + NetType m_netType; + + std::thread m_ioThread; + + asio::io_context m_ioContext; + void startServer(); + + void startClient(); +}; \ No newline at end of file diff --git a/src/network/client/Client.cpp b/src/network/client/Client.cpp new file mode 100644 index 0000000..d55cf77 --- /dev/null +++ b/src/network/client/Client.cpp @@ -0,0 +1,99 @@ +#include "Client.h" +#include +Client::Client(asio::io_context& ioContext): + m_resolver(ioContext), + m_socket(ioContext) +{ + // 构造函数实现 +} + +void Client::setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn) { + m_onOpponentMove = onOpponentMove; + m_onMyTurn = onMyTurn; +} + +void Client::connect(const std::string& host, int port, bool iAmFirst) { + m_host = host; + m_port = port; + //用shared_ptr保持对象存活 + auto self = shared_from_this(); + + std::cout << "try to resolve: " << host << ":" << port << std::endl; + // 屎山一样的lambda + m_resolver.async_resolve(host, std::to_string(port), + [this, self, iAmFirst](const asio::error_code& ec, asio::ip::tcp::resolver::results_type results) { + if (!ec) { + asio::async_connect(m_socket, results, + [this, self, iAmFirst](const asio::error_code& ec, const asio::ip::tcp::endpoint& /*endpoint*/) { + if (!ec) { + onConnected(iAmFirst); + } else { + // 处理连接错误 + std::cerr << "connect failed: " << ec.message() << std::endl; + } + }); + } else { + std::cerr << "resolve failed: " << ec.message() << std::endl; + } + }); +} + +void Client::onConnected(bool iAmFirst) { + std::cout << "Connected to server " << m_host << ":" << m_port << std::endl; + + // 如果是先手,触发回调 + if (iAmFirst && m_onMyTurn) { + m_onMyTurn(); + } else { + // 等待对手动作 + waitForOpponent(); + } +} + + +// 发送点击位置数据给对手 + +void Client::sentClickPosition(const NetData& data) { + auto self = shared_from_this(); + char buffer[NetData::size()]; + data.serialize(buffer); + asio::async_write(m_socket, asio::buffer(buffer, NetData::size()), + [this, self](const asio::error_code& ec, std::size_t /*bytesTransferred*/) { + if (!ec) { + // 发送成功,等待对手动作 + waitForOpponent(); + } else { + std::cerr << "send failed: " << ec.message() << std::endl; + } + }); +} + +void Client::waitForOpponent() { + auto self = shared_from_this(); + m_socket.async_read_some( + asio::buffer(m_readBuffer, NetData::size()), + [this, self](const asio::error_code& ec, std::size_t bytesTransferred) { + if (!ec) { + + if (bytesTransferred == NetData::size()) { + NetData netData = NetData::deserialize(m_readBuffer); + // 触发对手移动回调 + if (m_onOpponentMove) { + m_onOpponentMove(netData); + } + // 轮到我了 + if (m_onMyTurn) { + m_onMyTurn(); + } + // 重置读取缓冲区以准备下一次读取 + + } else { + std::cerr << "Incomplete data received from opponent." << std::endl; + } + } else { + std::cerr << "read failed: " << ec.message() << std::endl; + } + } + ); +} + diff --git a/src/network/client/Client.h b/src/network/client/Client.h new file mode 100644 index 0000000..15d5da5 --- /dev/null +++ b/src/network/client/Client.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include "network/NetData.h" + +class Client : public std::enable_shared_from_this { +public: + + // 事件回调类型 + using MoveCallback = std::function; + using TurnCallback = std::function; + + Client(asio::io_context& ioContext); + ~Client() = default; + + void setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn); + + void connect(const std::string& host, int port, bool iAmFirst = true); + + void sentClickPosition(const NetData& data); + + + + +private: + asio::ip::tcp::resolver m_resolver; + asio::ip::tcp::socket m_socket; + std::string m_host; + int m_port; + MoveCallback m_onOpponentMove; + TurnCallback m_onMyTurn; + char m_readBuffer[NetData::size()]; + + + void onConnected(bool iAmFirst); + void waitForOpponent(); + + +}; \ No newline at end of file diff --git a/src/network/server/GameServer.cpp b/src/network/server/GameServer.cpp new file mode 100644 index 0000000..3005331 --- /dev/null +++ b/src/network/server/GameServer.cpp @@ -0,0 +1,116 @@ +#include "GameServer.h" +#include +//#include +GameServer::GameServer(asio::io_context& ioContext) + : m_ioContext(ioContext), + m_player1(ioContext), + m_player2(ioContext), + m_acceptor(ioContext) +{ + +} + +void GameServer::startServer(int port) { + + std::cout << "Starting server on port " << port << std::endl; + //std::cout << "startServer called on thread: " + // << std::this_thread::get_id() << std::endl; + // 同步初始化(通常这部分是同步的) + asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), port); + m_acceptor.open(endpoint.protocol()); + m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); + m_acceptor.bind(endpoint); + m_acceptor.listen(); + + // 开始异步接受连接 + waitForPlayers(1); + + + +} + +void GameServer::waitForPlayers(int playerNum) { + + /*if (!m_acceptor) { + std::cerr << "ERROR: m_acceptor is null in waitForPlayers!" << std::endl; + return; + }*/ + + std::cout << "waitForPlayers called for player " << playerNum << std::endl; + + //auto& socket = (playerNum == 1) ? m_player1 : m_player2; + std::cout << "accepting player " << playerNum << std::endl; + // 捕获 shared_ptr 确保对象存活 + //auto self = shared_from_this(); + try { + std::cout << "Before async_accept for player " << playerNum << std::endl; + + /*if (!m_acceptor || !m_acceptor->is_open()) { + std::cerr << "Acceptor is not open!" << std::endl; + return; + }*/ + // 保存当前对象的原始指针(用于调试) + /*static std::atomic lastThisPtr = nullptr; + lastThisPtr = this; + std::cout << "waitForPlayers: this=" << this + << ", playerNum=" << playerNum << std::endl;*/ + //std::cout << "acceptor open: " << m_acceptor->is_open() << "\n"; + //std::cout << "socket open: " << socket.is_open() << "\n"; + // mingw老版本有重大问题,导致asio内部状态损坏,引起段错误!!! + m_acceptor.async_accept( + [this, playerNum](const asio::error_code& ec, asio::ip::tcp::socket peerSocket) { + if (!ec) { + std::cout << "player" << playerNum << " connect successfully\n"; + if (playerNum == 1) { + m_player1 = std::move(peerSocket); + std::cout << "try to accept player2\n"; + waitForPlayers(2); + } else { + m_player2 = std::move(peerSocket); + startGame(); + } + } else { + std::cerr << "Error accepting player " << playerNum << ": " << ec.message() << std::endl; + } + } + ); + std::cout << "After async_accept for player " << playerNum << std::endl; + } catch (const std::exception& e) { + std::cerr << "Exception in waitForPlayers: " << e.what() << std::endl; + } +} + +void GameServer::startGame() { + std::cout << "Gmae Start player1 is the first\n"; + + forwardMoves(); +} + +void GameServer::forwardMoves() { + listenPlayer(m_player1, m_player2); + listenPlayer(m_player2, m_player1); +} + +void GameServer::listenPlayer(asio::ip::tcp::socket& fromPlayer, asio::ip::tcp::socket& toPlayer) { + auto self = shared_from_this(); + + fromPlayer.async_read_some( + asio::buffer(m_buffer, NetData::size()), + [this, self, &fromPlayer, &toPlayer](const asio::error_code& ec, size_t bytes) { + if (!ec) { + if (bytes == NetData::size()) { + // 转发给对手 + asio::async_write( + toPlayer, + asio::buffer(m_buffer, bytes), + [](const asio::error_code& ec, size_t) { + + } + + ); + listenPlayer(fromPlayer, toPlayer); + } + } + } + ); +} \ No newline at end of file diff --git a/src/network/server/GameServer.h b/src/network/server/GameServer.h new file mode 100644 index 0000000..8d04988 --- /dev/null +++ b/src/network/server/GameServer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include "network/NetData.h" + + + +class GameServer : public std::enable_shared_from_this { +public: + GameServer(asio::io_context& ioContext); + ~GameServer() = default; + + void startServer( int port); + +private: + asio::io_context& m_ioContext; + //std::unique_ptr m_acceptor; + asio::ip::tcp::acceptor m_acceptor; + asio::ip::tcp::socket m_player1; + asio::ip::tcp::socket m_player2; + char m_buffer[NetData::size()]; + + void waitForPlayers(int playerNumber); + + void startGame(); + + void forwardMoves(); + + void listenPlayer(asio::ip::tcp::socket& fromPlayer, asio::ip::tcp::socket& toPlayer); +}; \ No newline at end of file diff --git a/src/ui/managers/OnlineGameUIManager.cpp b/src/ui/managers/OnlineGameUIManager.cpp new file mode 100644 index 0000000..d1f611c --- /dev/null +++ b/src/ui/managers/OnlineGameUIManager.cpp @@ -0,0 +1,51 @@ +#include "OnlineGameUIManager.h" +#include "ui/base/UIWidgetFactory.h" +OnlineGameUIManager::OnlineGameUIManager(SceneEventCallback eventCallback) + : GameUIManager(eventCallback) { +} + +OnlineGameUIManager::~OnlineGameUIManager() { +} + +void OnlineGameUIManager::init() { + GameUIManager::init(); + // 在这里可以添加在线游戏特有的UI组件 + + auto it = m_buttons.find(makeHash("ActionButton")); + if (it != m_buttons.end()) { + auto& actionButton = it->second; + actionButton->setEnabled(false); // 在线游戏中初始禁用 + } + + auto onlineHostButton = UIWidgetFactory::createStandardButton( + "OnlineHostButton", + "Host Game", + 200, + 100, + [this]() { + // host 模式 + if(m_onlineTypeEvent) { + m_onlineTypeEvent(NetType::HOST); + } + } + ); + m_buttons.emplace(onlineHostButton->getNameHash(), std::move(onlineHostButton)); + auto onlineJoinButton = UIWidgetFactory::createStandardButton( + "OnlineJoinButton", + "Join Game", + 400, + 100, + [this]() { + + if(m_onlineTypeEvent) { + m_onlineTypeEvent(NetType::CLIENT); + } + } + ); + m_buttons.emplace(onlineJoinButton->getNameHash(), std::move(onlineJoinButton)); +} + +void OnlineGameUIManager::setOnlineTypeCallback(OnlineTypeEvent onlineTypeEvent) { + m_onlineTypeEvent = onlineTypeEvent; +} +