控制台和 GUI 的通用设计

发布于 2024-10-21 01:49:42 字数 2026 浏览 2 评论 0原文

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

我真正的目标是拥有一个可供任何玩家使用的界面IPlayer:计算机或人类、控制台或GUI、本地或网络。我还打算有一个 GameController,它只处理两个 IPlayer

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

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 接口以及 GameController 类本质上是同步的。我无法想象当 GUIHumanPlayerMakeAGuess 方法必须等待时,如何使用相同的 GameController 实现游戏的 GUI 变体例如,一些鼠标移动和点击。当然,我可以启动一个新线程来等待用户输入,而主线程会阻塞,以模仿同步IO,但不知怎的,这个想法让我感到厌恶。或者,我可以将控制器和播放器设计为异步的。在这种情况下,对于控制台游戏,我必须模仿异步性,这似乎比第一个版本更容易。

您能否评论一下我的设计以及我对选择同步或异步设计的担忧?另外,我觉得我把更多的责任放在了玩家类上而不是 GameController 类上。等等等等。

提前非常感谢。

PS 我不喜欢我的问题的标题。请随意编辑它:)

I am designing a little game for my own fun's and training's sake. The real identity of the game being quite irrelevant for my actual question, suppose it's the Mastermind game (which it actually is :)

My real goal here is to have an interface IPlayer which will be used for any player: computer or human, console or gui, local or network. I am also intending to have a GameController, which will deal with just two IPlayers.

the IPlayer interface would look something like this:

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

The GameController class would do something like this:

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

By this design, I am willing to make GameController class immutable, that is, I stuff the just game rules in it, and nothing else, so since the game itself is established, this class shouldn't change. For a console game this design would work perfectly. I would have HumanPlayer, which in its MakeAGuess method would read a Combination from the standard input, and a CPUPlayer, which would somehow randomly generate it etc.

Now here's my problem: The IPlayer interface, along with the GameController class, are synchronous in their nature. I can't imagine how I would implement the GUI variant of the game with the same GameController when the MakeAGuess method of GUIHumanPlayer would have to wait for, for example, some mouse movements and clicks. Of course, I could launch a new thread which would wait for user input, while the main thread would block, so as to imitate synchronous IO, but somehow this idea disgusts me. Or, alternatively, I could design both the controller and player to be asynchronous. In this case, for a console game, I would have to imitate asynchronousness, which seems easier than the first version.

Would you kindly comment on my design and my concerns about choosing synchronous or asynchronous design? Also, I am feeling that I put more responsibility on the player class than GameController class. Etc, etc.

Thank you very much in advance.

P.S. I don't like the title of my question. Feel free to edit it :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

柠檬心 2024-10-28 01:49:43

不要使用各种 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!。您的游戏控制器在活动玩家上调用 makeAGuess (这可能会立即计算结果,或者可能会为多人游戏执行一些网络 IO,或者会等待 GUI 执行某些操作)并且每当玩家做了他的选择后,游戏控制器就可以放心,guessMade 方法将会被调用。此外,玩家对象仍然不知道游戏控制器的任何信息。他们只是处理不透明的“IPlayerObserver”接口。

Instead of using return values of the various IPlayer methods, consider introducing an observer class for IPlayer objects, like this:

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

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

The methods of IPlayer should then call the appropriate methods of an installed IPlayerObserver instead of returning a value, as in:

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

Your GameController class could then implement IPlayerObserver so that it gets notified whenever a player did something interesting, like - making a guess.

With this design, it's perfectly fine if all the IPlayer methods are asynchronous. In fact, it's to be expected - they all return void!. Your game controller calls makeAGuess on the active player (this might compute the result immediately, or it might do some network IO for multiplayer games, or it would wait for the GUI to do something) and whenever the player did his choice, the game controller can rest assured that the guessMade method will be called. Furthemore, the player objects still don't know anything about the game controller. They are just dealing with an opaque 'IPlayerObserver' interface.

可可 2024-10-28 01:49:43

与控制台相比,GUI 的唯一不同之处在于 GUI 是事件驱动的。这些事件发生在 GUI 线程上,因此,如果您在 GUI 线程上托管游戏代码,就会遇到问题:让玩家采取行动的调用会阻塞 GUI 线程,这意味着您无法获取调用返回之前发生的任何事件。 [编辑:插入以下句子。]但是调用在获取事件之前无法返回。所以你陷入了僵局。

如果您只是将游戏代码托管在另一个线程上,这个问题就会消失。您仍然需要同步线程,因此 MakeAGuess() 在准备好之前不会返回,但这当然是可行的。

如果您想让所有内容保持单线程,您可能需要考虑不同的模型。游戏可以通知玩家轮到他们处理事件,但让玩家在游戏上启动操作。

The only thing making this different for the GUI as compared to the console is that your GUI is event driven. Those events take place on the GUI thread, and therefore, if you host the Game code on the GUI thread, you have a problem: Your call to have the player make a move blocks the GUI thread, and this means you can't get any events until that call returns. [EDIT: Inserted the following sentence.] But the call can't return until it gets the event. So you're deadlocked.

That problem would go away if you simply host the game code on another thread. You'd still need to synchronize the threads, so MakeAGuess() doesn't return until ready, but it's certainly doable.

If you want to keep everything single-threaded you may want to consider a different model. Game could notify Players it's their turn with an event but leave it to players to initiate operations on the Game.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文