feat: add online functionality

This commit is contained in:
2025-12-28 16:24:00 +08:00
parent ef8011c090
commit 0b937336c2
14 changed files with 407 additions and 80 deletions

View File

@@ -1,19 +1,34 @@
#pragma once
#include <utility>
enum class NetDataType {
CLICK_POSITION,
GAME_START
};
struct NetData {
std::pair<int, int> clickPosition = {0, 0};
int firstPlayer = 1;
NetDataType type = NetDataType::CLICK_POSITION;
// 序列化,转换成字节数组
void serialize(char* buffer) const {
// 暴力转换
int* ptr = reinterpret_cast<int*>(buffer);
ptr[0] = clickPosition.first;
ptr[1] = clickPosition.second;
ptr[0] = static_cast<int>(type);
ptr[1] = clickPosition.first;
ptr[2] = clickPosition.second;
ptr[3] = firstPlayer;
}
// 反序列化,从字节数组恢复
static NetData deserialize(const char* buffer) {
// 暴力转换
const int* ptr = reinterpret_cast<const int*>(buffer);
return NetData{{ptr[0], ptr[1]}};
NetData data;
data.type = static_cast<NetDataType>(ptr[0]);
data.clickPosition = {ptr[1], ptr[2]};
data.firstPlayer = ptr[3];
return data;
}
// 大小固定16字节4个int
static constexpr size_t size() { return 4 * sizeof(int); }

View File

@@ -29,6 +29,10 @@ void NetworkManager::init(NetType type) {
std::cout << "client started\n";
}
void NetworkManager::setClickEventCallback(ClickEventCallback callback) {
m_clickEventCallback = callback;
}
/*
void NetworkManager::init(NetType type) {
// 先启动 io_context 线程
@@ -68,12 +72,23 @@ void NetworkManager::startServer() {
void NetworkManager::startClient() {
m_client->setCallbackes(
[](const NetData& click) {
[this](const NetData& click) {
/* 处理对手棋步 */
if (m_clickEventCallback) {
std::cout << "Received opponent move: ("
<< click.clickPosition.first << ", "
<< click.clickPosition.second << ")\n";
m_clickEventCallback(click.clickPosition.first, click.clickPosition.second);
}
},
[]() {
/* 提示用户走棋 */
std::cout << "It's your turn now!\n";
std::cout << "NetworkManager:It's your turn now!\n";
},
[this]() {
/* 游戏开始回调 */
m_startGameCallback();
std::cout << "Game has started!\n";
}
);
if (m_netType == NetType::HOST) {
@@ -100,4 +115,24 @@ void NetworkManager::startIOContextLoop() {
});
std::cout << "IO context loop started on thread: "
<< m_ioThread.joinable() << std::endl;
}
void NetworkManager::postClickPosition(int logicalX, int logicalY, bool isChangeTurn) {
if (m_client) {
NetData data;
data.clickPosition = {logicalX, logicalY};
// 发送位置并告诉对手是否换回合
m_client->sentClickPosition(data, isChangeTurn);
std::cout << "Posted click position: ("
<< logicalX << ", " << logicalY << ")\n";
}
}
void NetworkManager::setIsMyTurn(bool isMyTurn) {
m_isMyTurn = isMyTurn;
if (m_client) {
// 如果不是我的回合,则客户端应该等待对手
m_client->setShouldWait(!isMyTurn);
}
}

View File

@@ -5,6 +5,8 @@
class NetworkManager {
public:
using ClickEventCallback = std::function<void(int logicalX, int logicalY)>;
using StartGameCallback = std::function<void()>;
NetworkManager();
@@ -13,7 +15,15 @@ public:
void init(NetType type);
void setClickEventCallback(ClickEventCallback callback);
void setStartGameCallback(StartGameCallback callback) {
m_startGameCallback = callback;
}
void setIsMyTurn(bool isMyTurn);
bool isMyTurn() const { return m_isMyTurn; }
void postClickPosition(int logicalX, int logicalY, bool isChangeTurn = false);
private:
// 一定要在最前面
@@ -28,7 +38,10 @@ private:
asio::io_context::executor_type> m_workguard;
std::thread m_ioThread;
ClickEventCallback m_clickEventCallback;
StartGameCallback m_startGameCallback;
bool m_isMyTurn = false; // 新增:当前是否是我的回合
void startServer();
void startClient();

View File

@@ -7,14 +7,22 @@ Client::Client(asio::io_context& ioContext):
// 构造函数实现
}
void Client::setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn) {
void Client::setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn, TurnCallback onGameStart) {
m_onOpponentMove = onOpponentMove;
m_onMyTurn = onMyTurn;
m_onGameStart = onGameStart;
}
void Client::stopWaiting() {
m_shouldWait = false;
m_isWaiting = false;
}
void Client::connect(const std::string& host, int port, bool iAmFirst) {
m_host = host;
m_port = port;
m_isHost = iAmFirst;
m_shouldWait = true; // 连接后开始等待
//用shared_ptr保持对象存活
auto self = shared_from_this();
@@ -40,28 +48,41 @@ void Client::connect(const std::string& host, int port, bool iAmFirst) {
void Client::onConnected(bool iAmFirst) {
std::cout << "Connected to server " << m_host << ":" << m_port << std::endl;
// 如果是先手,触发回调
if (iAmFirst && m_onMyTurn) {
m_onMyTurn();
} else {
// 等待对手动作
// 重置等待状态
m_shouldWait = true;
m_isWaiting = false;
// 开始等待对手消息
if (m_shouldWait) {
waitForOpponent();
}
}
// 发送点击位置数据给对手
void Client::sentClickPosition(const NetData& data) {
void Client::sentClickPosition(const NetData& data, bool isChangeTurn) {
auto self = shared_from_this();
NetData sendData = data;
sendData.type = NetDataType::CLICK_POSITION;
char buffer[NetData::size()];
data.serialize(buffer);
sendData.serialize(buffer);
asio::async_write(m_socket, asio::buffer(buffer, NetData::size()),
[this, self](const asio::error_code& ec, std::size_t /*bytesTransferred*/) {
[this, self, isChangeTurn](const asio::error_code& ec, std::size_t /*bytesTransferred*/) {
if (!ec) {
// 发送成功,等待对手动作
waitForOpponent();
// 如果需要转换回合,则开始等待对手
if (isChangeTurn) {
m_isMyTurn = false;
m_shouldWait = true;
if (!m_isWaiting) {
waitForOpponent();
}
} else {
m_isMyTurn = true;
}
} else {
std::cerr << "send failed: " << ec.message() << std::endl;
}
@@ -69,6 +90,9 @@ void Client::sentClickPosition(const NetData& data) {
}
void Client::waitForOpponent() {
if (!m_shouldWait || m_isWaiting) {
return;
}
auto self = shared_from_this();
m_socket.async_read_some(
asio::buffer(m_readBuffer, NetData::size()),
@@ -77,21 +101,69 @@ void Client::waitForOpponent() {
if (bytesTransferred == NetData::size()) {
NetData netData = NetData::deserialize(m_readBuffer);
// 触发对手移动回调
if (m_onOpponentMove) {
m_onOpponentMove(netData);
// 检查消息类型
if (netData.type == NetDataType::GAME_START) {
std::cout << "Game started! First player is: " << netData.firstPlayer << std::endl;
// 判断自己是否是先手
bool iAmFirst = (netData.firstPlayer == 1 && m_isHost) ||
(netData.firstPlayer == 2 && !m_isHost);
m_isMyTurn = iAmFirst;
if (m_onGameStart) {
m_onGameStart();
}
if (m_isMyTurn && m_onMyTurn) {
std::cout << "It's your turn now! (You are first)\n";
m_onMyTurn();
} else if (!m_isMyTurn && m_onOpponentMove) {
// 如果不是先手,等待对手走棋
std::cout << "Waiting for opponent to move...\n";
// 可以在这里触发一个等待对手的回调,如果需要的话
if (m_shouldWait) {
waitForOpponent();
}
}
}
// 轮到我了
if (m_onMyTurn) {
m_onMyTurn();
else if (netData.type == NetDataType::CLICK_POSITION) {
// 正常的对手移动
if (m_onOpponentMove) {
std::cout << "Received opponent move: ("
<< netData.clickPosition.first << ", "
<< netData.clickPosition.second << ")\n";
m_onOpponentMove(netData);
}
// 现在轮到我了
//m_isMyTurn = true;
if (m_isMyTurn) {
m_shouldWait = false;
if (m_onMyTurn) {
m_onMyTurn();
}
} else {
if (m_shouldWait) {
waitForOpponent();
}
}
}
// 重置读取缓冲区以准备下一次读取
} else {
std::cerr << "Incomplete data received from opponent." << std::endl;
if (m_shouldWait) {
waitForOpponent();
}
}
} else {
std::cerr << "read failed: " << ec.message() << std::endl;
// 发生错误时,可以选择重新等待对手
if (m_shouldWait) {
waitForOpponent();
}
}
}
);

View File

@@ -15,12 +15,13 @@ public:
Client(asio::io_context& ioContext);
~Client() = default;
void setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn);
// 设置等待状态
void setShouldWait(bool shouldWait) { m_shouldWait = shouldWait; }
void setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn, TurnCallback onGameStart);
void connect(const std::string& host, int port, bool iAmFirst = true);
void sentClickPosition(const NetData& data);
void sentClickPosition(const NetData& data, bool isChangeTurn = false);
@@ -30,13 +31,21 @@ private:
asio::ip::tcp::socket m_socket;
std::string m_host;
int m_port;
bool m_isHost = false;
MoveCallback m_onOpponentMove;
TurnCallback m_onMyTurn;
TurnCallback m_onGameStart;
char m_readBuffer[NetData::size()];
// 新增状态控制
bool m_shouldWait = false; // 是否应该等待对手
bool m_isWaiting = false; // 当前是否正在等待
bool m_isMyTurn = false; // 是否是我的回合
void onConnected(bool iAmFirst);
void waitForOpponent();
void stopWaiting(); // 停止等待
};

View File

@@ -83,6 +83,37 @@ void GameServer::waitForPlayers(int playerNum) {
void GameServer::startGame() {
std::cout << "Gmae Start player1 is the first\n";
NetData gameStartMsg;
gameStartMsg.type = NetDataType::GAME_START;
gameStartMsg.firstPlayer = 1; // 玩家1先手
gameStartMsg.clickPosition = {-1, -1}; // 特殊值表示游戏开始
char buffer1[NetData::size()];
char buffer2[NetData::size()];
// player1
gameStartMsg.serialize(buffer1);
//player2
gameStartMsg.serialize(buffer2);
// 发送游戏开始消息给两个玩家
asio::async_write(m_player1, asio::buffer(buffer1, NetData::size()),
[](const asio::error_code& ec, size_t) {
if (ec) {
std::cerr << "Failed to send start message to player1: " << ec.message() << std::endl;
} else {
std::cout << "Game start message sent to player1\n";
}
});
asio::async_write(m_player2, asio::buffer(buffer2, NetData::size()),
[](const asio::error_code& ec, size_t) {
if (ec) {
std::cerr << "Failed to send start message to player2: " << ec.message() << std::endl;
} else {
std::cout << "Game start message sent to player2\n";
}
});
forwardMoves();
}