Para divertirme, estoy tratando de escribir uno de los juegos de mesa favoritos de mi hijo como una pieza de software. Eventualmente espero construir una interfaz de usuario de WPF sobre ella, 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 a muchos juegos de mesa, y quizás otros ya los hayan resuelto mejor que yo.
(Tenga en cuenta que la inteligencia artificial para jugar el juego 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 de los jugadores (por ejemplo, dinero, puntuación), su nombre, etc.
Un objeto que representa el estado del juego: los jugadores, quién es el turno, la disposición de las piezas en el tablero, etc.
Una máquina de estado que gestiona la secuencia de turnos. Por ejemplo, muchos juegos tienen un pequeño pre-juego donde cada jugador tira 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 su lugar, luego otros jugadores adivinan qué raza de pollo son, luego reciben puntos.
¿Existe algún estado de la técnica que pueda aprovechar?
EDITAR: Una cosa de la que me di cuenta recientemente es que el estado del juego se puede dividir en dos categorías:
Estado del artefacto del juego . "Tengo $ 10" o "mi mano izquierda está en azul".
Estado de la secuencia del juego . "He tirado dobles dos veces; la siguiente me mete en la cárcel". Una máquina de estados puede tener sentido aquí.
EDITAR: Lo que realmente estoy buscando aquí es la mejor manera de implementar juegos multijugador basados en turnos como Ajedrez, Scrabble o Monopolio. Estoy seguro de que podría crear un juego de este tipo simplemente trabajando en él de principio a fin, pero, al igual que otros patrones de diseño, es probable que haya algunas formas de hacer que las cosas funcionen mucho mejor que no son obvias sin un estudio cuidadoso. Eso es lo que espero.
fuente
Respuestas:
Parece que este es un hilo de 2 meses que acabo de notar ahora, pero qué diablos. Ya diseñé y desarrollé el marco de juego para un juego de mesa comercial en red. Tuvimos una experiencia muy agradable trabajando con él.
Tu juego probablemente puede estar en una cantidad (cercana a) infinita de estados debido a las permutaciones de cosas como cuánto dinero tiene el jugador A, cuánto dinero tiene el jugador B, etc. Por lo tanto, estoy bastante seguro de que quieres 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, juntos, proporcionan el estado completo del juego (es decir, si quieres guardar el juego en el disco, escribes esa estructura).
Usamos el patrón de comando para representar todas las acciones de juego válidas que un jugador podía realizar. A continuación se muestra una acción de ejemplo:
Entonces ves que para decidir si un movimiento es válido, puedes construir esa acción y luego llamar a su función IsLegal, pasando al estado actual del juego. Si es válido y el jugador confirma la acción, puede llamar a la función Aplicar para modificar el estado del juego. Asegurándose de que su código de juego solo pueda modificar el estado del juego creando y enviando Acciones legales (en otras palabras, la familia de métodos Action :: Apply es lo único que modifica directamente el estado del juego), entonces se asegura de que su juego El estado nunca será inválido. Además, al usar el patrón de comando, hace posible serializar los movimientos deseados de su jugador y enviarlos a través de una red para que se ejecuten en los estados de juego de otros jugadores.
Terminó siendo un problema 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 el momento en que el jugador lanzó los dados y antes de que decida 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 la acción normalmente sería nulo, lo que indica que el juego no se encuentra actualmente en ningún estado especial. Cuando el jugador lanza los dados y la acción de tirar los dados se aplica al estado del juego, se dará cuenta de que el jugador ha aterrizado en una propiedad que no tiene y puede crear una nueva "PlayerDecideToPurchaseProperty" contexto de acción que contiene el índice del jugador del que estamos esperando una decisión. Para cuando la acción RollDice se haya completado, nuestro estado de juego representa que actualmente está esperando que el jugador especificado decida si comprar una propiedad o no. Ahora es fácil que el método IsLegal de todas las demás acciones devuelva falso, excepto las acciones "BuyProperty" y "PassPropertyPurchaseOpportunity", 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 EXACTAMENTE lo que está sucediendo en el juego en ese momento. Esta es una propiedad muy deseable de su sistema de juego de mesa. Te resultará mucho más fácil escribir código cuando puedas encontrar todo lo que quieras saber sobre lo que está sucediendo en el juego examinando solo una estructura.
Además, se extiende muy bien a 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 de juego "oficial" del host, y luego repetir esa acción a todos los demás clientes para pídales que lo apliquen a sus estados de juego replicados.
Espero que esto haya sido conciso y útil.
fuente
La estructura básica de su motor de juego utiliza el patrón de estado . Los elementos de tu caja de juego son singletons de varias clases. La estructura de cada estado puede utilizar el Patrón de estrategia o el Método de plantilla .
Se utiliza una fábrica para crear los jugadores que se insertan en una lista de jugadores, otro singleton. La GUI vigilará el motor de juego mediante el uso del patrón Observer e interactuará con este mediante el uso de uno de los varios objetos de comando creados con el patrón de comando . El uso de Observer y Command se puede usar en el contexto de una vista pasiva, pero se puede usar casi cualquier patrón MVP / MVC según sus preferencias. Cuando guarde el juego, debe tomar un recuerdo de su estado actual
Recomiendo mirar algunos de los patrones en este sitio y ver si alguno de ellos lo toma como punto de partida. Una vez más, el corazón de su tablero de juego será una máquina de estados. La mayoría de los juegos estarán representados por dos estados antes del juego / configuración y el juego real. Pero puedes tener más estados si el juego que estás modelando tiene varios modos de juego distintos. 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, tablero principal, tablero de batalla con el juego cambiando continuamente entre el tablero principal y el tablero de batalla. Por supuesto, la secuencia de turnos también se puede representar mediante una máquina de estado.
fuente
Acabo de terminar de diseñar e implementar un juego basado en estados usando polimorfismo.
Usando una clase abstracta base llamada
GamePhase
que tiene un método importanteLo que esto significa es que cada
GamePhase
objeto contiene el estado actual del juego, y una llamada aturn()
mira su estado actual y devuelve el siguienteGamePhase
.Cada hormigón
GamePhase
tiene constructores que mantienen todo el estado del juego. Cadaturn()
método tiene un poco de las reglas del juego dentro de ellos. Si bien esto difunde las reglas, mantiene las reglas relacionadas juntas. El resultado final de cada unoturn()
es simplemente crear el siguienteGamePhase
y pasar en el estado completo a la siguiente fase.Esto permite
turn()
ser muy flexible. Dependiendo de su juego, un estado dado puede ramificarse en 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 manejarlo es muy simple:
Esto es extremadamente útil ya que ahora puedo crear fácilmente cualquier estado / fase del juego para probar
Ahora, para responder a la segunda parte de tu pregunta, ¿cómo funciona esto en el modo multijugador? Dentro de ciertos mensajes de correo
GamePhase
electrónico que requieren la entrada del usuario, una llamada deturn()
preguntará a la corrientePlayer
suStrategy
estado / fase actual.Strategy
es solo una interfaz de todas las decisiones posibles quePlayer
puede tomar. ¡Esta configuración también permiteStrategy
ser implementada con IA!También Andrew Top dijo:
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 un parámetro entero para los constructores de mi
GamePhase
s concreto .Monopolio
Ejemplo de algunos
GamePhase
s sería:Y algunos estados en la base
GamePhase
son:Y luego, algunas fases registrarían su propio estado según sea 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.
Se pueden reutilizar y vincular muchas fases. Por ejemplo
GamePhase
CommunityChestAdvanceToGo
, crearía la siguiente fasePlayerLandsOnGo
con el estado actual y la devolvería. En el constructor delPlayerLandsOnGo
jugador actual se movería a Go y su dinero se incrementaría en $ 200.fuente
Por supuesto que hay muchos, muchos, muchos, muchos, muchos, muchos, muchos recursos sobre este tema. Pero creo que está en el camino correcto al dividir los objetos y dejar que manejen sus propios eventos / datos, etc.
Al hacer juegos de mesa basados en mosaicos, le resultará agradable tener rutinas para mapear entre la matriz del tablero y la fila / columna y viceversa, junto con otras características. Recuerdo mi primer juego de mesa (hace mucho tiempo) cuando luché con cómo obtener row / col de boardarray 5.
Nostalgia. ;)
De todos modos, http://www.gamedev.net/ es un buen lugar para obtener información. http://www.gamedev.net/reference/
fuente
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 artículos académicos como Design Patterns for Games . También hay al menos un libro disponible en Amazon, Patterns in Game Design .
fuente
Three Rings ofrece bibliotecas de Java LGPL. Nenya y Vilya son las bibliotecas para cosas relacionadas con el juego.
Por supuesto, ayudaría si su pregunta mencionara las restricciones de plataforma y / o idioma que podría tener.
fuente
Estoy de acuerdo con la respuesta de Pyrolistical y prefiero su forma de hacer las cosas (aunque solo eché un vistazo a las otras respuestas).
Casualmente también usé su nombre "GamePhase". Básicamente, lo que haría en el caso de un juego de mesa por turnos es que tu clase GameState contenga un objeto del GamePhase abstracto como lo menciona Pyrolistical.
Digamos que los estados del juego son:
Podría tener clases derivadas concretas para cada estado. Tener funciones virtuales al menos para:
En la función StartPhase () puede establecer todos los valores iniciales para un estado, por ejemplo, deshabilitar la entrada del otro jugador y así sucesivamente.
Cuando se llama a roll.EndPhase (), asegúrese de que el puntero de GamePhase se establezca en el siguiente estado.
En este MovePhase :: StartPhase (), por ejemplo, establecerías los movimientos restantes del jugador activo a la cantidad obtenida en la fase anterior.
Ahora, con este diseño en su lugar, podría resolver su problema de "3 x doble = cárcel" dentro de la fase Roll. La clase RollPhase puede manejar su propio estado. Por ejemplo
Me diferencia de Pyrolistical en que debería haber una fase para todo, incluso cuando el jugador aterriza en el cofre de la comunidad o algo así. Manejaría todo esto en MovePhase. Esto se debe a que si tiene demasiadas fases secuenciales, es muy probable que el jugador se sienta demasiado "guiado". Por ejemplo, si hay una fase en la que el jugador SOLO puede comprar propiedades y luego SOLO comprar hoteles y luego SOLO comprar casas, es como si no hubiera libertad. Simplemente coloque todas esas partes en una BuyPhase y déle al jugador la libertad de comprar lo que quiera. La clase BuyPhase puede manejar fácilmente qué compras son legales.
Finalmente, abordemos el tablero de juego. Aunque una matriz 2D está bien, recomendaría tener un gráfico de mosaico (donde un mosaico es una posición en el tablero). En el caso del monopolio, sería preferible una lista doblemente enlazada. Entonces cada mosaico tendría un:
Entonces sería mucho más fácil hacer algo como:
La función AdvanceTo puede manejar sus animaciones paso a paso o lo que quiera. Y también disminuir los movimientos restantes, por supuesto.
El consejo de RS Conley sobre el patrón de observador para la GUI es bueno.
No he publicado mucho antes. Espero que esto ayude a alguien.
fuente
Si su pregunta no es específica de un idioma o plataforma. entonces le recomendaría que considere los patrones AOP para estado, recuerdo, comando, etc.
¿Cuál es la respuesta de .NET a AOP?
También intente encontrar algunos sitios web interesantes como http://www.chessbin.com
fuente