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.

Etiquetas:

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):

303 segundos para 4 000 elementos:
for n=1 to size
  let b=!x1#(n)
  let b=!x2#(n)
  let b=!x3#(n)
next n

162 segundos para 4 000 elementos:
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:

183 segundos para 4 000 elementos:
for n=1 to size
  let b$=!x1$(n)
  let b$=!x2$(n)
  let b$=!x3$(n)
next n

204 segundos para 4 000 elementos:
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.

Páginas relacionadas

La legionela del pisto
Juego de aventuras de texto para ZX Spectrum 128, escrito en Beta BASIC 4.0+D con formato BBim.
Apuntes sobre Beta BASIC 4.0+D
Relación de características destacadas, limitaciones, fallos y trucos de Beta BASIC 4.0+D (para ZX Spectrum 128 con interfaz +D).
BBim
Utilería para escribir programas para ZX Spectrum en BBim (formato mejorado de Beta BASIC) con el editor Vim.
Cómo no llegar a ser un escritor de aventuras conversacionales
Compendio de consejos prácticos, avalados por la experiencia, para no llegar a ser escritor de aventuras conversacionales.