2011-03-07 13 views
11

Estoy diseñando un pequeño juego para mi propia diversión y para el entrenamiento. La verdadera identidad del juego es bastante irrelevante para mi pregunta real, supongo que es el juego Mastermind (que en realidad es :)Diseño común para consola y GUI

Mi verdadero objetivo aquí es tener una interfaz IPlayer que se utilizará para cualquier jugador: computadora o humano, consola o gui, local o de red. También tengo la intención de tener un GameController, que se ocupará solo de dos IPlayer s.

la interfaz iPlayer sería algo como esto:

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

La clase GameController haría algo como esto:

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

Mediante este diseño, estoy dispuesto a hacer la clase GameController inmutable, que Es decir, incluyo las reglas del juego justo en él, y nada más, así que como el juego en sí está establecido, esta clase no debería cambiar. Para un juego de consola, este diseño funcionaría perfectamente. Tendría HumanPlayer, que en su método MakeAGuess leería un Combination de la entrada estándar, y una CPUPlayer, lo que generaría alguna manera al azar que etc.

Ahora aquí está mi problema: La interfaz IPlayer, junto con la clase GameController, son sincrónicos en su naturaleza. No puedo imaginar cómo implementaría la variante de GUI del juego con el mismo GameController cuando el método MakeAGuess de GUIHumanPlayer tendría que esperar, por ejemplo, algunos movimientos y clics del mouse. Por supuesto, podría lanzar un nuevo hilo que esperaría la entrada del usuario, mientras que el hilo principal se bloquearía, para imitar el IO sincrónico, pero de alguna manera esta idea me repugna. O, como alternativa, podría diseñar tanto el controlador como el reproductor para que sean asíncronos. En este caso, para un juego de consola, tendría que imitar la asincronía, que parece más fácil que la primera versión.

¿Podría comentar sobre mi diseño y mis inquietudes sobre elegir un diseño sincrónico o asincrónico? Además, siento que pongo más responsabilidad en la clase de jugador que en la clase GameController. Etc, etc.

Muchas gracias de antemano.

P.S. No me gusta el título de mi pregunta. Se sienten libres para editarlo :)

+7

Me gusta el título :-) –

+1

Quité la etiqueta C#, ya que no hay C# en cualquier lugar cerca de esta pregunta. – Puppy

+1

Dado que el idioma es irrelevante, espero obtener también la opinión de los expertos de C# –

Respuesta

11

En lugar de utilizar los valores de retorno de los diversos métodos IPlayer, considerar la introducción de una clase observador de IPlayer objetos, como este:

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

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

Los métodos de IPlayer continuación, deben llamar a la adecuada métodos de un IPlayerObserver instalado en lugar de devolver un valor, como en:

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

Su clase GameController podrían entonces aplicar IPlayerObserver para que recibe notificaciones cada vez que un jugador hace algo interesante, como - adivinar.

Con este diseño, está perfectamente bien si todos los métodos IPlayer son asincrónicos. De hecho, es de esperar, todos devuelven void!Su dispositivo de juego llama al makeAGuess en el jugador activo (esto podría calcular el resultado inmediatamente, o podría hacer un IO de red para juegos de varios jugadores, o esperaría a que la GUI hiciera algo) y siempre que el jugador hiciera su elección, el controlador del juego puede estar seguro de que se llamará al método guessMade. Además, los objetos del jugador aún no saben nada sobre el controlador del juego. Solo están lidiando con una interfaz opaca 'IPlayerObserver'.

1

Lo único que hace que esto sea diferente para la GUI en comparación con la consola es que su GUI está impulsada por eventos. Esos eventos tienen lugar en el hilo de GUI, y por lo tanto, si aloja el código del juego en el hilo de GUI, tiene un problema: su llamada para hacer que el jugador haga un movimiento bloquea el hilo de GUI, y esto significa que no puede obtener cualquier evento hasta que esa llamada regrese. [EDITAR: Insertó la siguiente oración.] Pero la llamada no puede regresar hasta que recibe el evento. Entonces estás estancado.

Ese problema desaparecería si simplemente alojas el código del juego en otro hilo. Aún necesitarás sincronizar los hilos, por lo que MakeAGuess() no volverá hasta que esté listo, pero ciertamente es factible.

Si desea mantener todo de rosca simple, es posible que desee considerar un modelo diferente. El juego podría notificar a los jugadores que es su turno con un evento, pero dejar que los jugadores inicien las operaciones en el juego.

Cuestiones relacionadas