Implementar el comportamiento en un simple juego de aventuras.

11

Ú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 Roomobjetos. Cada uno Roomtiene una lista de Entityobjetos que están en esa habitación. Cada uno Entitytiene 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 Roomutiliza el nombre de la entidad para devolver el apropiado Entityobjeto, 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 Roomobjeto muestra su propia cadena de descripción, luego agrega las cadenas de descripción de cada Entity. La Entitydescripció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 Entitysubclases 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?

Eric
fuente
1
Creo que necesita explicar qué hacen estas "funciones de acción" y tal vez publicar algún código, porque no estoy seguro de qué está hablando allí.
jhocking
Se agregó el código.
Eric

Respuestas:

5

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:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

ADICIÓN: aha, acabo de leer la respuesta de FxIII y este poco cerca del final me llamó la atención:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

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

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

pero la única puerta con una trampa de bola de fuego sería

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

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:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}
jhocking
fuente
1
Eso funciona para elementos comunes y repetibles. Pero, ¿qué pasa con las entidades que responden de manera única a la entrada del usuario? Crear una subclase de Entitysolo 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?
Eric
1
Me dirigí a los ejemplos que diste. No puedo leer tu mente; ¿Qué objetos y entradas quieres tener?
jhocking
Edité mi publicación para explicar mejor mis intenciones. Si entiendo su ejemplo correctamente, parece que cada etiqueta de entidad corresponde a alguna subclase de Entityy 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?
Eric
Sí, esa es la idea.
jhocking
Debería haber pensado que los componentes habrían sido parte de la solución. Gracias por la ayuda.
Eric
1

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:

  • un método para que el narrador agregue un elemento,
  • un método para que el motor muestre el elemento que contiene,
  • Un método para que el jugador elija un elemento.

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ódigo

En pocas palabras: scripts como archivo de configuración que pueden ejecutar código.

FxIII
fuente