Últimamente me he entretenido programando un simple juego de aventuras basado en texto, y estoy atascado en lo que parece un problema de diseño muy simple.
Para dar una breve descripción: el juego se divide en Room
objetos. Cada uno Room
tiene una lista de Entity
objetos que están en esa habitación. Cada uno Entity
tiene un estado de evento, que es una cadena simple-> mapa booleano, y una lista de acciones, que es una cadena-> mapa de funciones.
La entrada del usuario toma la forma [action] [entity]
. El Room
utiliza el nombre de la entidad para devolver el apropiado Entity
objeto, que luego se utiliza el nombre de acción para encontrar la función correcta, y lo ejecuta.
Para generar la descripción de la sala, cada Room
objeto muestra su propia cadena de descripción, luego agrega las cadenas de descripción de cada Entity
. La Entity
descripción puede cambiar según su estado ("La puerta está abierta", "La puerta está cerrada", "La puerta está cerrada", etc.).
Aquí está el problema: al usar este método, la cantidad de funciones de descripción y acción que necesito implementar se sale de control rápidamente. Mi sala de inicio solo tiene alrededor de 20 funciones entre 5 entidades.
Puedo combinar todas las acciones en una sola función y if-else / cambiar a través de ellas, pero aún son dos funciones por entidad. También puedo crear Entity
subclases específicas para objetos comunes / genéricos como puertas y llaves, pero eso solo me lleva muy lejos.
EDITAR 1: según se solicite, ejemplos de pseudocódigo de estas funciones de acción.
string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
if thisEntity["is_searched"] then
return "There was nothing more in the bushes."
else
thisEntity["is_searched"] := true
currentRoom.setEntity("dungeonDoorKey")
return "You found a key in the bushes."
end if
string dungeonDoorKeyUse(currentRoom, thisEntity, player)
if getEntity("outsideDungeonDoor")["is_locked"] then
getEntity("outsideDungeonDoor")["is_locked"] := false
return "You unlocked the door."
else
return "The door is already unlocked."
end if
Las funciones de descripción actúan casi de la misma manera, verifican el estado y devuelven la cadena apropiada.
EDIT 2: Revisé mi redacción de preguntas. Suponga que puede haber un número significativo de objetos en el juego que no comparten un comportamiento común (respuestas basadas en el estado a acciones específicas) con otros objetos. ¿Hay alguna manera de definir estos comportamientos únicos de una manera más limpia y fácil de mantener que escribiendo una función personalizada para cada acción específica de la entidad?
Respuestas:
En lugar de hacer una función separada para cada combinación de sustantivos y verbos, debes configurar una arquitectura donde haya una interfaz común que implementen todos los objetos del juego.
Un enfoque fuera de mi cabeza sería definir un objeto Entidad que extiendan todos los objetos específicos de tu juego. Cada entidad tendrá una tabla (cualquiera que sea la estructura de datos que utilice su idioma para las matrices asociativas) que asocia diferentes acciones con diferentes resultados. Las acciones en la tabla probablemente serán cadenas (por ejemplo, "abierto"), mientras que el resultado asociado podría incluso ser una función privada en el objeto si su lenguaje admite funciones de primera clase.
Del mismo modo, el estado del objeto se almacena en varios campos del objeto. Entonces, por ejemplo, puede tener una matriz de cosas en un Bush, y luego la función asociada con "buscar" actuará en esa matriz, ya sea devolviendo el objeto encontrado o la cadena "No había nada más en los arbustos".
Mientras tanto, uno de los métodos públicos es algo como Entity.actOn (acción de cadena) Luego, en ese método, compare la acción pasada con la tabla de acciones para ese objeto; si esa acción está en la tabla, devuelve el resultado.
Ahora, todas las diferentes funciones necesarias para cada objeto estarán contenidas dentro del objeto, lo que facilita la repetición de ese objeto en otras habitaciones (por ejemplo, crear una instancia del objeto Puerta en cada habitación que tenga una puerta)
Finalmente, defina todas las salas en XML o JSON o lo que sea para que pueda tener muchas salas únicas sin necesidad de escribir un código separado para cada sala. Cargue este archivo de datos cuando comience el juego y analice los datos para crear una instancia de los objetos que pueblan su juego. Algo como:
ADICIÓN: aha, acabo de leer la respuesta de FxIII y este poco cerca del final me llamó la atención:
Aunque no estoy de acuerdo con que se active una trampa de llama es algo que solo sucedería una vez (pude ver que esta trampa se reutiliza para muchos objetos diferentes) Creo que finalmente entiendo lo que querías decir sobre las entidades que reaccionan de manera única a la entrada del usuario. Probablemente abordaría cosas como hacer que una puerta de tu mazmorra tenga una trampa de bola de fuego construyendo todas mis entidades con una arquitectura de componentes (explicada en detalle en otra parte).
De esta manera, cada entidad de Puerta se construye como un paquete de componentes, y puedo mezclar y combinar componentes de manera flexible entre diferentes entidades. Por ejemplo, la mayoría de las puertas tendrían configuraciones como
pero la única puerta con una trampa de bola de fuego sería
y luego el único código único que tendría que escribir para esa puerta es el componente FireballTrap. Usaría los mismos componentes de bloqueo y portal que todas las otras puertas, y si luego decidiera usar FireballTrap en un cofre del tesoro o algo tan simple como agregar el componente FireballTrap a ese cofre.
Si define o no todos los componentes en el código compilado o en un lenguaje de scripting separado no es una gran distinción en mi mente (de cualquier manera va a escribir el código en alguna parte ) pero lo importante es que puede reducir significativamente cantidad de código único que necesita escribir. Diablos, si no le preocupa la flexibilidad para los diseñadores / modders de nivel (después de todo, está escribiendo este juego), incluso podría hacer que todas las entidades hereden de Entity y agregar componentes en el constructor en lugar de un archivo de configuración o script o lo que sea:
fuente
Entity
solo para un solo objeto agrupa el código pero no reduce la cantidad de código que tengo que escribir. ¿O es una trampa inevitable a este respecto?Entity
y los atributos definen su estado inicial. Supongo que las etiquetas secundarias de la entidad actúan como parámetros para cualquier acción con la que esté asociada la etiqueta, ¿verdad?El problema dimensional que aborda es bastante normal y casi inevitable. Desea encontrar una manera de expresar sus entidades que sea a la vez coincidente y flexible .
Un "contenedor" (el arbusto en la respuesta jhocking) es una forma coincidente , pero verá que no es lo suficientemente flexible .
No le sugiero que intente encontrar una interfaz genérica y luego usar archivos de configuración para especificar los comportamientos, porque siempre tendrá la desagradable sensación de estar entre una roca (entidades estándar y aburridas, fáciles de describir) y un lugar difícil ( entidades fantásticas únicas pero demasiado largas para implementar).
Mi sugerencia es usar un lenguaje interpretado para codificar comportamientos.
Piense en el ejemplo del arbusto: es un contenedor, pero nuestro arbusto debe tener elementos específicos dentro; el objeto contenedor puede tener:
Uno de estos artículos tiene una cuerda que desencadena un artilugio que a su vez dispara una llama que quema el arbusto ... (ya ves, puedo leer tu mente para saber las cosas que te gustan).
Puede usar una secuencia de comandos para describir este bush en lugar de un archivo de configuración que pone el código adicional relevante en un enlace que ejecuta desde su programa principal cada vez que alguien elige un elemento de un contenedor.
Ahora tiene muchas opciones de arquitectura: puede definir herramientas de comportamiento como clases base utilizando su lenguaje de código o el lenguaje de secuencias de comandos (cosas como contenedores, puertas, etc.). El propósito de estas cosas es permitirle describir las entidades agregando fácilmente comportamientos simples y configurándolos mediante enlaces en un lenguaje de script .
Todas las entidades deben ser accesibles para el script: puede asociar un identificador a cada entidad y colocarlas en un contenedor que se exporta en la extensión del script del lenguaje de script.
El uso de estrategias de secuencias de comandos le permite mantener su configuración simple (nada de
<item triggerFlamesOnPicking="true">
eso usará solo una vez) mientras le permite expresar comportamientos extraños (los divertidos) agregando alguna línea de códigoEn pocas palabras: scripts como archivo de configuración que pueden ejecutar código.
fuente