From 1c62588c380fb1c0b4cfb05d00851d86242914c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 29 Jul 2026 09:16:03 +0200 Subject: [PATCH] PR 0: introduce GameConfig, strategy pattern, and refactor Server_Game constructor - Add GameConfig struct consolidating 14 construction parameters - Add strategy interfaces for deck validation, game lifecycle, and match results - Add Server_MatchGameFactory abstract factory (forward-looking for tournaments) - Replace 15-arg Server_Game constructor with (GameConfig, Server_Room*) - Wire default strategies into Server_Game with lifecycle/match hooks - Update server_protocolhandler to build GameConfig from protobuf - Update reverse_card_move_test to use new constructor - Update CMakeLists.txt with new headers Took 7 minutes Took 2 minutes --- .../network/server/remote/CMakeLists.txt | 4 ++ .../network/server/remote/game/game_config.h | 27 ++++++++ .../game/server_deck_validation_strategy.h | 29 ++++++++ .../server/remote/game/server_game.cpp | 68 ++++++++++++------- .../network/server/remote/game/server_game.h | 34 ++++++---- .../game/server_game_lifecycle_strategy.h | 29 ++++++++ .../remote/game/server_match_game_factory.h | 18 +++++ .../game/server_match_result_strategy.h | 24 +++++++ .../server/remote/server_protocolhandler.cpp | 22 ++++-- .../movecard_tests/reverse_card_move_test.cpp | 8 ++- 10 files changed, 219 insertions(+), 44 deletions(-) create mode 100644 libcockatrice_network/libcockatrice/network/server/remote/game/game_config.h create mode 100644 libcockatrice_network/libcockatrice/network/server/remote/game/server_deck_validation_strategy.h create mode 100644 libcockatrice_network/libcockatrice/network/server/remote/game/server_game_lifecycle_strategy.h create mode 100644 libcockatrice_network/libcockatrice/network/server/remote/game/server_match_game_factory.h create mode 100644 libcockatrice_network/libcockatrice/network/server/remote/game/server_match_result_strategy.h diff --git a/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt index e883baa0d..23945c17d 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt @@ -10,7 +10,11 @@ set(HEADERS game/server_card.h game/server_cardzone.h game/server_counter.h + game/game_config.h + game/server_deck_validation_strategy.h game/server_game.h + game/server_game_lifecycle_strategy.h + game/server_match_result_strategy.h game/server_player.h game/server_spectator.h server.h diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/game_config.h b/libcockatrice_network/libcockatrice/network/server/remote/game/game_config.h new file mode 100644 index 000000000..0052e1451 --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/game_config.h @@ -0,0 +1,27 @@ +#ifndef GAME_CONFIG_H +#define GAME_CONFIG_H + +#include +#include +#include + +class GameConfig +{ +public: + ServerInfo_User creatorInfo; + int gameId = -1; + QString description; + QString password; + int maxPlayers = 2; + QList gameTypes; + bool onlyBuddies = false; + bool onlyRegistered = false; + bool spectatorsAllowed = false; + bool spectatorsNeedPassword = false; + bool spectatorsCanTalk = true; + bool spectatorsSeeEverything = true; + int startingLifeTotal = 20; + bool shareDecklistsOnLoad = true; +}; + +#endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_deck_validation_strategy.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_deck_validation_strategy.h new file mode 100644 index 000000000..4b321850d --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_deck_validation_strategy.h @@ -0,0 +1,29 @@ +#ifndef SERVER_DECK_VALIDATION_STRATEGY_H +#define SERVER_DECK_VALIDATION_STRATEGY_H + +#include + +class DeckList; +class Server_Game; +class Server_Player; +class ResponseContainer; + +class Server_DeckValidationStrategy +{ +public: + virtual ~Server_DeckValidationStrategy() = default; + + virtual Response::ResponseCode + validate(Server_Game *game, Server_Player *player, DeckList *deck, ResponseContainer &rc) = 0; +}; + +class Server_DefaultDeckValidationStrategy : public Server_DeckValidationStrategy +{ +public: + Response::ResponseCode validate(Server_Game *, Server_Player *, DeckList *, ResponseContainer &) override + { + return Response::RespOk; + } +}; + +#endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 4761199e5..c324932f3 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -53,34 +53,22 @@ #include #include -Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, - int _gameId, - const QString &_description, - const QString &_password, - int _maxPlayers, - const QList &_gameTypes, - bool _onlyBuddies, - bool _onlyRegistered, - bool _spectatorsAllowed, - bool _spectatorsNeedPassword, - bool _spectatorsCanTalk, - bool _spectatorsSeeEverything, - int _startingLifeTotal, - bool _shareDecklistsOnLoad, - Server_Room *_room) - : QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)), - gameStarted(false), gameClosed(false), gameId(_gameId), password(_password), maxPlayers(_maxPlayers), - gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies), - onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed), - spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk), - spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal), - shareDecklistsOnLoad(_shareDecklistsOnLoad), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), - firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), +Server_Game::Server_Game(const GameConfig &config, Server_Room *_room) + : QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(config.creatorInfo)), + gameStarted(false), gameClosed(false), gameId(config.gameId), description(config.description.simplified()), + password(config.password), maxPlayers(config.maxPlayers), gameTypes(config.gameTypes), activePlayer(-1), + activePhase(-1), onlyBuddies(config.onlyBuddies), onlyRegistered(config.onlyRegistered), + spectatorsAllowed(config.spectatorsAllowed), spectatorsNeedPassword(config.spectatorsNeedPassword), + spectatorsCanTalk(config.spectatorsCanTalk), spectatorsSeeEverything(config.spectatorsSeeEverything), + startingLifeTotal(config.startingLifeTotal), shareDecklistsOnLoad(config.shareDecklistsOnLoad), + inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), + turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), + deckValidationStrategy(new Server_DefaultDeckValidationStrategy), + matchResultStrategy(new Server_NullMatchResultStrategy), lifecycleStrategy(new Server_DefaultLifecycleStrategy), gameMutex() { currentReplay = new GameReplay; currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId()); - description = _description.simplified(); connect(this, &Server_Game::sigStartGameIfReady, this, &Server_Game::doStartGameIfReady, Qt::QueuedConnection); @@ -118,6 +106,10 @@ Server_Game::~Server_Game() for (auto *replay : replayList) { delete replay; } + + delete deckValidationStrategy; + delete matchResultStrategy; + delete lifecycleStrategy; replayList.clear(); room = nullptr; @@ -343,6 +335,13 @@ void Server_Game::doStartGameIfReady(bool forceStartGame) } players = getPlayers(); // players could have been kicked, get new list of players + + // Delegate pre-start logic (draft/tournament) to lifecycle strategy + if (lifecycleStrategy->onGameStarting(this) == Server_GameLifecycleStrategy::StartAction::Handled) { + locker.unlock(); + return; + } + for (Server_AbstractPlayer *player : players.values()) { player->setupZones(); } @@ -400,10 +399,12 @@ void Server_Game::stopGameIfFinished() QMutexLocker locker(&gameMutex); int playing = 0; + Server_AbstractPlayer *lastPlayer = nullptr; auto players = getPlayers(); for (auto *player : players.values()) { if (!player->getConceded()) { ++playing; + lastPlayer = player; } } if (playing > 1) { @@ -419,6 +420,17 @@ void Server_Game::stopGameIfFinished() sendGameStateToPlayers(); + // Delegate post-game actions (e.g., tournament reporting) to the match result strategy + bool matchDecided = matchResultStrategy->onGameFinished(this, playing, lastPlayer); + if (matchDecided) { + locker.unlock(); + + sendGameEventContainer(prepareGameEvent(Event_GameClosed(), -1)); + gameClosed = true; + deleteLater(); + return; + } + locker.unlock(); ServerInfo_Game gameInfo; @@ -901,3 +913,11 @@ void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPl } } } + +void Server_Game::setDeckValidationStrategy(Server_DeckValidationStrategy *strategy) +{ + if (deckValidationStrategy) { + delete deckValidationStrategy; + } + deckValidationStrategy = strategy; +} diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h index e0e7896b7..fa94d3aa7 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -21,6 +21,10 @@ #define SERVERGAME_H #include "../server_response_containers.h" +#include "game_config.h" +#include "server_deck_validation_strategy.h" +#include "server_game_lifecycle_strategy.h" +#include "server_match_result_strategy.h" #include #include @@ -78,6 +82,10 @@ private: QList replayList; GameReplay *currentReplay; + Server_DeckValidationStrategy *deckValidationStrategy; + Server_MatchResultStrategy *matchResultStrategy; + Server_GameLifecycleStrategy *lifecycleStrategy; + void createGameStateChangedEvent(Event_GameStateChanged *event, Server_AbstractParticipant *recipient, bool omniscient, @@ -92,21 +100,7 @@ private slots: public: mutable QRecursiveMutex gameMutex; - Server_Game(const ServerInfo_User &_creatorInfo, - int _gameId, - const QString &_description, - const QString &_password, - int _maxPlayers, - const QList &_gameTypes, - bool _onlyBuddies, - bool _onlyRegistered, - bool _spectatorsAllowed, - bool _spectatorsNeedPassword, - bool _spectatorsCanTalk, - bool _spectatorsSeeEverything, - int _startingLifeTotal, - bool _shareDecklistsOnLoad, - Server_Room *parent); + Server_Game(const GameConfig &config, Server_Room *parent); ~Server_Game() override; Server_Room *getRoom() const { @@ -221,6 +215,16 @@ public: GameEventStorageItem::SendToOthers, int privatePlayerId = -1); void returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player); + + Server_DeckValidationStrategy *getDeckValidationStrategy() const + { + return deckValidationStrategy; + } + Server_GameLifecycleStrategy *getLifecycleStrategy() const + { + return lifecycleStrategy; + } + void setDeckValidationStrategy(Server_DeckValidationStrategy *strategy); }; #endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game_lifecycle_strategy.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game_lifecycle_strategy.h new file mode 100644 index 000000000..7ea9bf676 --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game_lifecycle_strategy.h @@ -0,0 +1,29 @@ +#ifndef SERVER_GAME_LIFECYCLE_STRATEGY_H +#define SERVER_GAME_LIFECYCLE_STRATEGY_H + +class Server_Game; + +class Server_GameLifecycleStrategy +{ +public: + virtual ~Server_GameLifecycleStrategy() = default; + + enum class StartAction + { + ProceedNormal, + Handled, + }; + + virtual StartAction onGameStarting(Server_Game *game) = 0; +}; + +class Server_DefaultLifecycleStrategy : public Server_GameLifecycleStrategy +{ +public: + StartAction onGameStarting(Server_Game *) override + { + return StartAction::ProceedNormal; + } +}; + +#endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_match_game_factory.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_match_game_factory.h new file mode 100644 index 000000000..dc1ccfc6a --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_match_game_factory.h @@ -0,0 +1,18 @@ +#ifndef SERVER_MATCH_GAME_FACTORY_H +#define SERVER_MATCH_GAME_FACTORY_H + +class GameConfig; +class Server_Game; +class Server_AbstractUserInterface; + +class Server_MatchGameFactory +{ +public: + virtual ~Server_MatchGameFactory() = default; + + virtual Server_Game *createMatchGame(const GameConfig &config, int &outGameId) = 0; + virtual Server_AbstractUserInterface *getUserInterface(const QString &playerName) = 0; + virtual void addGameToRoom(Server_Game *game) = 0; +}; + +#endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_match_result_strategy.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_match_result_strategy.h new file mode 100644 index 000000000..df2bb95f0 --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_match_result_strategy.h @@ -0,0 +1,24 @@ +#ifndef SERVER_MATCH_RESULT_STRATEGY_H +#define SERVER_MATCH_RESULT_STRATEGY_H + +class Server_AbstractPlayer; +class Server_Game; + +class Server_MatchResultStrategy +{ +public: + virtual ~Server_MatchResultStrategy() = default; + + virtual bool onGameFinished(Server_Game *game, int playing, Server_AbstractPlayer *lastPlayer) = 0; +}; + +class Server_NullMatchResultStrategy : public Server_MatchResultStrategy +{ +public: + bool onGameFinished(Server_Game *, int, Server_AbstractPlayer *) override + { + return false; + } +}; + +#endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp index c441da781..1c2cf6850 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp @@ -1,5 +1,6 @@ #include "server_protocolhandler.h" +#include "game/game_config.h" #include "game/server_game.h" #include "game/server_player.h" #include "server_database_interface.h" @@ -885,10 +886,23 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room // When server doesn't permit registered users to exist, do not honor only-reg setting bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers()); - auto *game = new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), - cmd.max_players(), gameTypes, cmd.only_buddies(), onlyRegisteredUsers, - cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.spectators_can_talk(), - cmd.spectators_see_everything(), startingLifeTotal, shareDecklistsOnLoad, room); + GameConfig config; + config.creatorInfo = copyUserInfo(false); + config.gameId = gameId; + config.description = description; + config.password = QString::fromStdString(cmd.password()); + config.maxPlayers = cmd.max_players(); + config.gameTypes = gameTypes; + config.onlyBuddies = cmd.only_buddies(); + config.onlyRegistered = onlyRegisteredUsers; + config.spectatorsAllowed = cmd.spectators_allowed(); + config.spectatorsNeedPassword = cmd.spectators_need_password(); + config.spectatorsCanTalk = cmd.spectators_can_talk(); + config.spectatorsSeeEverything = cmd.spectators_see_everything(); + config.startingLifeTotal = startingLifeTotal; + config.shareDecklistsOnLoad = shareDecklistsOnLoad; + + auto *game = new Server_Game(config, room); game->addPlayer(this, rc, asSpectator, asJudge, false); room->addGame(game); diff --git a/tests/movecard_tests/reverse_card_move_test.cpp b/tests/movecard_tests/reverse_card_move_test.cpp index 2231a7e3b..e1e14bdbf 100644 --- a/tests/movecard_tests/reverse_card_move_test.cpp +++ b/tests/movecard_tests/reverse_card_move_test.cpp @@ -1,3 +1,4 @@ +#include "game/game_config.h" #include "game/server_abstract_player.h" #include "game/server_card.h" #include "game/server_cardzone.h" @@ -22,7 +23,12 @@ TEST(ReverseCardMoveTest, MoveCardFromBottomTest) // instantiate a fake server instance FakeServer server; Server_Room room(0, 0, "", "", "", "", false, "", {}, &server); - Server_Game game(user, 1, "", "", 2, QList(), false, false, false, false, false, false, 20, false, &room); + GameConfig config; + config.creatorInfo = user; + config.gameId = 1; + config.maxPlayers = 2; + config.startingLifeTotal = 20; + Server_Game game(config, &room); Server_AbstractPlayer player(&game, 1, user, false, nullptr); Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone); Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone);