Общий дизайн для консоли и графического интерфейса - PullRequest
11 голосов
/ 07 марта 2011

Я создаю небольшую игру для собственного удовольствия и тренировок. Реальная идентичность игры не имеет никакого отношения к моему актуальному вопросу, предположим, что это игра Mastermind (которая на самом деле :)

Моя настоящая цель здесь - иметь интерфейс IPlayer, который будет использоваться для любого игрока: компьютера или человека, консоли или графического интерфейса, локального или сетевого. Я также намереваюсь иметь GameController, который будет иметь дело только с двумя IPlayer s.

интерфейс 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 синхронны по своей природе. Я не представляю, как бы я реализовал вариант игры с графическим интерфейсом с тем же GameController, когда MakeAGuess метод GUIHumanPlayer должен был бы ждать, например, некоторых движений мыши и щелчков. Конечно, я мог бы запустить новый поток, который бы ожидал пользовательского ввода, в то время как основной поток блокировал бы, чтобы имитировать синхронный ввод-вывод, но почему-то эта идея мне противна. Или, в качестве альтернативы, я мог бы спроектировать контроллер и плеер как асинхронные. В этом случае для консольной игры мне пришлось бы имитировать асинхронность, которая кажется проще, чем в первой версии.

Не могли бы вы прокомментировать мой дизайн и мои опасения по поводу выбора синхронного или асинхронного дизайна? Кроме того, я чувствую, что возлагаю больше ответственности на класс игрока, чем на класс GameController. И т. Д.

Большое спасибо заранее.

P.S. Мне не нравится название моего вопроса. Не стесняйтесь редактировать это :)

Ответы [ 2 ]

11 голосов
/ 07 марта 2011

Вместо использования возвращаемых значений различных методов 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 для активного игрока (это может вычислить результат немедленно, или он может выполнить какой-либо сетевой ввод-вывод для многопользовательских игр, или он будет ждать, пока GUI что-то сделает), и всякий раз, когда игрок сделал свой выбор,Игровой контроллер может быть уверен, что будет вызван метод guessMade.Более того, объекты игрока все еще ничего не знают об игровом контроллере.Они просто имеют дело с непрозрачным интерфейсом IPlayerObserver.

1 голос
/ 07 марта 2011

Единственное, что отличает графический интерфейс от консоли, это то, что ваш графический интерфейс управляется событиями. Эти события происходят в потоке GUI, и поэтому, если вы размещаете код Game в потоке GUI, у вас возникает проблема: ваш призыв сделать ход игроком блокирует поток GUI, а это означает, что вы не можете получить любые события, пока этот звонок не вернется. [РЕДАКТИРОВАТЬ: Вставил следующее предложение.] Но вызов не может вернуться, пока не получит событие. Итак, вы зашли в тупик.

Эта проблема исчезнет, ​​если вы просто разместите код игры в другом потоке. Вам все равно нужно синхронизировать потоки, поэтому MakeAGuess () не возвращается, пока не будет готов, но это, безусловно, выполнимо.

Если вы хотите сохранить все однопоточными, вы можете рассмотреть другую модель. Игра может уведомить Игроков о своей очереди с событием, но предоставить игрокам возможность начать операции в Игре.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...