feat(server): implement Uno server functionality

- Added `UnoServer` class to handle player actions, game state, and networking.
- Supported message handling for joining, starting, drawing, and playing cards.
- Integrated `ServerGameState` with `NetworkServer` for multiplayer game logic.
- Created `main.cpp` to initialize and run the Uno server.
This commit is contained in:
Kieran Kihn
2025-12-02 12:28:59 +08:00
parent 4b498e3b4a
commit 0d5a187cba
3 changed files with 177 additions and 0 deletions

105
src/server/UnoServer.cpp Normal file
View File

@@ -0,0 +1,105 @@
/**
* @file UnoServer.cpp
*
* @author Yuzhe Guo
* @date 2025.12.01
*/
#include "UnoServer.h"
#include "../network/MessageSerializer.h"
namespace UNO::SERVER {
UnoServer::UnoServer(uint16_t port) :
networkServer_(port, [this](int playerId, const std::string &message) { this->handlePlayerMessage(playerId, message); }),
playerCount(0)
{
}
void UnoServer::handlePlayerMessage(size_t playerId, const std::string &message)
{
auto playerMessage = NETWORK::MessageSerializer::deserialize(message);
if (playerMessage.getMessageStatus() == NETWORK::MessageStatus::OK) {
if (playerMessage.getMessagePayloadType() == NETWORK::MessagePayloadType::JOIN_GAME) {
auto playerName = std::get<NETWORK::JoinGamePayload>(playerMessage.getMessagePayload()).playerName;
this->networkIdToGameId[playerId] = this->playerCount;
this->gameIdToNetworkId[this->playerCount] = playerId;
this->playerCount++;
this->serverGameState_.addPlayer(GAME::ServerPlayerState{playerName, 0, false});
}
if (playerMessage.getMessagePayloadType() == NETWORK::MessagePayloadType::START_GAME) {
this->isReadyToStart[networkIdToGameId[playerId]] = true;
for (size_t i = 0; i <= this->playerCount; i++) {
if (i == this->playerCount) {
this->handleStartGame();
break;
}
if (isReadyToStart[i] == false) {
break;
}
}
}
if (playerMessage.getMessagePayloadType() == NETWORK::MessagePayloadType::INIT_GAME
|| playerMessage.getMessagePayloadType() == NETWORK::MessagePayloadType::END_GAME) {
throw std::invalid_argument("Invalid message payload type from client");
}
if (this->networkIdToGameId.at(playerId)
!= this->serverGameState_.getCurrentPlayer() - this->serverGameState_.getPlayers().begin()) {
throw std::invalid_argument("Invalid player message: not this player's turn");
}
if (playerMessage.getMessagePayloadType() == NETWORK::MessagePayloadType::DRAW_CARD) {
this->handleDrawCard(playerId);
}
if (playerMessage.getMessagePayloadType() == NETWORK::MessagePayloadType::PLAY_CARD) {
this->handlePlayCard(playerId, std::get<NETWORK::PlayCardPayload>(playerMessage.getMessagePayload()).card);
}
}
}
void UnoServer::handleStartGame()
{
serverGameState_.init();
for (size_t i = 0; i < playerCount; i++) {
NETWORK::InitGamePayload payload = {serverGameState_.getDiscardPile(), serverGameState_.getPlayers()[i].getCards(), 0};
this->networkServer_.send(
gameIdToNetworkId.at(i),
NETWORK::MessageSerializer::serialize({NETWORK::MessageStatus::OK, NETWORK::MessagePayloadType::INIT_GAME, payload}));
}
}
void UnoServer::handleDrawCard(size_t playerId)
{
auto cards = this->serverGameState_.updateStateByDraw();
for (size_t i = 0; i < playerCount; i++) {
auto networkId = gameIdToNetworkId.at(i);
NETWORK::DrawCardPayload payload;
if (networkId != playerId) {
payload = {cards.size(), {}};
}
else {
payload = {cards.size(), cards};
}
this->networkServer_.send(
networkId,
NETWORK::MessageSerializer::serialize({NETWORK::MessageStatus::OK, NETWORK::MessagePayloadType::DRAW_CARD, payload}));
}
}
void UnoServer::handlePlayCard(size_t playerId, GAME::Card card)
{
this->serverGameState_.updateStateByCard(card);
NETWORK::PlayCardPayload payload = {card};
auto message = NETWORK::MessageSerializer::serialize({NETWORK::MessageStatus::OK, NETWORK::MessagePayloadType::PLAY_CARD, payload});
for (size_t i = 0; i < playerCount; i++) {
this->networkServer_.send(gameIdToNetworkId.at(i), message);
}
}
void UnoServer::run()
{
this->networkServer_.run();
}
} // namespace UNO::SERVER

58
src/server/UnoServer.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* @file UnoServer.h
*
* @author Yuzhe Guo
* @date 2025.12.01
*/
#pragma once
#include "../game/GameState.h"
#include "../network/NetworkServer.h"
namespace UNO::SERVER {
class UnoServer {
private:
GAME::ServerGameState serverGameState_;
NETWORK::NetworkServer networkServer_;
size_t playerCount;
std::map<size_t, size_t> gameIdToNetworkId;
std::map<size_t, size_t> networkIdToGameId;
std::map<size_t, bool> isReadyToStart;
private:
/**
* 处理玩家消息
* @param playerId 玩家 ID
* @param message 玩家消息
*/
void handlePlayerMessage(size_t playerId, const std::string &message);
/**
* 开始游戏
*/
void handleStartGame();
/**
* 处理玩家摸牌事件
* @param playerId 摸牌的玩家的网络 ID
*/
void handleDrawCard(size_t playerId);
/**
* 处理玩家出牌事件
* @param playerId 出牌的玩家的网络 ID
* @param card 玩家出的牌
*/
void handlePlayCard(size_t playerId, GAME::Card card);
public:
explicit UnoServer(uint16_t port = 10001);
/**
* 启动服务器
*/
void run();
};
} // namespace UNO::SERVER

14
src/server/main.cpp Normal file
View File

@@ -0,0 +1,14 @@
/**
* @file main.cpp
*
* @author Yuzhe Guo
* @date 2025.12.01
*/
#include "UnoServer.h"
int main()
{
UNO::SERVER::UnoServer uno_server;
uno_server.run();
return 0;
}