我正在为自己和娱乐而设计一款小游戏。游戏的真实身份与我的实际问题无关紧要,假设它是Mastermind游戏(它实际上是:)

我在这里的真正目标是拥有一个IPlayer接口(interface),该接口(interface)可用于任何播放器:计算机或人工,控制台或gui,本地或网络。我还打算有一个GameController,它将只处理两个IPlayer

IPlayer接口(interface)看起来像这样:

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;
};

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.
}

通过这种设计,我愿意使GameController类不可变,也就是说,我只将其中的游戏规则填充进去,而别无其他,因此,既然游戏本身已经建立,则该类不应更改。对于主机游戏,该设计将完美运行。我将使用HumanPlayer(在其MakeAGuess方法中会从标准输入中读取Combination)和CPUPlayer(将以某种方式随机生成)等。

现在,这是我的问题:IPlayer接口(interface)以及GameController类在本质上是同步的。我无法想象当GameControllerMakeAGuess方法必须等待例如某些鼠标移动和点击时,如何用相同的GUIHumanPlayer实现游戏的GUI变体。当然,我可以启动一个新线程,该线程将等待用户输入,而主线程将阻塞,以模仿同步IO,但这在某种程度上使我感到厌恶。或者,我也可以将 Controller 和播放器设计为异步的。在这种情况下,对于主机游戏,我将不得不模仿异步性,这似乎比第一个版本容易。

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

提前非常感谢您。

附言我不喜欢我的问题的标题。随时编辑它:)

最佳答案

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

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

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

然后,IPlayer的方法应调用已安装的IPlayerObserver的相应方法,而不是返回值,如:
void HumanPlayer::makeAGuess() {
  // get input from human
  Combination c;
  c = ...;
  m_observer->guessMade( c );
}

然后,您的GameController类可以实现IPlayerObserver,以便在玩家进行一些有趣的操作(例如进行猜测)时得到通知。

通过这种设计,如果所有IPlayer方法都是异步的,那就很好了。实际上,这是可以预期的-它们都返回void!。您的游戏 Controller 会在 Activity 的玩家上调用makeAGuess(这可能会立即计算结果,或者可能会为多人游戏提供一些网络IO,或者会等待GUI进行操作),并且只要玩家做出选择,游戏 Controller 可以放心guessMade方法将被调用。此外,玩家对象仍然对游戏 Controller 一无所知。他们只是在处理一个不透明的“IPlayerObserver”接口(interface)。

07-24 09:45