Historial de desarrollo de La legionela del pisto en 2012
Descripción del contenido de la página
Historial de desarrollo en 2012 del proyecto La legionela del pisto, una aventura conversacional en Beta BASIC 4.0+D (con formato BBim) para ZX Spectrum.
2012-01-28
Formateo manual del código de los procedimientos volantes para hacerlo más legible.
Nuevos nombres para las etiquetas que aún conservaban como nombre los números de línea originales.
El código de los procedimientos volantes es movido del módulo Init a un módulo propio, Overlays, para poder renumerarlo a partir de la línea 9000. Init lo carga con MERGE
.
2012-01-29
Fallo corregido en el procedimiento volante unMerge
.
2012-03-04
Cambios en el formato de los ficheros de código fuente, para hacerlo más legible y fácil de documentar: división en varias líneas de los comandos local
y let
que contienen varias variables.
2012-03-05
El metacomando #vim
recién implementado en BBim2BB permite entre otras cosas usar nombres de variable largos que serán sustituidos en el código final. Esto hace el código mucho más legible y facilita el desarrollo. En el caso de las variables numéricas, que sí pueden llevar nombres largos en Beta BASIC, esta funcionalidad permite ahorrar memoria y ganar velocidad de ejecución.
El siguiente extracto corresponde al procedimiento parser
y muestra cómo hacer estas sustituciones de nombres de variables para un solo procedimiento.
#vim /^defproc/,/^endproc/substitute/\<action$/a$/gI
#vim /^defproc/,/^endproc/substitute/\<command$/c$/gI
#vim /^defproc/,/^endproc/substitute/\<foundWord$/w$/gI
#vim /^defproc/,/^endproc/substitute/\<knownWordEnt(/e(/gI
#vim /^defproc/,/^endproc/substitute/\<knownWord\>/w/gI
#vim /^defproc/,/^endproc/substitute/\<knownWordsFound\>/k/gI
#vim /^defproc/,/^endproc/substitute/\<recognizedSentence$/r$/gI
#vim /^defproc/,/^endproc/substitute/\<wordData$/i$/gI
#vim /^defproc/,/^endproc/substitute/\<wordNumber\>/wn/gI
// Two alias for the same local variable:
#vim /^defproc/,/^endproc/substitute/\<spacePosition\>/i/gI
#vim /^defproc/,/^endproc/substitute/\<wordIndex\>/i/gI
defproc parser command$:\
local \
action$,\
entId,\
foundWord$,\
i,\
knownWordsFound,\
recognizedSentence$,\
wordData$,\
wordNumber:\
let \
command$=SHIFT$(2,command$)+" ",\
knownWordsFound=0,\
recognizedSentence$="":\
dim foundWord$(wordLength):\
dim knownWord(maxKnown):\
dim knownWordEnt(maxKnown):\
sort !word$(1 to )(fWord to )
// Get known words
do:\
let \
spacePosition=instring(1,command$," "),\
foundWord$=command$( to spacePosition-1),\
command$=command$(spacePosition+1 to ),\
wordNumber=inarray("word$(1,fWord to )",foundWord$):\
if wordNumber then \
let \
knownWordsFound=knownWordsFound+1,\
knownWord(knownWordsFound)=wordNumber
loop until not len command$ or knownWordsFound=maxKnown
// Get the syntax elements
let \
action=0,\
object=0,\
syntaxError=0
let \
actionSyntax=0,\
objectSyntax=0 // debug!!! needed?
let wordIndex=1
do while wordIndex<=knownWordsFound
print knownWord(wordIndex);":";knownWordEnt(wordIndex) // debug!!!
let \
wordData$=!word$(knownWord(wordIndex)),\
recognizedSentence$=recognizedSentence$+wordData$(fWord to ):\
trim recognizedSentence$:\
let recognizedSentence$=recognizedSentence$+" "
tellNL "r:"+recognizedSentence$ // debug!!!
let \
knownWordEnt(wordIndex)=code wordData$(fEntId),\
syntax=code wordData$(fSyntax)
// Action?
if not and(verb,syntax) then goto @checkObject
if action and action<>knownWordEnt(wordIndex) then \
if and(syntax,noun) then \
goto @check2objects:\
else
tellError tError2Actions,recognizedSentence$:\
exit if sgn pi
let \
action=knownWordEnt(wordIndex),\
actionSyntax=syntax,\
action$=!word$(knownWord(wordIndex),fActionStart to fActionEnd)
goto @parserNext
// Object?
@checkObject
if not and(noun,syntax) then goto @parserNext
if not action then \
tellError tErrorVocative,recognizedSentence$:\
exit if 1
@check2objects
if object and object<>knownWordEnt(wordIndex) then \
tellError tError2Objects,recognizedSentence$:\
exit if 1
if and(actionSyntax,noObject) then \
tellError tError1Object,recognizedSentence$:\
exit if 1
let \
object=knownWordEnt(wordIndex),\
objectSyntax=syntax
@parserNext
let wordIndex=wordIndex+1
loop
// Result
if not action then tellError tErrorNoAction,recognizedSentence$
if \
action \
and and(actionSyntax,needsObject) \
and not object \
then tellErrortErrorNoObject,recognizedSentence$
if action and not syntaxError then \
tellNL recognizedSentence$,iInk:\
keyin "action_"+action$
endproc
El procedimiento accept
ha sido modificado para que espere la liberación de cada tecla leída. De este modo la velocidad del emulador puede aumentarse todo lo que sea necesario sin afectar a la entrada de comandos.
defproc accept ref t$:\
local \
key,\
deadKey:\
let deadKey=0:\
window iWin:\
wblank:\
print t$;:\
poke 23202+len t$,cursorAttr+flashy
do:\
pause 0:let key=code inkey$:\
do while code inkey$:loop:\
if key then \
if deadKey then \
compoundKey deadKey,key,t$:\
else directKey deadKey,key,t$
loop until key=13
print " ":\
window oWin:\
endproc
2012-03-06
Nuevo sistema más eficaz para usar los caracteres castellanos y otros caracteres no ASCII en los textos del juego. Un nuevo fichero de metacomandos globales, que debe incluirse con #include
, hace la conversión a la notación de GDU propia de BBim:
#vim %substitute/á/\A/gI
#vim %substitute/Á/\B/gI
#vim %substitute/é/\C/gI
#vim %substitute/É/\D/gI
#vim %substitute/í/\E/gI
#vim %substitute/Í/\F/gI
#vim %substitute/ó/\G/gI
#vim %substitute/Ó/\H/gI
#vim %substitute/ú/\I/gI
#vim %substitute/Ú/\J/gI
#vim %substitute/ñ/\K/gI
#vim %substitute/Ñ/\L/GI
#vim %substitute/ü/\M/GI
#vim %substitute/Ü/\N/gI
#vim %substitute/¿/\O/gI
#vim %substitute/¡/\P/gI
#vim %substitute/º/\Q/gI
Por tanto el procedimiento volante translate
(llamado por newText
para traducir los caracteres al añadir un texto a la matriz de textos en el disco RAM) ya no es necesario y puede ser eliminado de llpd_overlays.bbim:
defproc translate ref t$:\
translatePrefix "'",sgn pi,15,t$:\
translatePrefix """",12,18,t$:\
endproc
defproc translatePrefix p$,first,last, ref t$:\
local \
i,\
pos,\
o$,\
n$:\
for i=first to last:\
let \
o$=p$+k$(sgn pi,i),\
n$=k$(2,i),\
pos=sgn pi
do :\
let pos=instring(pos,t$,o$):\
exit if not pos:\
delete t$(pos to pos+sgn pi):\
copy n$ to t$(pos):\
let pos=pos+sgn pi:\
loop
next i:\
endproc
Detectado un interesante fallo que afecta a la sintaxis de INARRAY
, como en esta línea:
wordNumber=inarray("word$(1,fWord to )",foundWord$)
El to
selecciona la zona de la cadena que deberá ser examinada, pero debe ser la palabra clave TO
de Sinclair BASIC, no el texto «to». La interpretación correcta debe forzarse en la línea de comandos de Beta BASIC, escribiendo el parámetro sin la comilla de apertura y añadiéndola después de que el intérprete rechace la línea por incorrecta. De ese modo en la cadena quedará la palabra clave TO
. Pero ese método no puede simularse con BBimport.
La solución pasa primero por marcar de alguna manera la palabra, por ejemplo así:
wordNumber=inarray("word$(1,fWord{TO})",foundWord$)
Y en segundo lugar usar una sustitución para traducirla por el código que tiene TO
en Beta BASIC (el mismo que en Sinclair BASIC), CC en hexadecimal (204 en decimal):
#vim %substitute/{TO}/\="\<Char-0xCC>"/gI
Nueva versión más rápida del primer bucle de análisis en el procedimiento parser
, con menos operaciones y comprobaciones:
// Get known words
do:\
let \
spacePosition=instring(spacePosition+1,command$," "),\
foundWord$=command$( to spacePosition-1),\
wordNumber=inarray("word$(1,fWord{TO})",foundWord$):\
if wordNumber then \
let \
knownWordsFound=knownWordsFound+1,\
knownWord(knownWordsFound)=wordNumber
loop until spacePosition=commandLenght
Comienzo de la implementación de un sistema de lectura de teclado más rápido, a cambio de ocupar 1 KiB de espacio con matrices en el disco RAM.
La preparación de las matrices está como sigue:
defproc setupKeys
/*
Setup the RAM disk arrays that hold the key maps
used to filter the valid characters during the user input,
and also to manage dead keys.
This method uses a 1 KiB in the RAM disc, but it makes
the typing faster.
*/
local i
dim !key$(3,255):\ // keymap 1: normal; 2 and 3: types of dead keys.
dim !deadKey$(255) // types of dead keys
// Mark the direct keys (first key map):
let !key$(1,12)=chr$ 12 // delete
let !key$(1,code " ")=" " // space
for i=code "a" to code "z"
let !key$(1,i)=chr$ i // a-z
let !key$(1,i+32)=chr$(i+32) // A-Z
next i
// Mark the dead keys with their type:
let !deadKey$(code "'")=chr$ 2 // apostrophe
let !deadKey$(code "/")=chr$ 2 // slash
let !deadKey$(code """")=chr$ 3 // double quote
// Mark the compound keys created by the dead keys:
for i=2 to 3:\ // The dead key type is the key map used.
let \
!key$(i,code "A")="Á",\
!key$(i,code "a")="á",\
!key$(i,code "E")="É",\
!key$(i,code "e")="é",\
!key$(i,code "O")="Í",\
!key$(i,code "i")="í",\
!key$(i,code "I")="Ó",\
!key$(i,code "o")="ó":\
next i:\
let \
!key$(2,code "U")="Ú",\
!key$(2,code "u")="ú",\
!key$(3,code "U")="Ü",\
!key$(3,code "u")="ü":\
// Copy the RAM disk arrays to disk:
ramToDisk "key$":\
ramToDisk "deadKey$":\
endproc
Y el primer borrador de las rutinas modificadas de entrada:
defproc accept ref t$:\
/*
Read the user input and return it in a string.
*/
local \
l,\ // current lenght of the input
key,\
deadKey:\
let deadKey=0:\
window iWin:\
wblank:\
print t$;:\
poke 23202+len t$,cursorAttr+flashy
do:\
let l=len t$:\
pause 100:let key=code inkey$:\
do while code inkey$:loop:\
exit if key=13:\
if key then \
if deadKey then \
compoundKey:\
else directKey
loop
print " ":\
window oWin:\
endproc
defproc compoundKey: \
/*
Manage a compound key.
*/
local c$:\
let c$=!key$(deadKey,key):\
if code c$ then newChar c$
let deadKey=0:\
endproc
defproc directKey: \
/*
Manage a direct key.
*/
local d$:\
let d$ = !deadKey$(key),deadKey = code d$:\
if not deadKey then newChar !key$(1,key)
endproc
defproc deleteKey: \
/*
Remove a character from the input string.
*/
if l then \
print chr$ 12;:\
poke 23202+l,black:\
let t$=t$( to l-1):\
poke 23202+l-1,cursorAttr+flashy
endproc
defproc spaceKey: \
/*
Add a space to the input string.
*/
if l then \
if t$(l)<>" " then newchar " "
endproc
defproc newChar c$: \
/*
Add the new character to the input string.
*/
if l<maxTyped then \
print c$;:\
let t$=t$+c$:\
poke 23202+l,cursorAttr+flashy and (l<maxTyped)
endproc
2012-03-09
Con el programa importador BBimport 5 no se produce el misterioso fallo que empezó a ocurrir con BBimport 4 al importar el módulo Init. BBimport 5 parece funcionar bien, a pesar de que es solo una reescritura de la versión anterior en formato BBim.
2012-03-12
Primera versión funcional del nuevo sistema de entrada de textos. Varias matrices en el disco RAM definen los mapas de teclado y las acciones con las teclas especiales:
defproc setupKeys
/*
Setup the RAM disk arrays that hold the key maps
used to filter the valid characters during the user input,
and also to manage dead keys.
This method uses 1.25 KiB in the RAM disc, but it makes
the typing faster.
*/
local \
i,\
n$ // 255 null characters
tellNL "Mapas de teclado.",,0:\
dim !keyMap$(3,255):\ // keymap (1: normal; 2 and 3: types of dead keys).
dim !deadKey$(255):\ // types of dead keys
dim !keyProc$(255):\ // procedure that manages the key
let \
n$=string$(255,chr$ not pi),\
!keyMap$(1)=n$,\
!keyMap$(2)=n$,\
!keyMap$(3)=n$,\
!deadKey$()=n$,\
!keyProc$()=string$(255,chr$ sgn pi)
// Mark the direct keys (first key map):
let \
!keyMap$(1,12)=chr$ 12,\ // delete
!keyMap$(1,32)=chr$ 32 // space
for i=code "a" to code "z"
let !keyMap$(1,i)=chr$ i // A-Z
let !keyMap$(1,i+32)=chr$(i+32) // a-z
next i
// Mark the dead keys with their type:
let !deadKey$(code "'")=chr$ 2 // apostrophe
let !deadKey$(code "/")=chr$ 2 // slash
let !deadKey$(code """")=chr$ 3 // double quote
// Mark the chars created by the dead keys (key maps 2 and 3):
for i=2 to 3:\ // The dead key type is the key map used.
let \
!keyMap$(i,code "A")="Á",\
!keyMap$(i,code "a")="á",\
!keyMap$(i,code "E")="É",\
!keyMap$(i,code "e")="é",\
!keyMap$(i,code "O")="Í",\
!keyMap$(i,code "o")="ó",\
!keyMap$(i,code "I")="Ó",\
!keyMap$(i,code "i")="í",\
!keyMap$(i,code "N")="Ñ",\
!keyMap$(i,code "n")="ñ":\
next i:\
let \
!keyMap$(2,code "U")="Ú",\
!keyMap$(2,code "u")="ú",\
!keyMap$(3,code "U")="Ü",\
!keyMap$(3,code "u")="ü":\
// Mark the keys that need a special procedure:
let \
!keyProc$(32)=chr$(2),\ // space
!keyProc$(12)=chr$(3) // delete
# // Copy the RAM disk arrays to disk:
# ramToDisk "key$":\
# ramToDisk "deadKey$":\
endproc
El procedimiento de entrada queda así:
defproc accept ref t$:\
/*
Read the user input and return it in a string.
*/
local \
l,\ // current lenght of the input
key,\
deadKey:\
let deadKey=0:\
window iWin:\
wblank:\
print t$;:\
poke 23202+len t$,cursorAttr+flashy
do:\
let l=len t$:\
pause 100:let key=code inkey$:\
do while code inkey$:loop:\
if key then \
exit if key=13:\
if deadKey then compoundKey: else directKey
loop :\
print " ":\
window oWin:\
endproc
defproc compoundKey: \
/*
Manage a compound key.
*/
local k$:\
let k$=!keyMap$(deadKey,key):\
if code k$ then newChar k$
let deadKey=0:\
endproc
defproc directKey: \
/*
Manage a direct key.
*/
local k$,p$:\
let \
k$ = !deadKey$(key),\
deadKey = code k$:\
if not deadKey then \
let \
k$=!keyMap$(1,key),\
p$=!keyProc$(key):\
on code p$:\
newChar k$:\
spaceKey:\
deleteKey
endproc
defproc deleteKey: \
/*
Remove a character from the input string.
*/
if l then \
print chr$ 12;:\
poke 23202+l,0:\ // black
let t$=t$( to l-1):\
poke 23201+l,cursorAttr+flashy
endproc
defproc spaceKey: \
/*
Add a space to the input string.
*/
if l then \
if t$(l)<>" " then newchar " "
endproc
defproc newChar c$: \
/*
Add the new character to the input string.
*/
if l<maxTyped then \
print c$;:\
let t$=t$+c$:\
poke 23203+l,cursorAttr+flashy and (l<maxTyped)
endproc
2012-03-13
Nuevos comentarios; nuevos metacomandos para sustituir variables largas; nuevo método para crear los GDU; borrador para un formato de datos mejorado.
2012-03-14
Pruebas de velocidad de ejecución para elegir el mejor método de almacenamiento de datos en las matrices del disco RAM: una matriz para varios campos o cada campo en una matriz. A priori parece que será más rápido usar varias matrices, porque se ahorra tiempo de cálculo en los índices. Sin embargo las pruebas demuestran que esto solo es cierto con las matrices de texto.
Sorprendentemente, lleva casi el doble de tiempo acceder a un elemento de tres matrices numéricas (primer ejemplo a continuación) que a tres elementos de una dimensión de una matriz numérica (segundo ejemplo a continuación):
for n=1 to size
let b=!x1#(n)
let b=!x2#(n)
let b=!x3#(n)
next n
for n=1 to size
let b=!x#(n,1)
let b=!x#(n,2)
let b=!x#(n,3)
next n
Posiblemente la explicación tiene que ver con la manera de localizar el fichero en el disco RAM. Sin embargo no parece que suceda lo mismo con las matrices textuales:
for n=1 to size
let b$=!x1$(n)
let b$=!x2$(n)
let b$=!x3$(n)
next n
for n=1 to size
let b$=!x$(n,1)
let b$=!x$(n,2)
let b$=!x$(n,3)
next n
2012-03-15
Nuevas conexiones entre entes escenario: dentro y fuera.
En MakeData, los entes y las palabras son creados ahora aparte; antes eran creados al mismo tiempo a partir de datos combinados. Además sus datos no están en líneas DATA
que un bucle principal debe leer, sino que son parámetros del procedimiento que crea cada elemento individual. Esto hace el código más flexible y sencillo.
2012-03-16
El programa herramienta que hasta ahora creaba las matrices con los textos del juego leía los datos de líneas DATA
, con un bucle:
defproc makeTexts:\
local \
newTextId$,\
newText$
print "Textos..."
let \
textsMem=3072,\
maxTexts=160
dim !text$(textsMem):\
dim !text#(maxTexts,2)
let \
textPos=sgn pi,\
nextText=sgn pi
restore
do
read newTextId$:\
exit if not len newTextId$
read newText$:\
if newText$(1)="=" then \
keyin " let "+newTextId$+newText$:\
else newText newText$,newTextId$
loop
ramToDisk "text$":\
ramToDisk "text#"
endproc
El nuevo sistema, análogamente a los cambios en curso para la creación del resto de matrices, no usa bucle ni líneas DATA
, pues crea cada elemento con una llamada al procedimiento correspondiente, como en el siguiente ejemplo:
newText "Hay dos acciones diferentes en la orden.","tError2Actions"
newText "Hay dos objetos o complementos diferentes en la orden.","tError2Objects"
Además, la creación de textos idénticos no precisa una sintaxis especial en la notación de los identificadores en las líneas DATA
, pues basta hacer una simple asignación, como en este ejemplo:
// Shared text ids
let \
tNPasillo2EN=tNPasillo1EN,\
tNPasillo2N=tNPasillo1N,\
tNPasillo2C=tNPasillo1C,\
tNPasillo2S=tNPasillo1S,\
tNPasillo2ES=tNPasillo1ES,\
tNEscalera1S=tNEscalera1N,\
tNEscalera2S=tNEscalera2N
2012-03-26
Simplificación del formato de DATA
en el módulo Overlays.