控制台和GUI的通用设计

Arm*_*yan 11 c++ language-agnostic oop design-patterns

我正在为自己的乐趣和训练设计一款小游戏.游戏的真实身份与我的实际问题完全无关,假设它是Mastermind游戏(它实际上是:)

我的真正目标是拥有一个IPlayer可用于任何玩家的界面:计算机或人,控制台或gui,本地或网络.我也打算有一个GameController,它只处理两个IPlayers.

IPlayer接口看起来像这样:

class IPlayer
{
public:
    //dtor
    virtual ~IPlayer()
    {
    }
    //call this function before the game starts. In subclasses,
    //the overriders can, for example, generate and store the combination.
    virtual void PrepareForNewGame() = 0;
    //make the current guess
    virtual Combination MakeAGuess() = 0;
    //return false if lie is detected.
    virtual bool ProcessResult(Combination const &, Result const &) = 0;
    //Answer to opponent's guess
    virtual Result AnswerToOpponentsGuess(Combination const&) = 0;
};
Run Code Online (Sandbox Code Playgroud)

GameController类会做这样的事情:

IPlayer* pPlayer1 = PlayerFactory::CreateHumanPlayer();
IPlayer* pPlayer1 = PlayerFactory::CreateCPUPlayer();

pPlayer1->PrepareForNewGame();
pPlayer2->PrepareForNewGame();

while(no_winner)
{
   Guess g = pPlayer1->MakeAguess();
   Result r = pPlayer2->AnswerToOpponentsGuess(g);
   bool player2HasLied = ! pPlayer1->ProcessResult(g, r);
   etc. 
   etc.
}   
Run Code Online (Sandbox Code Playgroud)

通过这种设计,我愿意让GameController类不可变,也就是说,我把游戏规则填入其中,而不是其他任何东西,所以既然游戏本身已经建立,那么这个类就不应该改变.对于控制台游戏,这种设计可以完美地运行.我会HumanPlayer,它在其MakeAGuess方法中将从Combination标准输入读取a ,和a CPUPlayer,它会以某种方式随机生成它等.

现在这是我的问题:IPlayer界面和GameController类一起本质上是同步的.我无法想象GameController当我必须等待的MakeAGuess方法(GUIHumanPlayer例如,一些鼠标移动和点击)时,我将如何实现游戏的GUI变体.当然,我可以启动一个等待用户输入的新线程,而主线程会阻塞,以便模仿同步IO,但不知怎的,这个想法让我感到厌恶.或者,或者,我可以将控制器和播放器设计为异步.在这种情况下,对于控制台游戏,我将不得不模仿异步,这似乎比第一个版本更容易.

您是否愿意评论我的设计以及我对选择同步或异步设计的担忧?另外,我觉得我对玩家类的责任比GameController类更多.等等

非常感谢你提前.

PS我不喜欢我的问题的标题.随意编辑:)

Fre*_*abe 11

不要使用各种IPlayer方法的返回值,而是考虑为IPlayer对象引入一个观察者类,如下所示:

class IPlayerObserver
{
public:
  virtual ~IPlayerObserver() { }
  virtual void guessMade( Combination c ) = 0;
  // ...
};

class IPlayer
{
public:
  virtual ~IPlayer() { }
  virtual void setObserver( IPlayerObserver *observer ) = 0;
  // ...
};
Run Code Online (Sandbox Code Playgroud)

的方法,IPlayer那么应该调用的安装适当的方法IPlayerObserver,而不是返回一个值,如:

void HumanPlayer::makeAGuess() {
  // get input from human
  Combination c;
  c = ...;
  m_observer->guessMade( c );
}
Run Code Online (Sandbox Code Playgroud)

然后,您的GameController类可以实现,IPlayerObserver以便在玩家做一些有趣的事情时得到通知,例如 - 猜测.

使用这种设计,如果所有IPlayer方法都是异步的,那就完全没问题了.事实上,这是可以预期的 - 它们都会回归void!您的游戏控制器会调用makeAGuess活动播放器(这可能会立即计算结果,或者它可能会为多人游戏做一些网络IO,或者它会等待GUI执行某些操作)并且每当玩家做出他的选择时,游戏控制器就可以请放心,该guessMade方法将被调用.此外,玩家对象仍然不知道游戏控制器的任何信息.他们只是在处理一个不透明的'IPlayerObserver'接口.