Historial de desarrollo del proyecto Autohipnosis.
Descripción del contenido de la página
Historial de desarrollo del juego conversacional Autohipnosis, escrito en Forth con Gforth.
2011-12-13
Descripción del proyecto sobre papel.
2012-02-09
Código complementario tomado y adaptado de Asalto y castigo [en Forth]: impresión justificada de textos, manejo de la terminal, definición de vocabularios...
Primera versión del método de creación y asociación de datos:
variable #sentences \ Número de frases
defer 'sentences \ Tabla de las direcciones de las frases
: >sentence> ( u1 -- u2 )
\ Convierte el número ordinal de una frase
\ en el número de elemento correspondiente
\ en la tabla 'SENTENCES .
#sentences @ swap -
;
: 'sentence ( u -- a )
\ Convierte el número ordinal de una frase
\ en su dirección.
>sentence> cells 'sentences + @
;
: sentence ( u -- a1 u1 )
\ Devuelve una frase a partir de su número ordinal.
'sentence count
;
: .sentence ( u -- )
\ Imprime una frase.
sentence paragraph
;
: hs, ( a u -- a1 )
\ Compila una cadena en el diccionario
\ y devuelve su dirección.
here rot rot s,
;
: sentence: ( a u "name" -- a1 )
\ Compila una frase, devuelve su dirección
\ y crea una constante que devolverá su número ordinal.
hs, #sentences 1 over +! @ constant
;
0 #sentences ! \ Iniciar contador de frases
\ Crear las frases y sus identificadores,
\ definidos en un fichero independiente:
s" autohipnosis_frases.fs" included
\ En este punto, las direcciones de todas las frases
\ están en la pila y la variable SENTENCES# contiene
\ el número de frases que han sido creadas.
( a1 ... an )
: sentences, ( a1 ... an -- )
\ Compila las direcciones de las frases.
#sentences @ 0 do , loop
;
\ Tabla para las direcciones de las frases
create ('sentences) ' ('sentences) is 'sentences
sentences, \ Rellenar la tabla compilando su contenido
: associated? ( u a | a u -- ff )
\ ¿Está una palabra del juego asociada a una frase?
\ u = Identificador de la frase
\ a = Dirección de la zona de datos de la palabra
+ c@ 0<>
;
: associate ( u a -- )
\ Asocia una frase a una palabra del juego.
\ u = Identificador de la frase
\ a = Dirección de la zona de datos de la palabra
+ true swap c!
;
: execute_nt ( i*x nt -- j*x )
\ Ejecuta el comportamiento en modo de interpretación
\ de una palabra cuyo nt se proporciona.
name>int execute
;
: execute_latest ( i*x -- j*x )
\ Ejecuta el comportamiento en modo de interpretación
\ de la última palabra creada.
latest execute_nt
;
: update_word ( u nt -- )
\ Asocia una palabra del juego a una frase.
\ u = Identificador de la frase
\ nt = Identificador de nombre de la palabra
execute_nt ( u a ) associate
;
: create_word_header ( a u -- )
\ Crea la cabecera de una palabra del juego.
\ a u = Nombre de la palabra
cr ." create_word_header ... " .s noop \ depuración!!!
name-too-short? header, reveal dovar: cfa,
cr ." create_word_header ... " .s noop \ depuración!!!
;
: create_word_array ( -- )
\ Crea la matriz de datos de una palabra del juego.
\ Cada palabra del juego tiene una matriz para marcar
\ las frases con las que está asociada.
\ Para simplificar el código, se una matriz de octetos
\ en lugar de una matriz de bitios: tantos octetos
\ como frases hayan sido definidas.
here #sentences @ dup allot align erase
;
: (create_word) ( a u -- )
\ Crea una palabra asociada a una frase,
\ que funciona como una variable
\ pero que tiene una zona de datos de tantos octetos
\ como frases hayan sido definidas.
\ a u = Nombre de la palabra
cr ." (create_word) ... " .s noop \ depuración!!!
create_word_header create_word_array
cr ." (create_word) ... " .s noop \ depuración!!!
;
: init_word ( u -- )
\ Inicializa la última palabra del juego creada,
\ asociándola a una frase.
\ u = Identificador de la frase
cr ." init_word ... " .s noop \ depuración!!!
execute_latest associate
cr ." init_word ... " .s noop \ depuración!!!
;
: create_word ( u a1 u1 -- )
\ Crea e inicializa una palabra asociada a una frase.
\ u = Identificador de la frase
\ a1 u1 = Nombre de la palabra
cr ." create_word ... " .s noop \ depuración!!!
(create_word) init_word
cr ." create_word ... " .s noop \ depuración!!!
;
: another_word ( u a1 u1 -- )
\ Crea o actualiza una palabra asociada una frase.
\ u = Identificador de la frase
\ a1 u1 = Nombre de la palabra
cr ." another_word ... " .s noop \ depuración!!!
\ cr ." another_word ... " 2dup type \ depuración!!!
2dup find-name ?dup
cr ." another_word ... " .s noop \ depuración!!!
if update_word else create_word then
cr ." another_word ... " .s noop \ depuración!!!
;
: parse_word ( -- a u )
\ Devuelve la siguiente palabra asociada a una frase.
begin parse-name dup 0=
while 2drop refill 0= abort" Error en el código fuente: falta un }words"
repeat
;
: another_word? ( -- a u ff )
\ ¿Hay una nueva palabra en la lista?
\ Toma la siguiente palabra en el flujo de entrada
\ y comprueba si es el final de la lista de palabras asociadas a una frase.
\ a u = Palabra encontrada
\ ff = ¿No es el final de la lista?
parse_word
2dup cr type \ depuración!!!
2dup s" }words" compare
;
: words{ ( u "name#0" ... "name#n" "}words" -- )
\ Crea o actualiza palabras asociadas a una frase.
\ u = Identificador de frase
cr ." words{ at the start ... " .s noop \ depuración!!!
begin dup another_word? ( u a1 u1 ff )
cr ." words{ before while ... " .s noop \ depuración!!!
while another_word
cr ." words{ before repeat... " .s noop \ depuración!!!
repeat
cr ." words{ after repeat... " .s noop \ depuración!!!
2drop 2drop
;
\ Crear las palabras asociadas a las frases,
\ definidas en un fichero independiente:
s" autohipnosis_palabras.fs" included
Boceto del bucle principal:
: enough? ( -- ff )
\ ¿Es suficiente por hoy?
\ ff = true: terminar el juego; false: jugar otra partida
true \ Provisional!!!
;
: ask ( -- )
\ Pide un comando al jugador, hasta que recibe uno válido.
begin command valid? until
;
: game ( -- )
\ Bucle principal de cada partida.
#sentences @ dup 1
do i .sentence ask loop
.sentence \ Frase final
;
: main ( -- )
\ Bucle principal del juego.
init/once
begin init/game game enough? until
;
2012-02-10
Pruebas.
2012-02-11
Corregido el error que impedía la creación de los términos asociados a las frases.
Palabras para imprimir el título en ASCII Art.
Completado y depurado el sistema de evaluación de comandos. Eliminado el código de la primera versión, que ya no se usaba:
: valid++ ( u -- )
\ Incrementa la cuenta de aciertos, si procede.
\ u = Número ordinal de frase
sentence# @ = 1 and valid +!
;
: valid? ( false u ... u' u1 -- ff )
\ Comprueba si en un grupo de frases está la frase actual.
\ false = Indicador de final de lista
\ u ... u' = Números ordinales de las frases propuestas
\ u1 = Número ordinal de la frase actual
\ ff = ¿Se encontró la frase actual?
sentence# ! valid off
begin ?dup
while valid++
repeat valid @ 0<>
;
El método nuevo es muy diferente:
: valid++ ( a -- )
\ Incrementa la cuenta de aciertos, si procede.
\ a = Dirección de la zona de datos de un término asociado a una frase
sentence# @ associated? abs valid +!
;
: execute_term ( nt -- )
\ Executa un término asociado a una frase.
\ nt = Identificador de nombre del término
execute_nt ( a ) valid++
;
: (evaluate_command) ( -- )
\ Analiza la fuente actual con los vocabularios activos,
\ ejecutando las palabras reconocidas
\ como términos asociados a una frase.
begin parse-name ?dup
while find-name ?dup if execute_term then
repeat drop
;
: evaluate_command ( a u -- )
\ Analiza una cadena con el vocabulario del jugador.
only player_vocabulary
['] (evaluate_command) execute-parsing
restore_vocabularies
;
: valid? ( a u -- ff )
\ ¿Contiene una cadena algún término asociado a la frase actual?
valid off evaluate_command valid @ 0<>
;
2012-02-12
Más términos asociados a las frases.
2012-02-13
Completado el fichero de términos asociados.
2012-02-14
Corregido y completado el código para las pausas:
: time? ( d -- ff ) utime d< ;
: microseconds ( u -- )
\ Espera los microsegundos indicados, o hasta que se pulse una tecla.
s>d utime d+
begin 2dup time? key? 0= or until
begin 2dup time? key? or until
2drop
;
: miliseconds ( u -- )
\ Espera los milisegundos indicados, o hasta que se pulse una tecla.
1000 * microseconds
;
: seconds ( u -- )
\ Espera los segundos indicados, o hasta que se pulse una tecla.
1000000 * microseconds
;
Primer borrador del código para el menú.
2012-03-20
Primer prototipo funcional del sistema de menú. Pequeña simplificación de los bucles principales. Añadidas algunas palabras asociadas a las frases.
2012-03-29
Nuevo sistema de menú, con comandos. Añadidas algunas palabras asociadas a las frases.
2012-03-30
Arreglado y completado el sistema de desplazamiento de la ventana de salida.
Añadidas algunas palabras asociadas a las frases; borradas algunas que quedaban en plural.
Implementado el sistema de textos aleatorios, tomado de Asalto y castigo [en Forth], para usarlo en las instrucciones y en el presto.
2012-04-18
Corregidos errores en la construcción del texto de ayuda.
2012-04-19
Eliminado el sistema genérico de información del tamaño de pantalla, que usaba el módulo trm de Forth Foundation Library:
: at-max-xy ( -- )
\ Sitúa el cursor en los mayores valores posibles de x e y.
\ El mayor valor posible es -1 interpretado sin signo;
\ usamos -2 porque la palabra AT-XY en Gforth incrementa los valores.
-2 dup at-xy
;
: last_row ( -- u )
\ Devuelve el número de la última fila de la pantalla.
trm+save-cursor at-max-xy row trm+restore-cursor
;
: rows ( -- u )
\ Devuelve las filas de la pantalla.
last_row 1+
;
: last_col ( -- u )
\ Devuelve el número de la última columna de la pantalla.
trm+save-cursor at-max-xy col trm+restore-cursor
;
: cols ( -- u )
\ Devuelve el número de columnas de la pantalla.
last_col 1+
;
En su lugar usamos las palabras propias de Gforth:
: last_col ( -- u ) cols 1- ;
: last_row ( -- u ) rows 1- ;
Se borra el código que ya está independizado en forma de módulos reutilizables, como las herramientas para crear textos aleatorios. También se sustituye csb2 por la nueva versión mejorada creada para Gforth.
Se sigue produciendo desde hace días un extraño error esporádico al interpretar el código, en la definición de alguno de los alias. Parece ser problema interno de Gforth. Ocurre de forma aparentemente arbitraria. Por ejemplo, a veces basta cambiar una palabra en el código para que no ocurra.
2012-05-07
Sustitución del código de impresión de textos justificados heredado de versiones anteriores de Asalto y castigo [en Forth] por la última versión, basada en la nueva librería Galope.
Eliminado el codigo obsoleto de la ortodoxa primera versión del menú.
2012-05-16
Para que el completado de las palabras con el tabulador sea útil, se reproducen los últimos cambios en Asalto y castigo. Por ejemplo, se modifica la palabra de entrada de comandos para que esté disponible solo el vocabulario del jugador:
: command ( -- a u )
\ Acepta un comando del jugador.
init_command_line
only player_vocabulary
'command /command accept 'command swap
restore_vocabularies
no_window ;
Del mismo modo se hacen los restantes cambios en el vocabulario del jugador, para permitir el uso de las letras propias del castellano tanto en minúsculas como en mayúsculas.
La definición del vocabulario del jugador como una tabla se elimina:
false [if]
\ El método que sigue provoca errores de acceso a
\ memoria al crear palabras en el vocabulario. No sé por
\ qué.
: case_sensitive_vocabulary ( "name" -- )
\ Crea un vocabulario con nombre y sensible a mayúsculas.
create table ,
does> ( pfa -- )
\ Reemplaza el vocabulario superior.
\ Código tomado de Gforth (compat/vocabulary.fs).
@ >r
get-order dup 0= 50 and throw \ Error 50 («search-order underflow») si la lista está vacía
nip r> swap set-order
;
case_sensitive_vocabulary player_vocabulary \ Palabras del jugador
[else]
\ Método alternativo.
table value (player_vocabulary)
: player_vocabulary
\ Reemplaza el vocabulario superior con el del jugador.
\ Código adaptado de Gforth (compat/vocabulary.fs).
get-order dup 0= 50 and throw \ Error 50 («search-order underflow») si la lista está vacía.
nip (player_vocabulary) swap set-order
;
[then]
Y en su lugar se usa simplemente vocabulary player_vocabulary
.
2012-05-17
Examino de nuevo el código en busca del extraño error de compilación, siguiendo la pista indicada en la lista de correo de Gforth: el rastro del error indica que tabla hash del vocabulario se ha rehecho, debido posiblemente a una corrupción previa. Sospecho que tiene que ver con terms{
, aunque todo parece correcto:
: terms{ ( u "name#0" ... "name#n" "}terms" -- )
\ Crea o actualiza palabras asociadas a una frase.
\ u = Identificador de frase
\ cr ." ############################# terms{" show \ depuración!!!
only game_vocabulary also player_vocabulary definitions
assert( depth 1 = )
begin
\ cr ." terms{ after begin ... " show \ depuración!!!
dup another_term? ( u u a1 u1 f )
\ cr ." terms{ before while ... " show \ depuración!!!
assert( depth 5 = )
while another_term
assert( depth 1 = )
\ cr ." terms{ before repeat... " show \ depuración!!!
repeat
assert( depth 4 = )
\ cr ." antes de 2drop 2drop ... " show \ depuración!!!
2drop 2drop
restore_vocabularies ;
Corregido un error: al empezar el juego la pantalla no se coloreaba con el color de fondo configurado. No obstante esta funcionalidad aún está en pruebas debido a que la terminal cuando se enrolla muestra las nuevas líneas con el color de fondo configurado en el sistema.
2012-05-18
Añadidos al fichero de configuración los colores del presto de pausa de narración, que habían quedado olvidados.
Completado por fin el código para poder poner un color de fondo a toda la pantalla diferente del predeterminado en la terminal. Había que puentear el salto de línea de la rutina de impresión de párrafos, para que coloreara cada vez la nueva línea con el color de fondo. Así se evita que las líneas nuevas fruto del desplazamiento de pantalla aparezcan del color predeterminado en la terminal. Lo único que no queda por cambiar, y que en principio no es posible, es el color del cursor.
2012-06-17
Simplificada la creación de términos (que son variables con un espacio de datos ampliado para ser usado como matriz de octetos). El método usado hasta ahora estaba entresacado de algunas palabras internas de Gforth y al parecer no era completo pues el diccionario se corrompía a menudo:
: create_term_header ( a u -- )
\ Crea la cabecera de una palabra del juego.
\ a u = Nombre de la palabra
name-too-short? header, reveal dovar: cfa,
;
Para crear una variable a partir de su nombre basta completar el comando y ejecutarlo con evaluate
; es mucho más sencillo y más estándar:
\ Alias necesario porque el vocabulario 'forth' no estará
\ visible durante la creación de los términos:
' variable alias term
: create_term_header ( a u -- )
\ Crea la cabecera de una palabra del juego.
\ a u = Nombre de la palabra
s" term" 2swap s& evaluate
;
create
sería más adecuada que variable
, que reserva una innecesaria celda, pero con create
se produce el misterioso error fruto al parecer de la corrupción del diccionario. Sin embargo este error se produce también usando variable
aquí, de forma imprevisible, tras modificar el código, igual que ya ocurría. Por tanto la causa del error no es el método empleado en create_term_header
.
Por otra parte, estas palabras no cumplen bien su función por la forma en la que trabaja xy
:
2variable output-xy \ Coordenadas del cursor en la ventana de salida
: save_output_cursor
\ Guarda la posición actual del cursor en la ventana de salida.
xy output-xy 2!
;
: restore_output_cursor
\ Restaura la posición guardada del cursor en la ventana de salida.
output-xy 2@ at-xy
;
La alternativa es usar las palabras trm+save-current-state
y trm+restore-current-state
del módulo trm de Forth Foundation Library.
2012-06-18
Depuración para intentar aislar la causa del error. Reorganización de varias zonas del código. Variantes aleatorias del mensaje del menú. Mensaje de salida del programa.
2012-09-12
La palabra nextname
de Gforth permite usar una cadena como nombre de la próxima palabra que será creada por cualquier medio (:
, create
, variable
...), lo que posibilita una alternativa segura, de más alto nivel, a la parte del código donde podría estar la causa del error:
\ Alias necesario porque el vocabulario 'forth' no estará
\ visible durante la creación de los términos:
' variable alias term
\ ' create alias variablx \ xxx try
\ xxx Si se usa 'create' en lugar de 'variable', se reproduce
\ el dichoso error.
: create_term_word ( a u -- )
\ Crea una palabra para el término.
\ a u = Término
\ cr ." create_term_header ... " show \ xxx depuración
\ Sistema antiguo:
\ name-too-short? header, reveal dovar: cfa,
\ 2012-06-17 Sistema nuevo:
\ s" term" 2swap s& evaluate
\ xxx funciona:
\ Alternativa al sistema nuevo:
\ also forth s" variable" 2swap s& evaluate previous
\ xxx prueba:
\ also forth s" variablx" 2swap s& evaluate previous
\ xxx El error también se produce usando 'create' así:
\ also forth s" create" 2swap s& evaluate previous
\ cr ." salida de create_term_header ... " show \ xxx depuración
\ 2012-09-11 Otro sistema:
nextname term
;
El programa ahora funciona bien con nextname
... y extrañamente también sin ella, con el mísmo código que la última vez.
2012-09-15
Nuevos términos asociados a las frases. Nuevos comandos de inicio del programa y del juego.
Corregido un error en la construcción del texto de ayuda.
Añadido un presto para marcar la entrada del jugador.
2015-12-08
Revisión del código. Sustitución de la licencia GPL por otra más permisiva.
2015-12-10
Traducción de la mayoría de los comentariós al inglés. Actualización del formato del código y de los comentarios.
2016-01-17
Completada la traducción de los comentarios. Cambios en la interfaz de arranque.