Historial de desarrollo en 2011 del proyecto Asalto y castigo en Forth

Descripción del contenido de la página

Historial de desarrollo durante 2011 del proyecto de la versión en Forth del juego conversacional Asalto y castigo.

Etiquetas:

2011-07-05

Tomado como punto de partida el código de un incipiente proyecto previo iniciado en SP-Forth. Primeros cambios en el código.

2011-07-09

Creados la introducción y el final del juego. Implementadas las palabras para hacer las pausas.

Creados los entes escenario, sus descripciones y el mapa.

Separados los nombres de los escenarios de sus descripciones, para tener más flexibilidad.

2011-07-10

Descripciones y vocabulario de los objetos. Localización inicial de los objetos y personajes.

2011-07-11

Corregido un error en el cálculo del tiempo en la palabra no_time_left?.

Cambiado el sistema de cálculo de pausas; usados los módulos dtm y dti de Forth Foundation Library.

2011-07-12

Implementado el sistema de configuración, mediante un fichero independiente con palabras en castellano. Para empezar se usa para elegir el número de líneas y columnas de la pantalla.

Reescritas las palabras para preguntar al jugador qué quiere hacer tras terminar el juego.

Implementado el sistema de distinción del sexo del jugador (no del protagonista), para ajustar algunos mensajes en consecuencia, y añadido al sistema de configuración.

Implementado el sistema dual para imprimir las citas: con raya y con comillas castellanas. Añadida la distinción al sistema de configuración.

2011-07-13

Creadas las variables relacionadas con la trama.

Completadas las palabras que comprueban las condiciones de finalización del juego, y las que muestran los mensajes finales según el resultado.

Ampliado el sistema de descripción de entes para distinguir los escenarios del resto. Colores y formato diferentes para cada caso.

Abandonado el uso de cadenas contadas (con c") y choose count para elegir textos al azar; en su lugar se usan cadenas estándar (con s") y dchoose (con el alias estético schoose). Así el código es más claro.

Creadas las palabras relacionadas con los diálogos entre el protagonista y los otros dos personajes del juego.

Modificada la definición de alias, tomada de la librería de contribuciones de SP- Forth, para que funcione de la forma tradicional, más cómoda: que tome una dirección de ejecucion de la pila, en lugar de un nombre del flujo de entrada.

2011-07-14

Corregidos fallos de concatenación de los nuevos textos alternativos en algunas descripciones.

Corregido plot (faltaba salir de plot tras la primera subtrama ejecutada).

Quitado do_exits (que tiene un fallo grave no localizado) de la definición de enter (donde en cualquier caso no era necesario, pues no se listan explícitamente las salidas al entrar en un escenario.

Creados los tres objetos que eran mencionados en la descripción del escenario 47: catre, velas y mesa.

Iniciadas las palabras que gestionan las tramas relacionadas con los entes escenario.

2011-07-15

Completadas las palabras que gestionan las tramas relacionadas con los entes escenario.

Dividido el código fundamental de imprimir párrafos justificados, para poder hacerlo con o sin salto de línea posterior.

Campo >decoration? para marcar los entes que no deben ser incluidos en la lista de objetos presentes porque ya son descritos como parte de su lugar.

Completados los créditos.

2011-07-16

Primera versión funcional del sistema de tramas de escenario.

2011-07-17

Reformado y ampliado el sistema de tramas de escenario.

Corregido y reformado el sistema de la trama global.

Nueva acción: nadar.

2011-07-18

Nuevo objeto: la coraza. Nuevas acciones: ponerse y quitarse prendas.

Perfeccionada la creación de listas de entes localizados en otro para indicar el estado de las prendas.

Terminada la implementación de los artículos posesivos.

2011-07-19

Factorizada la palabra /list con la nueva /list++ para hacer el código más legible.

Escrita la palabra >^uppercase para no modificar el contenido original de las cadenas, como hace ^uppercase.

Ampliados y perfeccionados los mensajes de error sobre acciones imposibles o que no tienen sentido.

Iniciada la trasformación de los textos que hablan del protagonista, de la tercera a la segunda persona.

Implementación de la resolución de ambigüedades en el vocabulario del jugador. Primeros dos casos: «hombre» y «ambrosio».

Modificación y ampliación de la ambientación de la batalla: fases cronológicas, cada una con un mensaje calculado al azar.

2011-07-20

Ampliación del sistema de mensajes de error del analizador.

2011-07-24

Inicio de la implementación del reconocimiento del segundo complemento de los comandos (complemento indirecto o preposicional).

2011-07-25

Implementada la indentación configurable de párrafos.

Ajustados los textos de presentación del inventario a la segunda persona. Distinguido el caso de que solo haya un ente en la lista.

La frase no se analiza si su longitud es cero.

Creado el ente «arco» con el sinónimo «puente».

Iniciada la implementación del ente global «cueva».

2011-07-27

El seguimiento de los hombres antes de la emboscada es movido de nuevo desde la trama global a la trama de cada escenario, como está en el original, para evitar que salgan los mensajes tras cada comando.

La descripción de la serpiente ya no se usa para informar del bloqueo de la salida. Para esto se usa la trama del escenario correspondiente, que comprueba si la serpiente está presente.

Corregido el fallo que creaba una indentación de un carácter aunque /indentation valiera cero. Es la palabra trm+move-cursor-right quien mueve el cursor una posición aunque su parámetro sea cero.

Mejora de los mensajes de error sobre movimientos imposibles.

Corregido error en battle_phase (faltaba cells en el cálculo de la dirección del elemento de la tabla).

Dividos los créditos y la introducción en varias palabras pequeñas; en el caso de la introducción, para ampliar sus textos con variantes aleatorias.

Perfeccionado el mensaje de error dedicado a las acciones sin sentido.

Creado el mensaje de error para acciones para las que no hay motivo.

2011-07-28

Implementado el código base para las acciones violentas, que en el juego original eran sinónimos: matar, atacar, romper, afilar, golpear...

Añadidas al mapa las conexiones «dentro» y «fuera» (todas impracticables por ahora).

2011-08-19

Añadidas al código fuente, en comentarios de Forth, las marcas usadas por Vim para plegar zonas del texto.

2011-09-05

Corregido un error en la construcción del vocabulario, que descuadraba la pila.

Mayúsculas en los nombres de los puntos cardinales cuando son nombres propios.

Corregido un error en obbey, que dejaba en la pila la dirección de la cadena recibida cuando esta estaba vacía.

Error corregido: no se utilizaba el devuelto por CATCH tras la llamada a EVALUATE en evaluate_answer (que evalúa las respuestas a las preguntas binarias) Se elimina CATCH porque no tiene utilidad.

Reorganizadas las fases de la batalla de la emboscada. Completados los textos de algunas de las fases que aún no los tienen.

2011-09-27

Completada la novena y última fase de la batalla (reducido en uno el número de fases).

2011-10-01

Ampliados los textos de algunas conversaciones con Ambrosio y con el jefe de los refugiados.

2011-10-02

Retoques en algunos textos.

2011-11-11

Mejoradas varios mensajes de error. Nuevo mensaje de error para acciones peligrosas.

Mejorados los mensajes al nadar, según la situación de la coraza (puesta, llevada en la mano o ausente).

La descripción del altar depende del estado del ídolo.

2011-11-15

Mensajes not_by_hand y not_with_that.

Adelantada la sección de depuración en el código, para poder llamar a .stack.

Completado el código del listado de salidas existentes en el escenario actual.

Corregidos errores en you_do_not_wear_it y en that$.

Nuevos campos calculados >direct_pronoun@ y >indirect_pronoun@.

>noun_ending@ factorizado en dos componentes: >gender_ending@ y >plural_ending@.

you_do_not_have_it ampliado con dos variantes aleatorias del mensaje.

Ampliados diversos textos, como los de intro_5.

Nueva palabra clear_screen_for_location.

Las variables location_page? y location_scene? pasar a estar a cero de forma predeterminada en el código, pero activadas en el fichero de configuración.

Corregido error: faltaba hacer desaparecer la coraza tras bucear.

Nuevo objeto de decoración: el puente semipodrido en el escenario del mismo nombre.

2011-11-16

Nueva palabra something_so_crazy$ para usar en algunos mensajes de error, en lugar something_like_that$.

Inicio de la reescritura del sistema de errores del análisis y las acciones, con CATCH y THROW.

Movida la palabra save_ayc, aún en pruebas, a la sección inicial «Meta».

2011-11-18

Primera versión operativa del sistema de errores con CATCH y THROW. Adaptación de las primeras acciones.

Ampliado el cálculo de artículos para incluir los adjetivos «ningún/a/s», tratados como si fueran artículos negativos.

2011-11-19

Corregido el error en los artículos «negativos» producido por el carácter «ú» y el cálculo de la longitud de una cadena codifica en UTF -8.

Creado el campo >personal_name? para marcar los entes que tiene nombre propio. Eso permite calcular el artículo adecuado en algunos casos.

Completada y perfeccionada la palabra >subjective_negative_name@; creada también >subjective_negative_name_as_direct_object@ para mensajes en que el nombre hace de objeto directo.

Creado el campo virtual >known?@ para combinar >familiar y >known?.

Implementado el control de límite en more_familiar.

Completado el sistema de errores condicionales con throw. Factorizado para que sea aplicable a cualquier ente, no solo al complemento directo de la acción.

Creado el indicador silent_well_done? para controlar el funcionamiento de well_done durante las acciones automáticas intermedias, aún no implementadas.

Escritura de las palabras para las acciones de abrir y cerrar.

Ampliado el sistema de filtros de error. Nueva nomenclatura homogénea y generalizable para las palabras, por ejemplo {hold} y direct_complement{hold}.

2011-11-20

Ampliado el sistema de filtros con versiones que no actúan si reciben cero (por ejemplo ?{hold}). Esto hace más fácil aplicar filtros de error a los complementos del comando cuando estos son opcionales.

Primeras versiones de las acciones de romper y matar.

2011-11-25

Creadas las palabras para devolver los artículos definido e indefinido y el pronombre correspondientes al género y número gramaticales de un ente. También algunos adjetivos demostrativos, que se usan como artículos «distantes»: ese, esa, esos, esas.

2011-11-26

Primer boceto del error que informa de que un ente no tiene nada especial, con todas sus variantes y casos especiales.

2011-11-27

Primeros pasos para reunir todas las operaciones de creación de cada ente. Esto hará más cómoda la creación y edición de entes. Ahora el código necesario para crear entes está repartido en siete lugares: identificadores, nombres, atributos, descripción, salidas, localización y vocabulario asociado. La intención es crear como mucho solo cuatro bloques: identificadores, atributos (que incluye nombre, descripción y salidas), localización y vocabulario. La localización no se puede juntar con el resto de propiedades a menos que definamos previamente los identificadores como vectores, lo que probablemente no merece la pena.

Reorganización del sistema de errores: los identificadores se crean como vectores, todos juntos y por anticipado. Esto tiene ventajas, pero además ha sido necesario tras mover código de lugar para lograr el obtivo de reunir las operaciones de creción de entes.

Nuevas palabras schoose{ y }schoose para posibilitar una sintaxis más cómoda y segura que schoose con listas largas de cadenas.

Ampliación y mejora del error que informa de que algo no puede hacerse sin herramienta.

Mejorados el código y los mensajes de apertura del candado y de la puerta. La aparición del candado se gestiona ahora en una palabra propia, usada en la descripción de la puerta y en su intento de apertura.

2011-11-28

Agrupada la inicialización de todos los atributos de un ente en una sola palabra; otra se ocupa de la dirección. Aún es necesario crear los identificadores previamente, pero en conjunto el resultado es muy bueno. Solo hay tres zonas en lugar de siete: identificadores, atributos y descripción.

Terminado el nuevo sistema de inicialización de los entes.

Simplificado el sistema de creación de las tramas asociadas a lugares. El código anterior creaba una tabla con las direcciones de ejecución de las palabras, para restaurar con ellas la base de datos en cada partida:

\ Crear la tabla para guardar las direcciones de ejecución de las tramas de los entes escenario
create location_plots_xt  \ Tabla
#entities cells dup allot  \ Hacer el espacio necesario
location_plots_xt swap erase  \ Borrar la zona con ceros

: :location_plot  ( a -- xt a ) \ Crea una palabra sin nombre que manejará la trama de un ente escenario
  :noname swap
  ;
: ;location_plot  ( xt a -- )  \ Termina la definición de una palabra que manejará la trama de un ente escenario
  \ a = Ente escenario cuya palabra de trama se ha creado
  \ xt = Dirección de ejecución de la palabra de trama
  2dup  entity># cell *  location_plots_xt + !  \ Guardar xt en la posición de la tabla LOCATION_PLOTS_XT correspondiente al ente
  >location_plot_xt !  \ Guardar xt en la ficha del ente
  postpone ;  \ Terminar la definición de la palabra
  ; immediate
: (location_plot)  ( a -- )  \ Ejecuta la palabra de trama de un ente escenario
  >location_plot_xt @ ?dup  if  execute  then
  ;
' (location_plot) is location_plot
: init_location_plots  \ Restaura las tramas originales de los entes escenario
  #entities 0  do
    i cell * location_plots_xt + @  \ Tomar de la tabla la dirección de ejecución
    ?dup if  i #>entity >location_plot_xt !  then  \ Si no es cero, guardarla en su ficha
  loop
  ;

El método nuevo es más sencillo: guarda las direcciones de ejecución en la propia ficha desde el principio:

: :location_plot  ( a -- xt a ) \ Crea una palabra sin nombre que manejará la trama de un ente escenario
  :noname swap >location_plot_xt !  \ Crear la palabra y guardar su xt en la ficha del ente
  ;
' ; alias ;location_plot immediate
: (location_plot)  ( a -- )  \ Ejecuta la palabra de trama de un ente escenario
  >location_plot_xt @ ?dup  if  execute  then
  ;
' (location_plot) is location_plot

Después, el campo >location_plot será preservado antes de cada restauración, como ya se hace con los demás campos especiales, con el método creado recientemente para la definición de los atributos de cada ente:

variable name_str_backup
variable init_xt_backup
variable description_xt_backup
variable location_plot_xt_backup
: backup_entity  ( a -- )  \ Respalda los datos que se calcularon la primera vez y deben preservarse.
  dup >name_str @ name_str_backup !
  dup >init_xt @ init_xt_backup !
  dup >location_plot_xt @ location_plot_xt_backup !
  >description_xt @ description_xt_backup !
  ;
: restore_entity  ( a -- )  \ Restaura los datos que se calcularon la primera vez y deben preservarse.
  name_str_backup @ over >name_str !
  init_xt_backup @ over >init_xt !
  location_plot_xt_backup @ over >location_plot_xt !
  description_xt_backup @ swap >description_xt !
  ;
: setup_entity  ( a -- )  \ Prepara la ficha de un ente para ser completada con sus datos
  dup backup_entity  dup erase_entity  restore_entity
  ;
0 value this  \ Guardará el ente que está siendo definido
: [:attributes]  ( a -- )  \ Inicia la definición de propiedades de un ente
  \ Esta palabra se ejecuta cada vez que hay que restaurar los datos del ente
  dup to this  \ Actualizar el puntero al ente
  setup_entity
  ;
: :name_str  ( a -- )  \ Crea una cadena dinámica para guardar el nombre del ente.
  str-new swap >name_str !
  ;
: (:attributes)  ( a xt -- )  \ Operaciones preliminares para la definición de atributos de un ente
  \ Esta palabra solo se ejecuta una vez, cuando se compila el código
  \ a = Ente para la definición de cuyos atributos se ha creado una palabra
  \ xt = Dirección de ejecución de la palabra creada
  over >init_xt !  \ Conservar la dirección en la ficha del ente
  :name_str  \ Crear una cadena dinámica para el campo >NAME_STR
  ;
: :attributes  ( a -- )  \ Inicia la creación de una palabra sin nombre que definirá las propiedades de un ente
  :noname (:attributes)  \ Crear la palabra y hacer las operaciones preliminares
  postpone [:attributes]  \ Compilar la palabra (:ATTRIBUTES) en la palabra creada
  ;
' ; alias ;attributes immediate

El mismo método se podrá ampliar fácilmente para dividir las tramas de lugares en tres: al entrar en el escenario; en cada turno que se pase en el escenario; y al salir del escenario.

2011-11-29

Simplificación de la sintaxis para definir sinónimos en el vocabulario del juego. Las herramientas hasta ahora eran:

: synonym:  ( xt "name" -- )  \ Crea un sinónimo de una palabra
  \ xt = Dirección de ejecución de la palabra a clonar
  nextword sheader  last-cfa @ !
  ;
: synonyms:  ( xt u "name 1"..."name u" -- )  \ Crea uno o varios sinónimos de una palabra
  \ xt = Dirección de ejecución de la palabra a clonar
  \ u = Número de sinónimos que siguen en el flujo de entrada
  0  do  dup synonym:  loop  drop
  ;

Que posibilitaban la siguiente sintaxis:

: hablar  do_speak_xt action!  ;
' hablar synonym: habla
' hablar 2 synonyms: háblale hablarle
' hablar 4 synonyms: conversar conversa charlar charla

Al modificar el vocabulario había que recordar actualizar la cuenta de sinónimos definidos. Para evitar esta incomodidad, las nuevas herramientas son:

: synonym  ( xt a u -- )  \ Crea un sinónimo de una palabra
  \ xt = Dirección de ejecución de la palabra
  \ a u = Nombre del sinónimo
  sheader  last-cfa @ !
  ;
: synonyms:  ( xt "name 0"..."name n" "]synonyms2" -- )  \ Crea uno o varios sinónimos de una palabra
  \ xt = Dirección de ejecución de la palabra a clonar
  begin  dup nextword 2dup s" ;" compare
  while  synonym
  repeat  2drop 2drop
  ;

Y por tanto la nueva sintaxis, homogénea y más cómoda, es:

: hablar  do_speak_xt action!  ;
' hablar synonyms:  habla   ;
' hablar synonyms:  háblale hablarle  ;
' hablar synonyms:  conversar conversa charlar charla  ;

Ampliados todos los verbos con formas en primera y tercera personas, ordenadas para facilitar la edición.

Creado el código para resolver varias ambigüedades: partir (dividir o marchar), parte (pedazo o marchar) y cierre (cerrar o candado).

Un retoque más en la sintaxis de sinónimos:

: parse_synonym  ( -- a u)  \ Devuelve el siguiente sinónimo de la lista
  begin  parse-name dup 0=
  while  2drop refill 0= abort" Error en el código fuente: lista de sinónimos incompleta"
  repeat
  ;
: synonyms:  ( xt "name 0"..."name n" "]synonyms2" -- )  \ Crea uno o varios sinónimos de una palabra
  \ xt = Dirección de ejecución de la palabra a clonar
  begin  dup parse_synonym 2dup s" ;" compare
  while  (alias)
  repeat  2drop 2drop
  ;

Y el código resulta más claro:

: romper  do_break_xt action!  ;
' romper synonyms:
  rompe rompo rompa
  despedazar despedaza despedazo despedace
  destrozar destroza destrozo destroce
  ;

Aún es posible afinar más, unificando la creación de la palabra con la lista de sinónimos, o evitando que la lista de sinónimos parezca la definición de una palabra, pues no lo es.

Los campos de la base de datos han sido renombrados con el prefijo «~» en lugar de «>»; y los identificadores de entes con el sufijo «%» en lugar de «_e».

2011-11-30

Corregido el fallo en la inicialización de los entes; era un desajuste en la pila entre las operaciones de respaldo y restauración de los campos especiales.

En las palabras de descripción, la constante _% apunta al ente al que se refieren, igual que se en las palabras de definición de atributos. Esto aligera un poco la sintaxis.

Las acciones se definen ahora con las nuevas palabras :action, ;action y action:. Esto permite hacer referencia, durante la compilación, a acciones que aún no han sido definidas.

: action:  ( "name" -- )  \ Crear un identificador de acción
  \ "name" = nombre del identificador de la acción, en el flujo de entrada
  create  \ Crea una palabra con el nombre indicado...
    ['] noop ,  \ ...y guarda en su campo de datos la dirección de ejecución de NOOP
  does>  ( a -- )  \ Cuando la palabra sea llamada...
    @ execute  \ ...ejecutará la dirección de ejecución que tenga guardada
  ;
: :action  ( "name" -- )  \ Inicia la definición de una palabra que ejecutará una acción
  \ "name" = nombre del identificador de la acción, en el flujo de entrada
  :noname  ( xt )  \ Crea una palabra para la acción
  ' >body !  \ Guarda su dirección de ejecución en el campo de datos del identificador de la acción
  ;
' ; alias ;action immediate

De esta manera además se unifica la sintaxis con la de definición de atributos y descripciones de los entes.

Un método alternativo considerado es usar una variable para guardar el identificador de la acción, pero por ello con el inconveniente que necesitar una operación adicional para hacer la llamada a la misma:

' variable alias action:  \ Una variable servirá de identificador de acción
: :action  ( a -- )  \ Inicia la definición de una palabra que ejecutará una acción
  \ a = Dirección del identificador de la acción
  :noname  \ Crea la palabra
  swap !  \ Guarda su dirección de ejecución en el identificador de la acción
  ;
' ; alias ;action immediate

Implementado el nuevo sistema de descripción de las salidas en cada escenario. Queda extraer de cada descripción general las descripciones parciales aplicables a las direcciones, y completarlas con las de los escenarios vecinos.

2011-12-01

Nueva acción: «otear».

Detectado un extraño problema de incompatibilidad entre el uso del almacén circular de cadenas (programa csb2) y la palabra SAVE de SP- Forth, al parecer debido a que las palabras que manipulan las cadenas para construir el nombre del fichero que se usa como parámetro de SAVE usan el almacén circular. Esto ocurre tanto si el almacén está en memoria, reservada con ALLOCATE, como en espacio del diccionario, reservado con ALLOT; aunque los errores son diferentes. La causa última de los errores no está clara, pero no hay duda del origen: El comando S" fichero" SAVE funciona bien con la palabra S" original y provoca un error con la versión nueva creada por csb2 y que usa el almacén circular para guardar la cadena.

2011-12-02

Completado y el código pendiente en la concatenación de la cadena dinámica de impresión; y factorizadas las palabras principales de concatenación.

Antes:

: space+?  ( -- f )  \ ¿Se debe añadir un espacio al concatenar una cadena a la cadena dinámico PRINT_STR ?
  \ Inacabada!!!
  print_str str-length@ 0<>
  ;
: »&  ( a u -- )  \ Añade una cadena al final de la cadena dinámica TXT, con un espacio de separación
  space+?  if  bl print_str str-append-char  then  »+
  ;
: «&  ( a u -- )  \ Añade una cadena al principio de la cadena dinámica TXT, con un espacio de separación
  space+?  if  bl print_str str-prepend-char  then  «+
  ;

Ahora:

: «c+  ( c -- )  \ Añade un carácter al principio de la cadena dinámica PRINT_STR
  print_str str-prepend-char
  ;
: »c+  ( c -- )  \ Añade un carácter al final de la cadena dinámica PRINT_STR
  print_str str-append-char
  ;
: «»bl+?  ( u -- f )  \ ¿Se debe añadir un espacio al concatenar una cadena a la cadena dinámica PRINT_STR ?
  \ u = Longitud de la cadena que se pretende unir a la cadena dinámica PRINT_STR
  0<> print_str str-length@ 0<> and
  ;
: »&  ( a u -- )  \ Añade una cadena al final de la cadena dinámica TXT, con un espacio de separación
  dup «»bl+?  if  bl »c+  then  »+
  ;
: «&  ( a u -- )  \ Añade una cadena al principio de la cadena dinámica TXT, con un espacio de separación
  dup «»bl+?  if  bl «c+  then  «+
  ;

Tras la pruebas realizadas, parece improbable que funcione de forma segura y fiable la creación de un ejecutable con el estado del juego.

Este es el boceto del código de pruebas:

: n>s  ( u -- a1 u1 )  \ Convierte un número en una cadena (con dos dígitos como mínimo)
  s>d <# # #s #> >csb
  ;
: n>s+  ( u a1 u1 -- a2 u2 )  \ Añade a una cadena un número tras convertirlo en cadena
  rot n>s s+
  ;
: yyyymmddhhmmss$  ( -- a u )  \ Devuelve la fecha y hora actuales como una cadena en formato «aaaammddhhmmss»
  time&date n>s n>s+ n>s+ n>s+ n>s+ n>s+
  ;
: file_name$  ( -- a u )  \ Devuelve el nombre con que se grabará el juego
  s" ayc_" yyyymmddhhmmss$ s+
  s" .exe" windows? and s+  \ Añadir sufijo si estamos en Windows
  ;
defer reenter
svariable filename
: (do_save_the_game)  \ Graba el juego
  \ Inacabado!!! No está decidido el sistema que se usará para salvar las partidas.
  \ 2011-12-01 No funciona bien. Muestra mensajes de gcc con parámetros sacados de textos del programa!
\  false to spf-init?  \ Desactivar la inicialización del sistema
\  true to console?  \ Activar el modo de consola (no está claro en el manual)
\  false to gui?  \ Desactivar el modo gráfico (no está claro en el manual)
  ['] reenter to <main>  \ Actualizar la palabra que se ejecutará al arrancar
\  file_name$ save  clear_screen
  file_name$ filename place filename count save
  ;
:action do_save_the_game  \ Acción de salvar el juego
  main_complement{forbidden}
  (do_save_the_game)
  ;action

No obstante, se necesita una palabra para crear un ejecutable principal. Para no usar el almacén circular de cadenas con el parámetro del nombre de fichero que hay que pasar a SAVE, hemos creado un alias de S" (llamado P", en referencia al PAD) antes de que el programa csb2 cree su propia versión, y así poder usar posteriormente la versión original:

defer main
: program_filename$  ( -- a u )  \ Devuelve el nombre con que se grabará el juego
  \ Nota: No se puede usar la versión actual S" (que usa el almacén circular de cadenas)
  \ sino la original (que usa el PAD ) y cuyo alias está en P" .
  \ De otro modo SAVE no funcionará bien:
  \ mostrará mensajes de gcc con parámetros sacados de textos del programa,
  \ y creará el fichero objeto pero no el ejecutable.
  \ La causa última del problema no está clara.
  windows?  if p" ayc.exe"  else  p" ayc"  then
  ;
: create_executable  \ Crea un ejecutable con el programa
\  false to spf-init?  \ Desactivar la inicialización del sistema
\  true to console?  \ Activar el modo de consola (no está claro en el manual)
\  false to gui?  \ Desactivar el modo gráfico (no está claro en el manual)
  ['] main to <main>  \ Actualizar la palabra que se ejecutará al arrancar
  program_filename$ save
  ;

Para guardar el estado de la partida usaremos ficheros de texto que reproduzcan el código Forth necesario para restaurarlas. El fichero ocupará menos y el método será muy fácil de trasportar entre diferentes sistemas Forth llegado el momento.

Renombramos las palabras schoose{ y }schoose como s{ y }s para aligerar la sintaxis. Creamos las palabras }s& }s+ para concatenar el resultado de la selección en un único paso. Esto hace el código más ligero y legible en las zonas en que se crean textos complejos a partir de cadenas elegidas al azar.

Nuevas palabras para aligerar aún más la sintaxis de creación de textos aleatorios: s?, s?& y 0$.

2011-12-03

Nuevas versiones de s{ y }s, anidables. Las anteriores eran:

variable schoose_depth  \ Guardará la profundidad de la pila cuando se ejecute S{
: s{  \ Inicia una zona de selección aleatoria de cadenas
  depth schoose_depth !
  ;
: }s  ( a1 u1 .. an un -- a' u' )  \ Elige una cadena entre las puestas en la pila desde que se ejecutó S{
  depth schoose_depth @ - 2 / schoose
  ;

Para poder ser anidables, las nuevas versiones necesitan una pila propia para guardar la profundidad de la pila en cada anidación:

4 constant /dstack  \ Elementos de la pila (y por tanto número máximo de anidaciones)
variable dstack>  \ Puntero al elemento superior de la pila (o cero si está vacía)
0 dstack> !  \ Pila vacía para empezar
/dstack cells allot  \ Hacer espacio para la pila
: 'dstack>  ( -- a )  \ Dirección del elemento superior de la pila
  dstack> dup @ cells +
  ;
: dstack_full?  ( -- f )  \ ¿Está la pila llena?
  dstack> @ /dstack =
  ;
: dstack_empty?  ( -- f )  \ ¿Está la pila vacía?
  dstack> @ 0=
  ;
: dstack!  ( u -- )  \ Guarda un elemento en la pila
  dstack_full? abort" Error de anidación de S{ y }S : su pila está llena."
  dstack> ++ 'dstack> !
  ;
: dstack@  ( -- u )  \ Devuelve el elemento superior de la pila
  dstack_empty? abort" Error de anidación de S{ y }S : su pila está vacía."
  'dstack> @ dstack> --
  ;
: s{  \ Inicia una zona de selección aleatoria de cadenas
  depth dstack!
  ;
: }s  ( a1 u1 .. an un -- a' u' )  \ Elige una cadena entre las puestas en la pila desde que se ejecutó S{
  depth dstack@ - 2 / schoose
  ;

Creado Halto 2 para ayudar en la depuración.

Completadas las descripciones de direcciones de varios escenarios más.

Nuevas palabras (whom y unknown_whom) para dotar de más inteligencia y versatilidad a algunas las acciones,

2011-12-04

Corregido un error en la tabla exits_table, que relaciona campos de dirección con entes dirección; el cálculo se había quedado anticuado, la tabla no se inicializaba correctamente y por ello no funcionaba la lista de salidas disponibles.

Completadas algunas de las descripciones pendientes de direcciones de escenarios.

Primera versión de palabras para probar todas las descripciones de forma automática, sin tener que interactuar con el juego.

Detectado un problema interesante: Tras crear un ejecutable con el programa y arrancarlo se produce un error grave en el sistema, debido a que las cadenas dinámicas que fueron creadas durante la compilación del código, para guardar el nombre de cada ente, lógicamente se han volatilizado, pues se crean con ALLOCATE en memoria reservada en cada caso por el sistema... ¿Cómo arreglar esto? Las cadenas dinámicas no deben crearse en (:ATTRIBUTES) durante la compilación del código como hasta ahora, sino en [:ATTRIBUTES] durante la ejecución las palabras que definen los atributos de cada ente. Además, antes de crear cada cadena dinámica debe comprobarse si ya hay había una anterior y en ese caso borrarla.

El código corregido queda así:

: :name_str  ( a -- )  \ Crea una cadena dinámica nueva para guardar el nombre del ente.
  dup ~name_str @  if  str-free  then
  str-new swap ~name_str !
  ;
: [:attributes]  ( a -- )  \ Inicia la definición de propiedades de un ente
  \ Esta palabra se ejecuta cada vez que hay que restaurar los datos del ente,
  \ y antes de la definición de atributos contenida en la palabra correspondiente al ente.
  dup to self%
  dup :name_str  \ Crear una cadena dinámica para el campo ~NAME_STR
  setup_entity
  ;
: (:attributes)  ( a xt -- )  \ Operaciones preliminares para la definición de atributos de un ente
  \ Esta palabra solo se ejecuta una vez para cada ente,
  \ al inicio de la compilación del código de la palabra
  \ que define sus atributos.
  \ a = Ente para la definición de cuyos atributos se ha creado una palabra
  \ xt = Dirección de ejecución de la palabra recién creada
  over ~init_xt !  \ Conservar la dirección de ejecución en la ficha del ente
  ['] default_description swap ~description_xt !  \ Poner la descripción predeterminada
  ;
: :attributes  ( a -- )  \ Inicia la creación de una palabra sin nombre que definirá las propiedades de un ente
  :noname (:attributes)  \ Crear la palabra y hacer las operaciones preliminares
  postpone [:attributes]  \ Compilar la palabra [:ATTRIBUTES] en la palabra creada, para que se ejecute cuando sea llamada
  ;

2011-12-05

Eliminado el prefijo «is_» de los nombres de los campos virtuales buleanos, para unificar la nomenclatura.

Completada la descripción del albergue de los refugiados, con textos variables en función de la trama. Para mayor claridad, las palabras necesarias para construir los textos de las descripciones se agrupan en una nueva sección, en lugar de mezclarse con las palabras de atributos y descripción.

Ampliada la palabra que crea las palabras de trama de los escenarios, para que (como hace la palabra análoga de descripción) inicialice el valor self% (antes llamado _%) con el escenario cuya trama se está definiendo.

Antes:

: :location_plot  ( a -- xt a ) \ Crea una palabra sin nombre que manejará la trama de un ente escenario
  :noname swap ~location_plot_xt !  \ Crear la palabra y guardar su xt en la ficha del ente
  ;
' ; alias ;location_plot immediate

Ahora:

: [:location_plot]  ( a -- )  \ Inicia la definición de trama de un ente escenario
  \ Esta palabra se ejecutará al comienzo de la palabra de trama de escenario.
  \ El identificador del ente está en la pila porque se compiló con LITERAL cuando se creó la palabra de trama.
  to self%  \ Actualizar el puntero al ente, usado para aligerar la sintaxis
  ;
: (:location_plot)  ( a xt -- ) \ Operaciones preliminares para la definición de la trama de un ente escenario
  \ Esta palabra solo se ejecuta una vez para cada ente,
  \ al inicio de la compilación del código de la palabra
  \ que define su trama.
  \ a = Ente escenario para cuya trama se ha creado una palabra
  \ xt = Dirección de ejecución de la palabra recién creada
  over ~location_plot_xt !  \ Guardar el xt de la nueva palabra en la ficha del ente
  postpone literal  \ Compilar el identificador de ente en la palabra de descripción recién creada, para que [:DESCRIPTION] lo guarde en SELF% en tiempo de ejecución
  ;
: :location_plot  ( a -- xt a ) \ Crea una palabra sin nombre que manejará la trama de un ente escenario
  :noname (:location_plot)  \ Crear la palabra y hacer las operaciones preliminares
  postpone [:location_plot]  \ Compilar la palabra [:LOCATION_PLOT] en la palabra creada, para que se ejecute cuando sea llamada
  ;
' ; alias ;location_plot immediate

Con las palabras de atributos se ha hecho lo mismo. Antes debían recibir en la pila el ente al que se referían, para guardarlo en SELF%; ahora lo compilan y recuperan como en el caso anterior.

Una nueva tanda de escenarios tienen ya escritas las descripciones de sus direcciones.

2011-12-06

Reorganizada, ampliada y comentada la sección de la interfaz de campos y seudo-campos.

Reorganizado el código de trama principal (batalla y emboscada). Iniciada la trama del seguimiento de Ambrosio.

Separadas en dos las palabras de las fases de la batalla: texto por lado e impresión por otro. Esto permite usar los mismos textos en la descripción de un nuevo ente: los soldados.

Nuevas palabras para probar las fases de la batalla y detectar posibles errores en la creación de los textos.

Creados nuevos entes para los soldados y los refugiados, con descripciones calculadas según la trama y el escenario.

Ampliado el filtro {taken} con una norma general (la palabra nueva can_be_taken?) tras la comprobación existente sobre un error específico para el ente:

: {taken}  ( a -- )  \ Provoca un error si un ente no puede ser tomado
  \ Nota: los errores apuntados por el campo ~TAKE_ERROR# no reciben parámetros salvo en WHAT
  dup what !
  ~take_error# @ throw  \ Error específico del ente
  can_be_taken? 0= nonsense_error# and throw  \ Condición general de error
  ;

Y:

: can_be_taken?  ( a -- f )  \ ¿El ente puede ser tomado?
  \ Se usa como norma general, para aquellos entes que no tienen un error indicado en el campo ~TAKE_ERROR#
  dup is_decoration?
  over is_human? or
  swap is_character? or 0=
  ;

Sin esto, era posible tomar los soldados como si de un objeto se tratara...

2011-12-07

Creadas las palabras básicas para describir los tramos de cueva (el laberinto) distinguiendo las salidas del mismo modo que en el original (principales y secundarias, aunque solo por la forma en que son citadas en los textos).

Después se empezó a escribir una capa superior para que el programa creara las descripciones de esos escenarios (y de sus salidas) de forma aleatoria, con diferentes estilos y separando cada vez de una manera las salidas en principales y secundarias. De este modo el mantenimiento de las descripciones se simplifica y el ambiente propio del laberinto se intensifica.

Nueva palabra unsort para desordenar un número de elementos de la pila. Esto permite mostrar las salidas de cada tramo de cueva cada vez en un orden diferente.

2011-12-08

Terminadas las descripciones variables de los escenarios y sus direcciones. Solo quedan algunos retoques en casos especiales.

Descripción especial para los soldados durante el saqueo.

La dirección de bajada desde la colina, que no estaba habilitada, se calcula ahora al azar cada vez que se sube, y la descripción actúa en consecuencia.

2011-12-09

Modificado el algoritmo de listado de salidas: las salidas se muestran ahora en orden aleatorio.

Arreglado problema que impedía «mirar las salidas», porque el ente correspondiente no tenía definidos los atributos.

2011-12-10

Corregido el oscuro problema de desajuste en la pila tras el CATCH de valid_parsing?: si se había producido un error lanzado por THROW, se recuperaba (como marca el estándar) el tamaño previo de la pila, y esto dejaba una cadena falsa en ella (en la posición que había ocupado la cadena con el comando a evaluar); era necesario eliminarla con una comprobación y un par de NIP.

Mejorado el sistema de preguntas y respuestas binarias («sí» y a «no»). Ahora tiene un sistema de gestión de errores y es más versátil.

2011-12-11

Implementado el sistema para poder modificar los colores del juego en el fichero de configuración.

Simplificado el formato de la notación del fichero de configuración, con «sí» y «no».

Descripción hacia arriba de la aldea sajona, y retoques en otras.

Los soldados ya no están regresando a casa en la primera localidad, a menos que hayan salido y entrado en ella. De otro modo chocaría con su descripción, que incluye el pillaje.

is_worn? estaba definida dos veces, una de ellas para obtener el valor del campo y otra para comprobar si un este lo lleva puesto el protagonista. Renombrada esta segunda palabra como is_worn_by_me?.

2011-12-13

Modificada la palabra NOTFOUND para que antes de borrar la cadena compruebe si la pila tiene dos o más elementos. Esto evita que el sistema estalle si el jugador escribiese «notfound» en el comando.

Comentado detalladamente el fichero de configuración. Añadida una configuración de color alternativa. Iniciada la implementación de un color de fondo para toda la pantalla, independiente del de cada tipo de texto.

Primeros cambios para hacer configurables los prestos también.

2011-12-14

Configuraciones de color alternativas. El color de fondo debe ser descartado porque el sistema no lo permite (usa el color predeterminado del sistema al borrar, durante la edición de comandos).

Arreglada la redundancia entre las pausas de escena y las pausas de pantalla llena, que además no se usa.

Implementado un nuevo sistema para las pausas de final de escena y las pausas narrativas, configurable en milisegundos o con pausas indefinidas hasta la pulsación de una tecla.

Implementada la indentación opcional de los prestos, también configurable.

Corregido error en la activación y desactivación de CASE-INS; ya funciona la interpretación del comando con distinción de mayúsculas y minúsculas, lo que permite distinguir las palabras de configuración.

2011-12-15

Mejorada la presentación de las preguntas de «sí» o «no», que hasta ahora no se imprimían como el resto de los textos.

2011-12-17

Arreglado el fallo que hacía que los comandos de configuración fueran reconocidos también en minúsculas.

Inicio de la implementación de las herramientas necesarias para guardar y recuperar una partida.

Homogeneización de las palabras para recuperar y modificar los campos.

2011-12-18

Primera versión de la palabra para recuperar una partida.

2011-12-22

Corregido un error: la palabra NOTFOUND debe crearse con su nombre en mayúsculas, porque es así como el sistema la buscará cuando está desactivada la opción de ignorar las mayúsculas, lo cual es necesario para distinguir en el comando del jugador las palabras del sistema de las del juego.

2011-12-22

Palabra immediate_synonyms{ para crear sinónimos inmediatos, necesarios para las operaciones de grabación y recuperación de la partida.

2011-12-23

Pruebas para hacer el código compatible con Gforth, bigFORTH y lina.

2011-12-25

Corregido un error introducido recientemente en la versión de synonyms{ para SP- Forth.

Primeras pruebas para compilar el programa con lina, tras lograr que este sistema pueda usar las librerías necesarias de Forth Foundation Library.

2011-12-26

Continuación de las pruebas para compilar el programa con lina.

2011-12-27

Más cambios en el código, con compilación condicional, para lograr compilar el programa con lina (que por cierto compila mucho más lentamente que Gforth y SP- Forth, probablemente porque está ensamblado para 386).

2011-12-28

Mejoras en las herramientas para lina que permitan compilar el programa, y más cambios en este para adaptar el código que era específico de SP- Forth.

2011-12-29

Primera compilación exitosa con lina (y nueva versión A-02), aunque la ejecución falla al leer el fichero de configuración del juego, por un problema en el orden de búsqueda de los vocabularios.

El programa puede compilarse ahora tanto con SP- Forth como con lina. Las adaptaciones necesarias para compilarlo con Gforth y bigFORTH están incompletas pero no urgen, porque estos dos sistemas no permiten crear ejecutables.

2011-12-30

Mejoras y reorganización en las extensiones creadas para lina.

Inclusión de funciones adicionales para la gestión de la consola, que faltan en el módulo str de Forth Foundation Library, por ejemplo obtener la medida de la consola. Con ello no hará falta especificar el número de líneas y columnas utilizables, pues se usarán en cada momento las que estén disponibles.

Nuevas pruebas con el color de fondo de la pantalla, para ver si hay alguna manera de cambiarlo por fin sin que lo corrompa la entrada de textos.

2011-12-31

Mejoras en la adaptación del código a lina, entre ellas una alternativa a EVALUATE para ignorar las palabras no reconocidas y así emular el funcionamiento del intérprete de SP- Forth y su uso de NOTFOUND.

La creación de ejecutables funciona perfectamente con lina, mientras que con SP- Forth casi siempre ha dado problemas, quizá porque debe compilar una cargador en C que será el que arrancará el sistema Forth. lina por el contrario es puro Forth con un núcleo en ensamblador, y puede crear una versión nueva de sí mismo más fácilmente.

Queda por probar wina, la versión de ciforth para Windows, análoga a lina y del mismo autor. No funciona con Wine, por lo que tendré que instalar un emulador de MS- DOS.

Los comandos de edición disponibles en lina (y en la palabra ACCEPT) son muy limitados: solo tiene el borrado hacia atrás. Un juego conversacional necesita todos los comandos de edición y movimiento del cursor. Hay que escribir una palabra alternativa para ello. El código equivalente de SP- Forth está en ensamblador y por tanto no sirve de modelo. El código equivalente de Gforth está en Forth, pero Gforth es un sistema muy complejo y adaptar su código a lina parece en principio demasiado costoso. Probablemente hay otras implementaciones similares en ANS Forth que podrían servir. En caso de no encontrar una solución razonable, se estimaría escribir el juego solo para Gforth, cuya única pega es que no crea ejecutables sino imágenes, y obliga a instalar el lenguaje para ejecutar el juego.

Páginas relacionadas

Asalto y castigo [en Forth]
Juego de aventuras de texto escrito en Forth con Gforth.