design-patterns - patterns - strategy pattern c#
¿Algún patrón para modelar juegos de mesa? (8)
Para divertirme, intento escribir uno de los juegos de mesa favoritos de mi hijo como un software. Eventualmente, espero construir una interfaz de usuario de WPF, pero ahora mismo estoy construyendo la máquina que modela los juegos y sus reglas.
Mientras hago esto, sigo viendo problemas que creo que son comunes en muchos juegos de mesa, y quizás otros ya los hayan resuelto mejor que yo.
(Tenga en cuenta que la inteligencia artificial para jugar y los patrones de alto rendimiento no me interesan).
Hasta ahora mis patrones son:
Varios tipos inmutables que representan entidades en la caja del juego, por ejemplo, dados, damas, cartas, un tablero, espacios en el tablero, dinero, etc.
Un objeto para cada jugador, que contiene los recursos del jugador (por ejemplo, dinero, puntaje), su nombre, etc.
Un objeto que representa el estado del juego: los jugadores, quién es el turno, el diseño de las fichas en el tablero, etc.
Una máquina de estado que maneja la secuencia de giro. Por ejemplo, muchos juegos tienen un pequeño juego previo donde cada jugador rueda para ver quién va primero; ese es el estado de inicio. Cuando comienza el turno de un jugador, primero rueda, luego se mueve, luego tiene que bailar en el lugar, luego los otros jugadores adivinan qué raza de pollo son y luego reciben puntos.
¿Hay alguna técnica anterior que pueda aprovechar?
EDITAR: Una cosa que me di cuenta recientemente es que el estado del juego se puede dividir en dos categorías:
Estado artefacto del juego . "Tengo $ 10" o "mi mano izquierda está en azul".
Estado de la secuencia del juego "He lanzado dobles en dos ocasiones, el siguiente me pone en la cárcel". Una máquina de estado puede tener sentido aquí.
EDITAR: Lo que realmente estoy buscando aquí es la mejor manera de implementar juegos basados en turnos para varios jugadores como Chess, Scrabble o Monopoly. Estoy seguro de que podría crear un juego de este tipo trabajando simplemente de principio a fin, pero, como otros Patrones de diseño, probablemente haya algunas formas de hacer las cosas mucho más fácilmente que no sean obvias sin un estudio cuidadoso. Eso es lo que estoy esperando.
¿Hay alguna técnica anterior que pueda aprovechar?
Si su pregunta no es específica del idioma o plataforma. entonces le recomendaría que considere los Patrones de AOP para Estado, Memento, Comando, etc.
¿Cuál es la respuesta de .NET al AOP?
También intente encontrar algunos sitios web geniales como http://www.chessbin.com
Acabo de terminar de diseñar e implementar un juego basado en el estado que usa polimorfismo.
Usando una clase abstracta base llamada GamePhase
que tiene un método importante
abstract public GamePhase turn();
Lo que esto significa es que cada objeto GamePhase
posee el estado actual del juego, y una llamada a turn()
mira su estado actual y devuelve la siguiente GamePhase
.
Cada GamePhase
concreto tiene constructores que mantienen todo el estado del juego. Cada método de turn()
tiene un poco de las reglas del juego dentro de ellos. Si bien esto difunde las reglas, mantiene las reglas relacionadas muy juntas. El resultado final de cada turn()
es simplemente crear la siguiente GamePhase
y pasar el estado completo a la siguiente fase.
Esto permite que turn()
sea muy flexible. Dependiendo de tu juego, un estado determinado puede ramificarse a muchos tipos diferentes de fases. Esto forma un gráfico de todas las fases del juego.
En el nivel más alto, el código para conducirlo es muy simple:
GamePhase state = ...initial phase
while(true) {
// read the state, do some ui work
state = state.turn();
}
Esto es extremadamente útil ya que ahora puedo crear fácilmente cualquier estado / fase del juego para probar
Ahora para responder la segunda parte de tu pregunta, ¿cómo funciona esto en el modo multijugador? Dentro de ciertas GamePhase
s que requieren la entrada del usuario, una llamada de turn()
le pediría al Player
actual su Strategy
dado el estado / fase actual. Strategy
es solo una interfaz de todas las posibles decisiones que un Player
puede tomar. ¡Esta configuración también permite que la Strategy
se implemente con AI!
También Andrew Top dijo:
Su juego probablemente puede estar en una cantidad (cercana) infinita de estados debido a las permutaciones de cosas como la cantidad de dinero que el jugador A tiene, cuánto dinero tiene el jugador B, y etc ... Por lo tanto, estoy bastante seguro de que quiere mantenerse alejado de las máquinas de estado.
Creo que esa afirmación es muy engañosa, si bien es cierto que hay muchos estados de juego diferentes, solo hay unas pocas fases de juego. Para manejar su ejemplo, todo lo que sería es un parámetro entero para los constructores de mis GamePhase
s concretos.
Monopolio
Ejemplo de algunos GamePhase
s sería:
- GameStarts
- PlayerRolls
- PlayerLandsOnProperty (FreeParking, GoToJail, Go, etc.)
- PlayerTrades
- PlayerPurchasesProperty
- JugadorComprasCasas
- JugadorComprasHoteles
- PlayerPaysRent
- JugadorBankrupts
- (Todas las tarjetas Chance y Community Chest)
Y algunos estados en la base de GamePhase
son:
- Lista de jugadores
- Jugador actual (quién es el turno)
- Dinero / propiedad del jugador
- Casas / Hoteles en Propiedades
- Posición de jugador
Y luego, algunas fases registrarían su propio estado según fuera necesario, por ejemplo, PlayerRolls registraría la cantidad de veces que un jugador ha lanzado dobles consecutivos. Una vez que dejamos la fase PlayerRolls, ya no nos importan las tiradas consecutivas.
Muchas fases se pueden reutilizar y vincular entre sí. Por ejemplo, GamePhase
CommunityChestAdvanceToGo
crearía la siguiente fase PlayerLandsOnGo
con el estado actual y la devolvería. En el constructor de PlayerLandsOnGo
el jugador actual se PlayerLandsOnGo
a Go y su dinero se incrementaría en $ 200.
Estoy de acuerdo con la respuesta de Pyrolistical y prefiero su forma de hacer las cosas (aunque leí las otras respuestas).
Casualmente también usé su nombre "GamePhase". Básicamente lo que haría en el caso de un juego de mesa basado en turnos es hacer que tu clase GameState contenga un objeto de GamePhase abstracto como lo menciona Pyrolistical.
Digamos que los estados del juego son:
- Rodar
- Movimiento
- Comprar / No comprar
- Cárcel
Podría tener clases derivadas concretas para cada estado. Tener funciones virtuales al menos para:
StartPhase();
EndPhase();
Action();
En la función StartPhase () puede establecer todos los valores iniciales para un estado, por ejemplo, deshabilitando la entrada del otro jugador, etc.
Cuando se invoca roll.EndPhase (), asegúrese de que el puntero GamePhase se establece en el siguiente estado.
phase = new MovePhase();
phase.StartPhase();
En esta MoveFase :: StartPhase (), por ejemplo, establecería los movimientos restantes del jugador activo en la cantidad que rodó en la fase anterior.
Ahora con este diseño en su lugar, podría resolver su problema "3 x doble = cárcel" dentro de la fase de Roll. La clase RollPhase puede manejar su propio estado. Por ejemplo
GameState state; //Set in constructor.
Die die; // Only relevant to the roll phase.
int doublesRemainingBeforeJail;
StartPhase()
{
die = new Die();
doublesRemainingBeforeJail = 3;
}
Action()
{
if(doublesRemainingBeforeJail<=0)
{
state.phase = new JailPhase(); // JailPhase::StartPhase(){set moves to 0};
state.phase.StartPhase();
return;
}
int die1 = die.Roll();
int die2 = die.Roll();
if(die1 == die2)
{
--doublesRemainingBeforeJail;
state.activePlayer.AddMovesRemaining(die1 + die2);
Action(); //Roll again.
}
state.activePlayer.AddMovesRemaining(die1 + die2);
this.EndPhase(); // Continue to moving phase. Player has X moves remaining.
}
Difiero de Pyrolistical en que debe haber una fase para todo, incluso cuando el jugador aterrice en el cofre de la Comunidad o algo así. Me encargaría de todo esto en la MoveFase. Esto se debe a que si tienes demasiadas fases secuenciales, es probable que el jugador se sienta demasiado "guiado". Por ejemplo, si hay una fase en la que el jugador SOLO puede comprar propiedades y SOLAMENTE comprar hoteles y SOLAMENTE comprar casas, es como si no hubiera libertad. Solo cierre todas esas partes en una BuyFase y dé al jugador la libertad de comprar lo que quiera. La clase BuyPhase puede manejar con bastante facilidad qué compras son legales.
Finalmente, abordemos el tablero de juego. Aunque una matriz 2D está bien, recomendaría tener un gráfico de mosaicos (donde una ficha es una posición en la pizarra). En el caso del monopolio, sería una lista doblemente vinculada. Entonces, cada azulejo tendría una:
- anteriorTile
- nextTile
Entonces sería mucho más fácil hacer algo como:
While(movesRemaining>0)
AdvanceTo(currentTile.nextTile);
La función AdvanceTo puede manejar tus animaciones paso a paso o lo que quieras. Y también disminuyen los movimientos restantes, por supuesto.
El Consejo sobre patrones de observadores de RS Conley para la GUI es bueno.
No he publicado mucho antes. Espero que esto ayude a alguien.
Gran parte de los materiales que puedo encontrar en línea son listas de referencias publicadas. La sección de publicaciones de Game Design Patterns tiene enlaces a versiones en PDF de los artículos y tesis. Muchos de estos parecen documentos académicos como Design Patterns for Games . También hay disponible al menos un libro de Amazon, Patterns in Game Design .
La estructura básica de tu motor de juego usa el patrón de estado . Los elementos de tu caja de juegos son singletons de varias clases. La estructura de cada estado puede usar el Patrón de estrategia o el Método de plantilla .
Una Factory se usa para crear los jugadores que se insertan en una lista de jugadores, otro singleton. La GUI vigilará Game Engine utilizando el patrón Observer e interactuará con esto utilizando uno de los varios objetos Command creados con Command Pattern . El uso de Observer y Comando se puede usar en el contexto de una Vista pasiva. Sin embargo, casi cualquier patrón de MVP / MVC podría usarse en función de sus preferencias. Cuando guardas el juego, necesitas tomar un memento de su estado actual
Recomiendo mirar algunos de los patrones en este site y ver si alguno de ellos lo toma como punto de partida. Una vez más, el corazón de tu tablero de juego será una máquina de estado. La mayoría de los juegos estarán representados por dos estados antes del juego / configuración y el juego real. Pero puede tener más estados si el juego que está modelando tiene varios modos distintos de juego. Los estados no tienen que ser secuenciales, por ejemplo, el juego de guerra Axis & Battles tiene un tablero de batalla que los jugadores pueden usar para resolver batallas. Así que hay tres estados antes del juego, la placa principal, el tablero de batalla con el juego cambiando continuamente entre la placa principal y el tablero de batalla. Por supuesto, la secuencia de giro también puede ser representada por una máquina de estado.
Por supuesto, hay muchos, muchos, muchos, muchos, muchos, muchos, recursos sobre este tema. Pero creo que estás en el camino correcto dividiendo los objetos y dejándolos manejar sus propios eventos / datos y demás.
Al hacer juegos de mesa con azulejos, le resultará agradable tener rutinas para mapear entre la matriz de tablas y la fila / columna y hacia atrás, junto con otras características. Recuerdo mi primer juego de mesa (hace mucho tiempo) cuando tuve problemas con la forma de obtener hilera / col de boardarray 5.
1 2 3
4 (5) 6 BoardArray 5 = row 2, col 2
7 8 9
Nostalgia. ;)
De todos modos, http://www.gamedev.net/ es un buen lugar para obtener información. http://www.gamedev.net/reference/
parece que este es un hilo de 2 meses que acabo de notar ahora, pero qué diablos. He diseñado y desarrollado el marco de juego para un juego de mesa comercial en red. Tuvimos una experiencia muy agradable trabajando con eso.
Su juego probablemente puede estar en una cantidad (cercana) infinita de estados debido a las permutaciones de cosas como la cantidad de dinero que el jugador A tiene, cuánto dinero tiene el jugador B, y etc ... Por lo tanto, estoy bastante seguro de que quiere mantenerse alejado de las máquinas de estado.
La idea detrás de nuestro marco era representar el estado del juego como una estructura con todos los campos de datos que, en conjunto, proporcionan el estado completo del juego (es decir, si usted quiere guardar el juego en el disco, usted escribe esa estructura).
Usamos el patrón de comando para representar todas las acciones de juego válidas que un jugador podría realizar. Aquí hay una acción de ejemplo:
class RollDice : public Action
{
public:
RollDice(int player);
virtual void Apply(GameState& gameState) const; // Apply the action to the gamestate, modifying the gamestate
virtual bool IsLegal(const GameState& gameState) const; // Returns true if this is a legal action
};
Entonces, usted ve que para decidir si un movimiento es válido, puede construir esa acción y luego llamar a su función IsLegal, pasando en el estado actual del juego. Si es válido, y el jugador confirma la acción, puede llamar a la función Aplicar para modificar realmente el estado del juego. Al asegurarte de que tu código de juego solo puede modificar el estado del juego al crear y enviar Acciones legales (es decir, la familia de métodos Action :: Apply es lo único que modifica directamente el estado del juego), entonces te aseguras de que tu juego el estado nunca será inválido. Además, al usar el patrón de comando, usted puede serializar los movimientos deseados de su jugador y enviarlos a través de una red para ser ejecutados en los estados de juego de otros jugadores.
Terminó siendo uno con este sistema que resultó tener una solución bastante elegante. A veces las acciones tendrían dos o más fases. Por ejemplo, el jugador puede aterrizar en una propiedad en Monopoly y ahora debe tomar una nueva decisión. ¿Cuál es el estado del juego entre cuándo el jugador rodó los dados y antes de decidir comprar una propiedad o no? Manejamos situaciones como esta presentando un miembro de "Contexto de Acción" de nuestro estado de juego. El contexto de acción normalmente sería nulo, lo que indica que el juego no se encuentra actualmente en ningún estado especial. Cuando el jugador tira los dados y la acción de lanzar dados se aplica al estado del juego, se dará cuenta de que el jugador ha aterrizado en una propiedad no propiedad y puede crear un nuevo contexto de acción "PlayerDecideToPurchaseProperty" que contiene el índice del jugador estamos esperando una decisión de. En el momento en que la acción RollDice se haya completado, nuestro estado del juego representa que actualmente está esperando que el jugador especificado decida si comprar o no una propiedad. Ahora es fácil para todas las demás acciones que el método IsLegal devuelva falso, excepto las acciones "ComprarPropiedad" y "PasarpropiedadPromoverOportunidad", que solo son legales cuando el estado del juego tiene el contexto de acción "PlayerDecideToPurchaseProperty".
Mediante el uso de contextos de acción, nunca hay un solo punto en la vida del juego de mesa en el que la estructura del estado del juego no represente por completo lo que está sucediendo en el juego en ese momento. Esta es una propiedad muy deseable de su sistema de juego de mesa. Te será mucho más fácil escribir código cuando puedas encontrar todo lo que siempre quieras saber sobre lo que está sucediendo en el juego al examinar una sola estructura.
Además, se extiende muy bien a los entornos en red, donde los clientes pueden enviar sus acciones a través de una red a una máquina host, que puede aplicar la acción al estado del juego "oficial" del host y luego repetir esa acción a todos los demás clientes. haga que lo apliquen a sus estados de juego replicados.
Espero que esto sea conciso y útil.
Three Rings ofrece bibliotecas LGPL de Java. Nenya y Vilya son las bibliotecas para cosas relacionadas con los juegos.
Por supuesto, ayudaría si su pregunta menciona restricciones de plataforma y / o idioma que pueda tener.