diff --git a/CMakeLists.txt b/CMakeLists.txt index 59f567d..dbff5b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ add_executable(uno-game src/main.cpp src/game/CardTile.cpp src/game/Player.cpp src/common/utils.cpp + src/game/GameState.cpp + src/game/GameState.h ) target_link_libraries(uno-game PRIVATE ftxui::screen @@ -21,6 +23,8 @@ add_library(uno-game-lib src/game/Card.cpp src/game/CardTile.cpp src/game/Player.cpp src/common/utils.cpp + src/game/GameState.cpp + src/game/GameState.h ) add_subdirectory(test) \ No newline at end of file diff --git a/src/game/GameState.cpp b/src/game/GameState.cpp new file mode 100644 index 0000000..d5453d6 --- /dev/null +++ b/src/game/GameState.cpp @@ -0,0 +1,163 @@ +/** + * @file GameState.cpp + * + * @author Yuzhe Guo + * @date 2025.11.16 + */ +#include "GameState.h" + +#include +#include + +namespace UNO::GAME { + PlayerState::PlayerState(std::string name, size_t remainingCardCount, bool isUno) : + name_(std::move(name)), remainingCardCount_(remainingCardCount), isUno_(isUno) + { + } + + + std::string PlayerState::getName() const + { + return this->name_; + } + + bool PlayerState::getIsUno() const + { + return this->isUno_; + } + + size_t PlayerState::getRemainingCardCount() const + { + return this->remainingCardCount_; + } + + void PlayerState::setRemainingCardCount(size_t x) + { + this->remainingCardCount_ = x; + } + + ServerPlayerState::ServerPlayerState(std::string name, size_t remainingCardCount, bool isUno, HandCard *handCard) : + PlayerState(std::move(name), remainingCardCount, isUno), handCard_(handCard) + { + } + + template + void ServerPlayerState::draw(T... args) + { + this->handCard_->draw(args...); + } + + template + GameState::GameState(GameStatus gameStatus) : + gameStatus_(gameStatus), isReversed_(false), drawCount_(0), players_(), currentPlayer_(players_.begin()) + { + } + + template + const std::vector &GameState::getPlayers() const + { + return this->players_; + } + + template + std::vector::iterator GameState::getCurrentPlayer() const + { + return this->currentPlayer_; + } + + template + const DiscardPile &GameState::getDiscardPile() const + { + return this->discardPile_; + } + + template + bool GameState::getIsReversed() const + { + return this->isReversed_; + } + + template + void GameState::addPlayer(PlayerStateType playerState) + { + this->currentPlayer_ = this->players_.push_back(std::move(playerState), this->currentPlayer_); + } + + template + void GameState::nextPlayer() + { + if (this->isReversed_ == false) { + if (this->currentPlayer_ == this->players_.end()) { + this->currentPlayer_ = std::next(this->currentPlayer_); + this->currentPlayer_ = this->players_.begin(); + } + } + else { + if (this->currentPlayer_ == this->players_.begin()) { + this->currentPlayer_ = std::prev(this->players_.end()); + } + else { + this->currentPlayer_ = std::prev(this->currentPlayer_); + } + } + } + + template + void GameState::clearPlayers() + { + this->players_.clear(); + this->currentPlayer_ = this->players_.begin(); + } + + template + void GameState::reverse() + { + this->isReversed_ ^= 1; + } + + template + void GameState::updateStateByCard(const Card &card) + { + if (this->discardPile_.isEmpty() == false && card.canBePlayedOn(this->discardPile_.getFront()) == false) { + throw std::invalid_argument("Card cannot be played"); + } + this->currentPlayer_->setRemainingCardCount(this->currentPlayer_->getRemainingCardCount() - 1); + if (card.getType() == CardType::DRAW2) { + this->drawCount_ += 2; + } + if (card.getType() == CardType::WILDDRAWFOUR) { + this->drawCount_ += 4; + } + if (card.getType() == CardType::REVERSE) { + this->reverse(); + } + if (card.getType() == CardType::SKIP) { + this->nextPlayer(); + } + this->nextPlayer(); + this->discardPile_.add(card); + } + + template + void GameState::updateStateByDraw() + { + if (this->drawCount_ != 0) { + this->drawCount_ = 0; + } + } + + ClientGameState::ClientGameState(GameStatus gameStatus, Player player) : GameState(gameStatus), player(std::move(player)) {} + + ServerGameState::ServerGameState() : GameState(GameStatus::WAITING_PLAYERS_TO_JOIN) {} + + void ServerGameState::updateStateByDraw() + { + if (this->drawCount_ == 0) { + this->drawCount_ = 1; + } + this->currentPlayer_->draw(this->deck_.draw(this->drawCount_)); + this->drawCount_ = 0; + } + + +} // namespace UNO::GAME \ No newline at end of file diff --git a/src/game/GameState.h b/src/game/GameState.h new file mode 100644 index 0000000..4c69bf6 --- /dev/null +++ b/src/game/GameState.h @@ -0,0 +1,172 @@ +/** + * @file GameState.h + * + * @author Yuzhe Guo + * @date 2025.11.16 + */ +#ifndef UNO_GAME_GAMESTATE_H +#define UNO_GAME_GAMESTATE_H +#include "CardTile.h" +#include "Player.h" + +#include + +namespace UNO::GAME { + + /** + * 玩家状态 + */ + class PlayerState { + protected: + std::string name_; + size_t remainingCardCount_; + bool isUno_; + + public: + PlayerState(std::string name, size_t remainingCardCount, bool isUno); + + /** + * @return 玩家名字 + */ + [[nodiscard]] std::string getName() const; + + /** + * @return 玩家是否处于 UNO 状态 + */ + [[nodiscard]] bool getIsUno() const; + + /** + * @return 玩家手中剩余的牌的数量 + */ + [[nodiscard]] size_t getRemainingCardCount() const; + + void setRemainingCardCount(size_t x); + }; + + /** + * (供客户端使用)玩家状态 + */ + class ClientPlayerState : public PlayerState {}; + + /** + * (供服务端使用)玩家状态 + */ + class ServerPlayerState : public PlayerState { + private: + HandCard *handCard_; + + public: + explicit ServerPlayerState(std::string name, size_t remainingCardCount, bool isUno, HandCard *handCard); + + template + void draw(T... args); + }; + + /** + * 游戏状态 + */ + enum class GameStatus : uint8_t { + WAITING_PLAYERS_TO_JOIN, + WAITING_PLAYERS_TO_START, + WAITING_PLAYERS_TO_NEXT_TURN, + WAITING_PLAYERS_TO_NEXT_ROUND + }; + + /** + * 保证模板使用的是 PlayerState 的派生类 + */ + template + concept PlayerStateTypeConcept = std::is_base_of_v; + + /** + * 游戏具体状态 + * @tparam PlayerStateType PlayerState 的派生类 + */ + template + class GameState { + protected: + explicit GameState(GameStatus gameStatus); + GameStatus gameStatus_; + DiscardPile discardPile_; + bool isReversed_; + size_t drawCount_; + std::vector players_; + std::vector::iterator currentPlayer_; + + public: + virtual ~GameState() = default; + /** + * @return 所有玩家状态 + */ + [[nodiscard]] const std::vector &getPlayers() const; + + /** + * @return 当前玩家对应的 iterator + */ + [[nodiscard]] std::vector::iterator getCurrentPlayer() const; + + /** + * @return 弃牌堆 + */ + [[nodiscard]] const DiscardPile &getDiscardPile() const; + + /** + * @return 是否反转 + */ + [[nodiscard]] bool getIsReversed() const; + + /** + * 向对局中添加玩家 + * @param playerState 要添加的玩家状态 + */ + void addPlayer(PlayerStateType playerState); + + /** + * 下一个玩家 + */ + void nextPlayer(); + + /** + * 清空玩家 + */ + void clearPlayers(); + + /** + * 反转 + */ + void reverse(); + + /** + * 由于用户出牌而改变状态 + * @param card 用户出的牌 + */ + void updateStateByCard(const Card &card); + + /** + * 由于用户摸牌而改变状态 + */ + void virtual updateStateByDraw(); + }; + + class ClientGameState final : public GameState { + public: + Player player; + + ClientGameState(GameStatus gameStatus, Player player); + }; + + class ServerGameState final : public GameState { + private: + Deck deck_; + + public: + ServerGameState(); + + /** + * 由于用户摸牌而改变状态 + */ + void updateStateByDraw() override; + }; +} // namespace UNO::GAME + +#endif // UNO_GAME_GAMESTATE_H