archivo

Programación

Este artículo presenta una pequeña función muy útil para el uso con el escalado de gráficos en el BASIC del QL. Ésta pequeña función debería trabajar tanto en SuperBASIC como en SBASIC.

Recientemente estaba escribiendo un programa que necesitaba del uso de POINT, LINE y otros cuantos comandos gráficos que usan el sistema de coordenadas gráficos del BASIC. Yo necesitaba estar en condiciones de poder escalar gráficos para encajarlos en la pantalla completa, independientemente de la resolución de la pantalla.

Los sistemas QL modernos ofrecen una variedad de formas y proporciones de pantalla, por ejemplo, el QL estándar tiene una resolución de 512×256 y los sistemas Q40/Q60 1024×512. Ambos tienen una proporción de 2:1 píxeles de pantalla. Pero un sistema QXL se ejecuta en modo SVGA con resoluciones de 800 por 600 pixels, esto es una ratio de 4:3 en lugar de 2:1. Un sistema Aurora ofrece un sistema de resolución tipo EGA 650×350, también el QPC ofrece resoluciones similares, lo cual viene a ser una proporción aproximadamente de 2:1 (sólo aproximadamente).

A modo de referencia, aquí se muestran algunas de las resoluciones gráficas más comúnmente usadas de varios tipos de sistemas que yo he usado. Notarás que en esta lista no se incluyen los emuladores como QLay, QemuLator y QDOS Classic dado que yo no tengo suficiente conocimiento de esos tipos de sistemas para poderlos incluir en esta lista.

Ancho    Alto    Ratio    Tipo de sistema
-----------------------------------------
 256     256      1:1     QL en modo 8
 512     256      2:1     QL en modo 4
 512     384      4:3     QPC, Aurora
 640     350  aprox. 2:1  modo EGA en QXL, Aurora, QPC
 640     480      4:3     modo VGA en QXL, Aurora, QPC
 768     280      2.74:1  Extendido 4 en ST-QL
 800     600      4:3     SVGA sobre QXL, QPC etc
1024     512      2:1     Q40/Q60, QPC
1024     768      4:3     XGA sobre QPC

Figura 1.

Cuando se define una ventana, el sistema le asigna un SCALE con valor 100 en la dirección del eje vertical, estableciendo el punto de origen en la coordenada 0,0 situado en la esquina inferior izquierda. Es menos fácil de predecir el número de puntos en el sistema de coordenadas que existirán en la dirección horizontal – una ventana que es cuadrada es en términos de número de píxeles no es cuadrada en términos de de coordenadas gráficas.

Esto nos provoca un problema, si utilizamos LINE, CIRCLE, ELLIPSE, etc. para dibujar formas o figuras y dichas formas no caben en la ventana en cuestión, parte de la figura se dibuja fuera de la ventana, aunque no se producen errores gracias a la forma en que los comandos gráficos del BASIC trabajan.

De hecho, existe una relación que no está muy bien documentada que, si se conoce la altura y anchura de la ventana y la escala vertical, nos permite predecir cuantos puntos en el sistema de coordenadas horizontal serán visibles dentro de la ventana en cuestión.

1000 DEFine FuNction X_Scale(y_scale,wide,high)
1010   RETurn 0.75 * y_scale * wide / high
1020 END DEFine X_Scale

Figura 2: Listado de la función.

La función toma tres parámetros. El primero (y_scale) es el número de puntos del sistema de coordenadas deseado en el plano vertical. A menos que lo cambies con el comando SCALE, tendrá normalmente el valor de 100. Si usas por ejemplo SCALE #1, 150,0,0 será de 150. El segundo parámetro es el ancho de la ventana en pixels (si estás usando la resolución del QL en modo TV la ventana #1 o #2 tendrán un ancho de 448, o 252 si usas el modo monitor del QL). El tercer parámetro es el alto de la ventana, normalmente 200 para ambas ventanas en ambos modos (TV o Monitor).

La linea 1010 usa esta información para calcular el número de puntos que le corresponden a eje horizontal. Ten en cuenta que hay una relación de 0,75 o 3:4 entre el eje horizontal y vertical. En otras palabras, sea cual sea la proporción de anchura de la ventana con respecto a la altura, el número de puntos visibles en el sistema de coordenadas horizonal es de 0,75 veces la parte visible del sistema de coordenadas verticales en esta proporción de píxeles.

Básicamente, habida cuenta de la forma de la pantalla QL, la introducción de un escalado como éste es necesaria a fin de que los círculos aparezcan realmente de forma circular dado que si dibujamos “círculos” con una proporción de 1:1 píxeles, éstos no aparecen como circulos en la pantalla del QL ya que los píxeles no son exactamente cuadrados.

Aquí hay un ejemplo simple del uso de esta fórmula. ¿Cuál sería el ancho máximo de una línea que cubra toda la pantalla? Estamos trabajando en una resolución de pantalla del QL de 512×256 y necesitamos dibujar un círculo en el centro de la pantalla. En el listado de la Figura 3 se muestra cómo podemos conseguirlo:

100 WINDOW 512,256,0,0 : CLS
110 x = X_Scale(100,512,256)
120 CIRCLE x/2,50,25
130 :
1000 DEFine FuNction X_Scale (y_scale,wide,high)
1010   RETurn .75 * y_scale*wide/high
1020 END DEFine X_Scale

Figura 3: Uso de la función para centrar un círculo en la pantalla

Bueno, esto podría parecer un gran alboroto para nada. En la realidad, llegar a esta fórmula, ha hecho más fácil para mí escribir programas con gráficos a escala que se ajusten al tamaño y forma de la ventana en cuestión. ¡Oh!, ¿qué programa era ese? Pues eran algunos de los módulos del protector de pantalla de mi programa LPsaver.

Esta rutina es actualmente una aproximación. Podrías encontrar errores, en parte debido a la simplificación de los cálculos realizados, al redondeo en las operaciones de punto flotante, etc, la escala real puede variar de con respecto a lo que indican la rutina, pero los resultados son lo suficientemente aproximados para la mayoría de los propósitos.

———-

Artículo original:  http://www.dilwyn.me.uk/docs/articles/scales.zip 

Dilwyn Jones
Tal-y-bont, Gales, Reino Unido.

Traducción: Afx
febrero de 2009

 

graphiql_demo

Pantalla demo creada con GraphiQL

Siguiendo con la fórmula de la semana pasada de recuperar artículos de QForum, hoy hablaremos de mostrar imágenes en la pantalla del QL. No entraré a explicar cómo es esta pantalla, pues existe documentación específica para ello en este mismo blog, pero sí indicaré cómo hacer para, por ejemplo, poner una pantalla de inicio a un juego o mostrar una imagen de un tamaño determinado en píxeles en una posición determinada de la pantalla.

Read More

Aunque hace mucho tiempo que no le dedico un rato al QL, sigo con interés las noticias del mundo retro y este blog, QBlog, que tan bien cuida Afx. Me gustan los artículos que leo aquí porque consiguen despertar mi curiosidad, y son, generalmente, eminentemente prácticos.

En una de estas noches en las que me atacó el insomnio, estuve revisando QForum, el foro de QL en castellano, y me encontré con algunos posts antiguos muy interesantes, que creo que merece la pena traer aquí para que lleguen a más gente por este medio. Este juego que explico a continuación es un ejemplo de ello.

crash

Lo que se expone en este artículo es aprovechable también para programar juegos en el BASIC de otros sistemas, así que espero que este contenido sirva más allá del QL.

El juego propuesto se llama CRASH, y nació como ejemplo para mostrar cómo detectar colisiones básicas en SuperBASIC, el BASIC del QL, a raíz de un comentario de Radastán en QBlog. Puedes ir al hilo original haciendo clic aquí.

En el juego se exponen nociones de uso de matrices, de creación de ventanas, scroll y uso de canales.

El artículo es un poco largo, así que respirad hondo. Allá vamos.

Read More

La recursividad es uno de esos temas que la mayoría de la gente con poca experiencia en programación seguramente ha oído hablar, pero pocos lo comprenden verdaderamente. Si pregunta a esa gente que ha oído hablar de la recursividad probablemente le conteste que la recursividad es algo así como “una rutina que se llama a si misma”.

Esa es una descripción razonable, aunque es algo más que eso. Si una rutina se llamara a si misma continuamente (como en el siguiente ejemplo), ésta se estaría ejecutando de forma indefinida, o al menos mientras el ordenador no agote su memoria.


1000 DEFine PROCedure MENU
1010 MENU
1020 END DEFine MENU

Además de ser una rutina, que se llama a sí misma (o se describe en el términos de sí misma), una rutina recursiva debe tener un mecanismo para garantizar las circunstancias en las se debe llamar a sí misma y cuando se debe proceder a una salida de dicha rutina o un retorno del mencionado procedimiento (o función). Como la recursividad es un tema difícil de explicar clara y brevemente en palabras sencillas, voy a presentar unas pocas rutinas de ejemplo para demostrar los usos de la recursividad en la programación en SuperBASIC.

La mayoría de los programadores que comprenden y utilizan la recursividad son capaces de detectar las tareas que requieren el uso de la recursividad, casi por instinto, pero pocos son capaces de explicar exactamente cómo llegaron a esa decisión. Sin embargo, una vez dominada la recursividad, ésta puede ser una poderosa y eficiente herramienta de programación.

La recursividad puede ser pensada como un proceso en el que una tarea puede ser desglosada en partes más manejables y pequeñas de una forma ordenada. Por lo tanto, la solución a esa tarea o problema se describe en términos de soluciones a problemas similares más pequeños. En este punto, los cerebros de muchas personas tienden a fundirse tratando de seguir las explicaciones, por lo que vamos a considerar un ejemplo muy simple para tratar de ilustrar lo que esto significa.

Supongamos que usted tiene un ejército. Su oponente tiene también un ejército con el mismo número de soldados que usted, por lo que seguramente no le guste la idea de entrar en una batalla donde existe un alto riesgo de que muchos de sus mejores soldados puedan ser asesinados o heridos. Así que decide optar por la estrategia de dividir a su enemigo en grupos más pequeños para simplificar el ataque. Esta estrategia podría ser denominada “divide y vencerás” – la reducción de un problema por medio de la división en varias secciones más pequeñas hasta que se haya terminado.

define procedure Conquista (grupos)
Si todos divididos entonces retornar
Dividir en otra sección (grupos - 1)
Conquistar ese grupo
end define

Tomemos ahora un ejemplo simple de la impresión de una serie de números del 1 al valor ‘n’ (cualquier número entero). Normalmente, enfocaremos el problema simplemente llamado a un procedimiento o instrucción de IMPRIMIR dentro de un bucle FOR. Sin embargo, podríamos escribir lo mismo mediante una rutina recursiva. Lo que estamos tratando de hacer es muy simple para intentar concentrarnos en la teoría y no en el problema en sí mismo. Empezaremos por desglosar el proceso en componentes más simples. Tenemos que imprimir los números, cada uno de los cuales es más grande que el tiene ante sí.

define procedure imprimir_numero (valor)
  Si hemos alcanzado el límite, entonces salir 
       de este prodecimiento
  Imprimir el siguiente número con imprimir_numero
       (valor - 1)
  Imprimir el número en la pantalla
end define

Esto es un pseudocódigo. Ahora vamos a escribir el código en un correcto SuperBASIC.

10 CLS : Print_Number_Up_To 10
20 :
100 DEFine PROCedure Print_Number_Up_To (value)
110   IF value = 0 THEN RETurn
120   Print_Number_Up_To value-1
130   PRINT value
140 END DEFine Print_Number_Up_To

La línea 110 comprueba si se debe ejecutar una vez más el bucle o si se deja de ejecutar la llamada. La línea 120 llama al mismo procedimiento 10 veces, y en cada llamada se decrece el valor pasado al procedimiento en 1 (al igual que un bucle al contar). Esto se va ejecutando hasta que el valor sea igual a 0 (10 veces). En definitiva se está llamado al mismo procedimiento para imprimir pero con 10 parámetros diferentes, cada llamada tiene un parámetro que es diferente a la llamada anterior, en este caso es el valor de la llamada anterior menos 1. Una vez que los 10 valores se calculan, hay 10 procedimientos, cada uno listo para imprimir su propio valor. Después de la décima definición, cuando el valor del parámetro “valor” se ha convertido en 0, los retornos comienzan a entrar en vigor de tal forma que hay 10 vueltas atrás. Recuerde que cada retorno vuelve a la llamada más reciente al procedimiento definido, de modo que la impresión se sucede en el orden inverso a aquel en el que los números se han definido.

La línea 10 hace una primera llamada al procedimiento con el valor más alto (en este caso 10). En esta primera llamada, el valor es 10, por lo que el retorno no es ejecutado. El programa a continuación pasa a la línea que contiene la llamada a imprimir un número pero especificando un nuevo valor. El próximo número en dicha llamada es el 9, con lo que el procedimiento se llama de nuevo con “valor-1”, o 10-1 en este caso. Esta llamada aún no retorna ya que el valor que se le pasa a la rutina sigue sin ser 0, con lo que se invocará de nuevo a si mima con el valor 9-1 u 8. Este se ejecuta una y otra vez hasta que se alcanza el valor 0, entonces los beneficios de la rutina recursiva empieza a entrar en acción. Cuando se alcanza el 0, la llamada que retoma su ejecución era cuando el parámetro pasado tenía el valor 1, por lo que éste se imprime en primer lugar. Luego retornará cuando el valor contenía 2, y así sucesivamente hasta retornar a la primera llamada donde el valor tenía 10. Espero que todo esto tenga sentido para usted. En caso de que no, aquí va una traza de la ejecución del programa.

10 llamada a Print_Number_Up_To con “value” = 10
100 definición del procedimiento, se le da el valor 10
     a la variable llamada "value"
110 se evalúa la expresión si "value" ha alcanzado
    el valor de 0
120 el valor 0 no se ha alcanzado, entonces llamamos al
    procedimiento otra vez con el valor de 10-1, (o 9)
100 regresa a la definición del procedimiento. 
    Esta vez, value=9
110 comprueba si se ha alcanzado 0, en este caso aun no.
120 se invoca de nuevo al procedimiento con 
    value-1 (9-1, u 8)
100 ... y así sucesivamente hasta que el value=0
110 se llega en este momento a value=0, así que se retorna 
    desde este procedimiento a la llamada anterior donde
    el valor previo de value era 1 (recuerde que nosotros 
    llamamos la última vez en la línea 120 convalue=1)
130 ahora se imprime el valor (1)
140 end define - fin de llamada al procedimiento, así 
    que se retorna a la llamada anterior, esta llamada fue
    ejecutada cuando value contenía el valor de 2. 
    Retornando a la siguiente sentencia después de 
    la línea 120 que contenía la llamada.
130 se imprime el valor actual (2) y así sucesivamente
    hasta que se imprimen los 10 valores.

Por supuesto, enfocar este problema de imprimir números de esta manera es como utilizar un martillo para cascar una nuez. Pero me parece que ayudan a explicar la técnica porque se trata de un ejemplo corto y bastante fácil de seguir paso a paso. Escribir cada paso sobre el papel le ayudará a entenderlo.

La recursividad a menudo se utiliza cuando se necesita una rutina similar para una serie de operaciones o donde los cálculos que se exijan sólo contienen una ligera diferencia en cada llamada a la rutina, pero donde la rutina sea suficientemente capaz de hacer frente a estas variaciones y las condiciones finales son suficientemente bien conocidas por ser claramente definibles. Esto es muy importante en la recursividad, la prueba a realizar cuando la rutina tiene que dejar de llamarse a si misma es vital para garantizar el correcto funcionamiento y el éxito en la finalización correcta. El siguiente ejemplo no es un programa real (¡no intente ejecutarlo como un programa BASIC!), es un “recorte” de código que le ayudará a mostrar el algoritmo de una forma simplificada de cómo funciona de forma general una rutina recursiva.

DEFine PROCedure Rutina_Recursiva (parámetro)
  LOCal lista de valores locales
  ... aquí se coloca el código que se precise
  IF fin condición THEN RETurn
  Rutina_Recursiva con parámetros variable
  Acciones que se ejecutan mientras
     se desenreda la llamada recursiva
END DEFine

La rutina establece el valor de sus propias variables cada vez que se ejecuta la llamada, además comprueba si el final de condición se cumple o no, entonces se llama a sí misma de nuevo con datos ligeramente diferentes, según sea necesario. Una cosa que podría ser no obvio es la necesidad de variables locales en el procedimiento. Si necesita crear variables temporales para calcular o apoyar a los valores que difieren en cada llamada al procedimiento, es necesario que las diferentes variables en cada llamada no destruyan los valores con que hemos trabajado, esto en muchas ocasiones es difícil de establecer.

En esta etapa, puede que no sea obvio el por qué alguien necesite o quiera usar la recursividad. La primera vez que me enfrenté a la recursividad hace algunos años, me pareció una especie de técnica repetitiva inventada por alguien muy inteligente que quería impresionar a los demás con una especie de código mágico que nadie pudiera entender. Siempre parece como si hubiera una forma más simple de hacer las cosas sin un código tan enrevesado. De alguna manera esto es cierto, por lo general hay una forma no recursiva de resolver un problema implementado como una llamada recursiva, pero por lo general suele ser más largo, menos elegante y menos eficiente (por no hablar de más lento). El arte reside en ser capaz de detectar los casos en que la recursividad es un beneficio para el programador. En cierta medida, este viene con experiencia y con conocimiento de un “estándar” en aplicaciones y rutinas donde se utiliza la recursividad. En términos de los procesos del pensamiento, usted tiene que meditar repetidamente patrones en los que se debería aplicar, o cuestionarse si una tarea se puede dividir y hacer más eficiente en secciones (de tipo “divide y vencerás”). Recuerdo que en una lectura de un libro de texto, hace algún tiempo, se mencionaba que la recursividad es un acto de fe, ya que es muy difícil seguir exactamente lo que uno hace en una rutina recursiva, es difícil estar seguro de que la rutina se llevará a cabo exactamente como usted espera o como piensa que se llevará a cabo. Seguramente cuando usted escriba la rutina al final se sorprenderá a si mismo de lo que la rutina está haciendo realmente.

A continuación le presentaré algunos ejemplos reales de rutinas recursivas, algunas usarán funciones y otros procedimientos. Espero que al menos algunos de estos ejemplos le ayude a ver cómo funciona la recursividad, y el motivo por el cual la recursividad puede ser una técnica de programación muy útil.

– Factoriales. Esta rutina matemática es uno de los ejemplos clásicos de la recursividad como técnica de programación. Se trata de calcular el total de una serie de productos de números, desde 1 hasta n. n! que es pronunciado como “n factorial”, consiste en el producto de todos los números enteros desde 1 hasta n.

n! = n * (n-1) * (n-2) … * 2 * 1

– Exploración de un sub-directorio. La rutina permite explorar todos lo ficheros que cuelgan de todos los sub-directorios de un disco duro (o disquete de alta capacidad). Sólo funciona en sistemas con «nivel 2» de directorios (creados con el comando MAKE_DIR básico, por ejemplo).

– Una rutina de ordenación de cadenas muy rápida, llamada “Quicksort”, donde los datos se van dividiendo en grupos más pequeños, cada uno de los cuales se van ordenando y dividiendo hasta quedar la lista totalmente ordenada. El algoritmo consiste en una rutina que se llama a si misma para ir ordenando los pequeños grupos que se van creando.

– Una rutina de relleno, donde polígonos irregulares se pueden rellenar con un determinado color. Esta rutina no es tan rápida como otras que se puedan encontrar en programas comerciales, pero funcionan correctamente. Desafortunadamente se requiere un toolkit para el uso de una instrucción que nos da el color de un pixel de la pantalla. Yo he usado mi propia extensión, PIXEL% x,y (donde x e y son las coordenadas de un punto en una pantalla estándar del QL, 512×256) pero se puede utilizar rutinas equivalentes como las de Simon N. Goodwin, las cuales se pueden llamar cambiando la instrucción y empleando los parámetros adecuados. De hecho, hay dos rutinas, una muy simple, que ilustra el principio básico (que funciona, pero no es muy eficiente), y la segunda, que incluye un elemento de buffering (o apilado) para reducir el coste general y llamar a la rutina de trabajo más rápidamente que el primer ejemplo.

– El rompecabezas llamado Torres de Hanoi. Consiste en tres varillas verticales y un número indeterminado de discos que determinarán la complejidad de la solución. No hay dos discos iguales, están colocados de mayor a menor en la primera varilla ascendentemente, y no se puede colocar ningún disco mayor sobre uno menor a él en ningún momento. El juego consiste en pasar todos los discos a la tercera varilla colocados de mayor a menor ascendentemente. Las reglas son: 1) Sólo se puede mover un disco cada vez, 2) un disco de mayor tamaño no puede descansar sobre uno más pequeño que él mismo y 3) sólo puedes desplazar el disco que se encuentre arriba en cada varilla. No es un problema muy complejo de resolver, pero muestra cómo utilizar las técnicas de recursividad para resolver el rompecabezas de una manera elegante.

1. Factoriales

Esta rutina nos permite calcular el producto de una serie de números hasta llegar a un valor dado. El término “n!” (pronunciado n factorial) se utiliza para representan este producto.

n! = n * (n-1) * (n-2) … hasta … * 2 * 1

Así por ejemplo 5! es 5*4*3*2*1, que resulta 120. Esto se puede codificar con un bucle FOR de la siguiente manera:

INPUT 'Factorial de > ';n
LET factorial = 1
FOR number = n TO 1 STEP -1
  LET factorial = factorial*number
END FOR number

Esto es perfectamente válido, pero vamos a hacer lo mismo empleando una función recursiva. El cálculo se puede realizar siguiendo las mismas etapas tal cual está escrito en la fórmula anterior, la multiplicación de un número por un número menos de la secuencia hasta llagar a 1. Por lo tanto, la secuencia incluye (i) el cálculo del número próximo, (ii) la actualización del valor total multiplicando el total por el próximo valor hasta que lleguemos hasta el último valor que es 1.

100 INPUT 'Factorial de > ';n : PRINT Factorial(n)
110 DEFine FuNction Factorial (num)
120   IF num = 1 THEN RETurn 1
130   RETurn num * Factorial (num - 1)
140 END DEFine

Esta rutina en realidad es algo más extensa que la primera, aunque se debe en parte a la definición de la función. Esta función devuelve el primer número multiplicado con la llamada a si misma con el siguiente valor descendente como parámetro. Cuando se llega a 1 tenemos simplemente que devolver ese valor 1 (línea 120) en lugar del cálculo de la multiplicación con el siguiente número de la serie (línea 130).

2. Lectura de subdirectorios

Esta rutina sólo tiene sentido para aquellos que tengan instalados en sus QL las facilidades de manejo de subdirectorio. Esto incluye la Gold Card, Super Gold Card, QXL, los emuladores de Atari ST-QL, también son validos aquellos sistemas con Trump Cards o SuperQBoards con una actualización nivel-2 de la ROM (actualización de Jurgen Falkenburg o Jochen Merz).

Al hacer un DIR de un disquete o disco duro que contiene directorios se obtienen los archivos del directorio actual y los nombres de los posibles subdirectorios, los cuales se muestran precedidos del símbolo ‘->’. Para obtener una lista de los archivos de esos subdirectorios se tiene que hacer otro DIR de ellos. Si esa lista contiene a su vez otros subdirectorios se deben ejecutar de forma sucesiva los comandos DIR sobre ellos para obtener la lista completa de todos los ficheros y subdirectorios del disco. Nos será útil plantear, para evitar esto, un procedimiento o rutina para explorar todos los archivos de todos los directorios a partir de un directorio concreto especificado. Así cuando empezamos a rastrear siguiendo adelante debemos explorar todos los subdirectorios, pero una vez agotado un camino debemos iniciar otros caminos si los hubiera hasta completar toda la exploración. De esta forma lograríamos enumerar todos los archivos y subdirectorios.

Aquí hay un resumen de cómo vamos a construir la rutina:

procedure extender_dir (unidad,directorio)
  Variables locales
  Abrir directorio
  Si fallo, retornar
  Leer todo los nombres en directorio
    ¿se ha terminado? si es así, cerrar directorio y salir del bucle
    obtener seiguiente fichero
    ¿es un nombre de directorio?
      si, hacer extender_dir en ese direcotorio llamada recusriva 
      no, imprimir ahora el fichero
    fin si
  final del bucle de lectura de nombres
fin de procedure

y así es como he decidido escribir la rutina en SuperBASIC:


100 REMark extended dir of all sub-directories
110 Extended_DIR 'win1_',''
120 :
130 DEFine PROCedure Extended_DIR (drive$,directory$)
140   LOCal loop,ch,d$,fp,n$
150   ch = FOP_DIR (drive$&directory$) : REMark open channel to directory
160   IF ch  0 THEN 
220       REMark a directory length of 0 may be a deleted file
230       BGET #ch: REMark file type byte
240       IF CODE(INKEY$(#ch)) = 255 THEN 
250         REMark this name is a subdirectory, so we need to DIR this
260         REMark if you want directory names printed, add this
270         REMark PRINT d$;' ->'
280         Extended_DIR drive$,d$
290       ELSE 
300         PRINT d$
310       END IF 
320     END IF 
330     fp = fp + 64
340   END REPeat loop
350 END DEFine Extended_DIR

Tenga en cuenta que la rutina, tal como está, no imprime los nombres de directorios sino sólo los nombres de archivo. Para imprimir los nombres de directorios, al igual que un comando DIR, debe eliminar la declaración REMark de la línea 270. Recuerde que la llamada a la rutina debe hacerse con dos parámetros, el primer parámetro con la unidad y el segundo con el nombre del directorio. Éstos se usan por separado dentro del cuerpo de la rutina.

3. Rutina de ordenación Quicksort

La rutina “quicksort” es una de las rutinas de ordenación más rápidas que pueden ser programadas en SuperBASIC. He encontrado dos variantes. La primera variante es más fácil de usar, pero no es tan rápida como la segunda variante, que tiene ciertos requisitos especiales que la hacen un poco menos conveniente en cuanto a su uso.

Quicksort hace su trabajo barajando punteros a las secciones de datos que van a ser ordenados y ordenando esas en secciones más pequeñas. Esto reduce el número total de pasadas a través de los datos que ralentiza otras rutinas de ordenación tales como el método “Burbuja” (Bubble Sort). Ya hemos utilizado la frase “divide y vencerás” para describir cómo trabajan algunas rutinas recursivas, y esto es precisamente lo que hace esta rutina de ordenación.

Esta rutina no es un ejemplo de rutina recursiva muy fácil de seguir – si usted desea seguir paso a paso el funcionamiento del programa, trate de asignar un pequeño conjunto de datos que se vayan a ordenar y trate de hacer un seguimiento, en cada parte del programa, de cómo se van clasificando dichos datos hasta conseguir el objetivo de ordenar la lista completa.

Este procedimiento en SuperBASIC está diseñado para ordenar cadenas; para ordenar arrays de números simplemente cambie las cadenas en la rutina (el nombre termina con $) por variables numéricas (enteros, finalizando con %, o variables de coma flotante). El segundo parámetro es el índice inferior del array que va a ser ordenado, generalmente 0, y el tercer parámetro es el índice más alto de dicho array (NO el total de número de items) generalmente el número total de elementos-1, siempre y cuando el primer subíndice sea 0.

100 Q_Sort array_name$,0,entries%-1
110 :
120 DEFine PROCedure Q_Sort (array$,bottom,top)
130   LOCal sort_loop,low,high,ptr
140   low  = bottom
150   high = top
160   ptr  = bottom
170   REPeat sort_loop
180     IF low >= high THEN EXIT sort_loop
190     IF array$(low) > array$(high) THEN
200       REMark need to swap these strings
210       temp$        = array$(low)
220       array$(low)  = array$(high)
230       array$(high) = temp$
240       REMark how do we shuffle pointers to sections
250       IF ptr = low THEN
260         low = low + 1
270         ptr = high
280       ELSE
290         high = high - 1
300         ptr  = low
310       END IF
320     ELSE
330       IF ptr = low THEN high = high - 1 : ELSE low = low + 1
340     END IF
350   END REPeat sort_loop
360   IF ABS(top - bottom) THEN RETurn : REMark can't sort 1 item!
370   Q_Sort array$,bottom,ptr - 1
380   Q_Sort array$,ptr + 1,top
390 END DEFine Q_Sort

4. Relleno de gráficos

Una rutina de relleno de gráficos consiste en llenar un área de la pantalla (regular o irregular) con un determinado color. Sea cual sea el color de relleno, la rutina debe asegurarse de no pintar zonas fuera del contorno delimitado por el área (polígono regular o irregular). La rutina de relleno comienza a partir de un punto situado dentro del área que se quiere rellenar, a partir de ahí, la rutina va poco a poco rellenando del color elegido el área demarcada. Imagine el boceto de una casa con ventanas, puertas y el techo ya diseñado. A partir de algún punto en la pared, se cubre toda la superficie de pared de toda la casa de un determinado color pasando alrededor de las ventas y puertas sin salirse fuera.

El principio básico es que, cualquiera que sea la línea a partir de la cual vamos a empezar el llenado, buscaremos a la izquierda para ver si podemos llenar, luego buscaremos la derecha para comprobar lo mismo, a continuación miramos encima y a continuación debajo para ver si podemos avanzar en esas direcciones. Si podemos hacerlo llenaremos con nuestro color elegido y a continuación seguiremos haciendo lo mismo hasta que no podamos ir más lejos. Debemos volver sobre nuestros pasos al punto que partimos en caso de que no podamos avanzar por una determinada dirección. Con un polígono irregular, lo que suele ocurrir es que se llena en la medida en que se pueda avanzar y, a continuación, empezar desde el punto de comienzo para encontrar otra dirección a partir de la cual se pueda continuar con el trabajo, y así sucesivamente. A este tipo de relleno a menudo se denomina por inundación (flood) porque emula a cómo el agua va inundando un lugar, llenando poco a poco bit a bit.

Esta rutina difiere con respecto al comando FILL del QL en el sentido que puede hacer frente a las llamadas formas re-entrante (formas irregulares que se pueden doblar de nuevo en si mismas). El comando FILL del QL funciona de una manera diferente a este tipo de relleno, y eso significa que (por lo general) es más rápido que la rutina de llenado por inundación, pero la rutina por inundación puede hacer frente a una variedad más amplia de formas y polígonos que se vaya a rellenar, por lo tanto, es capaz de llenar formas irregulares bastante complejas, mientras que para hacer lo mismo es posible que necesite más de un comando FILL del QL para rellenar una figura compleja correctamente. La otra gran diferencia es que la rutina por inundación trabaja correctamente sólo después de que el perímetro haya sido dibujado completamente, mientras que el comando FILL almacena los puntos y líneas a rellenar como un esquema del perímetro a dibujar, normalmente rellenando la figura cuando el perímetro esté cerrado. De cara al usuario hay poca diferencia, el resultado final es normalmente similar, pero para el programador hay una gran diferencia en cuanto a la codificación de las dos rutinas.

Para hacer que la rutina por inundación funcione, nosotros necesitamos una rutina que examine el color de un determinado pixel en la pantalla. Muchos toolkits de extensión al BASIC suministran utilidades de este tipo (por ejemplo la función PIXEL del DIY Toolkit). En esta rutina yo he usado una extensión llamada PIXEL% que requiere los siguientes parámetros (adapte esta rutina a las extensiones que usted usa). Desafortunadamente, el SuperBASIC estándar del QL carece de una función de este tipo.

x = coordenada x a través de la pantalla
y = coordenada y hacia abajo en la pantalla

Ambos, ‘x’ e ‘y’ asumen que estamos en una ventana que cubre el total de la pantalla, por ejemplo 0,0 es la esquina superior izquierda de la pantalla.

El esquema de la rutina no es demasiado difícil de comprender, pero la forma final de las rutinas es horrible para tratar de entenderlas, especialmente en lo más avanzado de las dos rutinas, que incluye un sistema de buffering para reducir el número masivo de llamadas recursivas realizada por el sistema, asi como una ayuda para acelerar la segunda rutina en comparación con la primera.

procedimiento relleno_desde punto comienzo x,y 
  si punto no rellenable entonces retornar
  rellenar a la izquierda de este punto 
  rellenar a la derecha de este punto 
  desde izquierda a derecha
    relleno_desde punto encima esta linea 
    relleno_desde punto debajo esta linea 
  fin escaneo izquierda a derecha 
fin definición procedimiento

Esto puede ser escrito de la siguiente forma, usando la mencionada extensión PIXEL%(x,y). Las primeras 3 líneas dibujan una caja dentro de otra con una brecha entre ellas, que va a ser rellenada por la rutina. La segunda línea en el procedimiento Fill_Outline (IF y255) protege el programa cuando el color de relleno intenta ir fuera de la parte superior o inferior de la pantalla. La rutina es llamada pasándole 4 parámetros al procedimiento Fill_Outline:

x = coordenada x a partir de la que se comienza el relleno (0 a 511)
y = coordenada y a partir de la que se comienza el relleno (0 a 255)
direction = dirección en que la rutina rellena, -1 = arriba, +1 = abajo
colour = número del color de la tinta usado en el relleno
100 WINDOW 512,256,0,0 : CLS : INK 7
110 BLOCK 100,100,50,50,7 : BLOCK 96,98,52,51,0
120 BLOCK 60,80,70,60,7   : BLOCK 56,78,72,61,0
130 Fill_Outline 100,55,-1,7 : REMark -1 = upward, +1 = downward
140 STOP
150 :
160 DEFine PROCedure Fill_Outline (x,y,direction,colour)
170   LOCal loop,a,lp%,rp%
180   IF y  255 THEN RETurn
190   IF PIXEL%(x,y) = colour THEN RETurn
200   lp% = x
210   REPeat loop
220     IF lp% = 0 THEN EXIT loop
230     IF PIXEL%(lp%-1,y) = colour : EXIT loop
240     lp% = lp% - 1
250   END REPeat loop
260   rp% = x
270   REPeat loop
280     IF rp% = 511 THEN EXIT loop
290     IF PIXEL%(rp%+1,y) = colour : EXIT loop
300     rp% = rp% + 1
310   END REPeat loop
320   BLOCK rp%-lp%+1,1,lp%,y,colour
330   FOR a = lp% TO rp%
340     Fill_Outline a,y+direction,direction,colour
350     Fill_Outline a,y-direction,-direction,colour
360   END FOR a
370 END DEFine Fill_Outline

El problema con esta rutina es que se llega a un callejón sin salida y, a continuación, ha de volver sus pasos hacia atrás para llenar en una dirección opuesta, esto consume mucho tiempo para algunas figuras. Podemos mejorar esto añadiendo un buffer que filtra algunos puntos en la dirección opuesta que no pueden ser rellenados, lo que a su vez significa que no tiene que volver sobre una gran cantidad de píxeles que ya se han llenado. Aún así sigue existiendo un retraso después del llenado en el último punto, pero se puede ahorrar tiempo utilizando una pila o un buffer similar a este. En el relleno de puntos en la dirección opuesta se actualizan a medida que la rutina va en una dirección, por lo que la pila (actualmente un array de enteros) crece para recordar las coordenadas rellenables fuera del punto de relleno original, entonces una vez que la rutina para en esa dirección, empieza a leer coordenadas de píxel de la pila para recordar donde volver. A continuación, trata de avanzar en la dirección opuesta, pero en caso de encontrar otra vez puntos fuera de la dirección de relleno que pueda comprobar más tarde, también los añade a la pila. Y así continúa el proceso hasta que haya terminado todos los puntos.

100 WINDOW 512,256,0,0 : CLS : INK 7
110 BLOCK 100,100,50,50,7 : BLOCK 96,98,52,51,0
120 BLOCK 60,80,70,60,7   : BLOCK 56,78,72,61,0
130 Fill_From 100,55,-1,7 : REMark -1 = upward, +1 = downward
140 STOP
150 :
160 DEFine PROCedure Fill_From (fx,fy,direc,col)
170   LOCal xst%(512),yst%(512),stack%
180   REMark this routine requires a 'stack'
190   DIM xst%(512),yst%(512)
200   stack% = 0 : REMark nothing on stack at first, of course!
210   Fill_Outline fx,fy,direc,col
220 END DEFine Fill_From
230 :
240 DEFine PROCedure Fill_Outline (x,y,direction,colour)
250   LOCal loop,a,lp%,rp%
260   IF y  255 THEN RETurn
270   IF PIXEL%(x,y) = colour THEN RETurn
280   lp% = x
290   REPeat loop
300     IF lp% = 0 THEN EXIT loop
310     IF PIXEL%(lp%,y-direction) > colour THEN xst%(stack%) = lp% :
             yst%(stack%) = y-direction : stack% = stack%+1
320     IF PIXEL%(lp%-1,y) = colour : EXIT loop
330     lp% = lp% - 1
340   END REPeat loop
350   rp% = x
360   REPeat loop
370     IF rp% = 511 THEN EXIT loop
380     IF PIXEL%(rp%,y-direction) > colour THEN xst%(stack%) = rp% :
             yst%(stack%) = y-direction : stack% = stack%+1
390     IF PIXEL%(rp%+1,y) = colour : EXIT loop
400     rp% = rp% + 1
410   END REPeat loop
420   BLOCK rp%-lp%+1,1,lp%,y,colour
430   FOR a = lp% TO rp%
440     Fill_Outline a,y+direction,direction,colour
450   END FOR a
460   REPeat loop
470     IF stack% = 0 : EXIT loop
480     stack% = stack% - 1
490     Fill_Outline xst%(stack%),yst%(stack%),-direction,colour
500   END REPeat loop
510 END DEFine Fill_Outline

La rutina tiene el inconveniente de que en figuras muy complejas puede provocar el agotamiento del espacio de la pila. En la gran mayoría de los casos, una pila de 512 espacios será suficiente. Estas rutinas tienen como ventaja que muestran en la pantalla la mayor parte de lo que está sucediendo, así que usted puede hacerse una idea de cómo “piensa” la rutina. Después de un tiempo, incluso con figuras complejas, ¡puede predecirse cómo va a ser rellenada la figura!

5. El puzzle de las Torres de Hanoi

El puzzle de las Torres de Hanoi consta de tres varillas y varios anillos de diferente diámetro. Lo que usted tiene que hacer es mover todos los anillos de la primera varilla a la segunda siguiendo las normas siguientes. Sólo puede mover un anillo a la vez. Ningún anillo puede colocarse en una varilla donde un anillo más pequeño quede debajo de uno mayor (es decir, los anillos grandes no pueden colocarse encima de uno menor). Por lo tanto, debemos utilizar la tercera varilla como almacén temporal. Sólo se puede desplazar el anillo que se encuentre en la parte superior de la varilla. De esta manera podemos ir desplazando los anillos de la primera varilla a la segunda de tal manera que al final del rompecabezas la segunda varilla contenga todo los anillos en el mismo orden que se encontraban en la primera varilla. Se puede dar una solución a este problema simplemente usando una rutina recursiva para mover progresivamente los anillos de una varilla a otra. Puede, si lo desea, agregar rutinas para animar la solución. Si usted es realmente ambicioso, y ha visto el pequeño robot de Tony Firshman y Reeves Laurence en el stand de TF Serivces en los “QL Shows” controlado por un QL equipado con una interfaz y Minerva, ¡tal vez desee considerar la manera de programar el robot para que pueda solucionar este puzzle! El programa puede hacer frente a cualquier número (dentro de lo razonable) de anillos, siempre empezando con el anillo 1 para el más pequeño y el número más alto para el anillo más grande. Con un número de varillas pequeño, podemos seguir los movimientos, por ejemplo, con una pequeña pila de monedas, siempre y cuando se puedan recordar que moneda estaban en qué anillo. Con más de 3 ó 4 anillos las instrucciones pueden llegar a ser bastante largas, dada la necesidad de estar rotando continuamente los anillos para evitar que el animo más grande esté por encima de otro más pequeño.

He aquí un programa en SuperBASIC que resuelve el rompecabezas.

100 REMark Towers of Hanoi puzzle
110 INPUT'How many rings > ';rings
120 Hanoi rings,1,2,3
130 :
140 DEFine PROCedure Hanoi(r,peg_a,peg_b,peg_c)
150   IF r = 0 THEN RETurn
160   Hanoi r-1,peg_a,peg_c,peg_b
170   PRINT'Move ring ';r;' from peg ';peg_a;' to peg ';peg_b
180   Hanoi r-1,peg_c,peg_b,peg_a
190 END DEFine Hanoi

Por ahora, podría ser suficiente para comprender cómo trabaja esta rutina el emplear una pila de monedas y reproducir la secuencia según la imprime el programa. Escriba un flujo generalizado de lo que hay que hacer y verá que el patrón a seguir emerge en su cabeza sin darse cuenta. Verá entonces que el problema es fácil de resolver. Escribir un programa para resolver esto no es una tarea obvia, especialmente para producir una solución tan corta como esta.


Fuente:
Artículo original: http://www.dilwyn.me.uk/docs/articles/recursion.zip
Dilwyn Jones (Tal-y-bont, Gales, Reino Unido).

¿Cuánto tiempo tardaríamos en resolver una expresión aritmética como la siguiente?

(((3+7)*5)*7)*((((((2+6-1)*5)+(((3+5)*9)+4))*7)-(4+3-6-7+9+8))*6)

Seguro que con ayuda de lápiz y papel lo resolveríamos sin problemas pero tardaríamos un buen “ratito”. Sin ayuda alguna (de papel y lápiz) seguramente tardáramos un rato más y con un esfuerzo mental más exigente. Desde luego, él cálculo de la solución no es algo que la mente humana pueda realizar de forma automática

Las estrategias para resolver este tipo de problemas es algo que ya está muy estudiado por los diseñadores de lenguajes de programación y por los ingenieros que los implementan en compiladores reales. Se emplean para ello algoritmos muy ingeniosos y poderosos, desarrollados por los científicos e ingenieros de esta materia a lo largo de los años.

Aprovechando este tema, vamos “a jugar” un poco con SuperBASIC. El “juego” consistiría en implementar una solución que resolviera este tipo de expresiones de forma inmediata y sin errores. Nuestro programa pedirá al usuario que teclee una expresión aritmética y deberá darnos el resultado sin importar cuán compleja sea dicha expresión. Para ello elegiremos un algoritmo llamado “análisis sintáctico descendente recursivo”. Es uno de los algoritmos más simples para resolver este tipo de problemas y fue uno de los primeros que se descubrieron para implementar compiladores de lenguajes de alto nivel. (El algoritmo lo he extraído del libro “Construcción de Compiladores” de K.C. Louden).

Este sería el resultado final.

Calculadora simple en SuperBASIC

 

Esta calculadora es muy simplista, en realidad sólo es capaz de sumar, restar y multiplicar números de un sólo dígito. La ventaja es que el programa se mantiene corto y más comprensible.

El punto de partida es elegir una gramática que define una serie de derivaciones de forma recursiva, esto es muy útil ya que éstas son las que luego guían la implementación en el código.

La gramática para nuestra calculadora es la siguiente:

expresion -> expresion operadorSuma termino | termino
operadorSuma -> + | -
termino -> termino operadorMult factor | factor
operadorMul -> *
factor -> ( expresion ) | numero
numero -> 0|1|2|3|4|5|6|7|8|9

El código en SuperBASIC de nuestro “juego” está aquí:

100 REMark ===============================
110 REMark = Calculadora artimƒtica simple
120 REMark ===============================
130 :
140 MODE 8:CLS
145 Instructions
150 :
160 REPeat loopMain
170   expIn$ = ""
180   token$ = ""
190   result = 0
200   idx% = 1
210   :
220   PRINT "Introduce una expresi–n: "
230   INPUT ">> "; expIn$
240   IF expIn$ = "" THEN
250     EXIT loopMain
260   END IF
270   expIn$ = expIn$ & " "
280   token$ = getchar$
290   :
300   result = exp%
310   :
320   PRINT
330   PRINT "El resultado es "; result
340   PRINT:PRINT
350 END REPeat loopMain
360 :
370 STOP
380 :
390 REMark --- Fun, Proc -----------------
400 :
410 DEFine FuNction getchar$
420   LOCal c$
430   c$ = expIn$(idx%)
440   idx% = idx% + 1
450   RETurn c$
460 END DEFine
470 :
480 DEFine PROCedure match (expectedToken$)
490   IF token$ = expectedToken$ THEN
500     token$ = getchar$(v$)
510   ELSE
520     errorExp
530   END IF
540 END DEFine
550 :
560 DEFine PROCedure errorExp
570   PRINT#0, "error en expresi–n"
580   STOP
590 END DEFine
600 :
610 DEFine FuNction exp%
620   LOCal temp%, k
630   temp% = term%
640   REPeat loopExp
650     IF token$ <> "+" AND token$ <> "-" THEN
660       EXIT loopExp
670     END IF
680     k = CODE(token$)
690     SELect ON k
700       = 43:
710         match "+"
720         temp% = temp% + term%
730       = 45:
740         match "-"
750         temp% = temp% - term%
760     END SELect
770   END REPeat loopExp
780   RETurn temp%
790 END DEFine
800 :
810 DEFine FuNction term%
820   LOCal temp%, k%
830   temp% = factor%
840   REPeat loopTerm
850     IF token$ <> "*" THEN
860       EXIT loopTerm
870     END IF
880     match "*"
890     temp% = temp% * factor%
900   END REPeat loopTerm
910   RETurn temp%
920 END DEFine
930 :
940 DEFine FuNction factor%
950   LOCal temp%
960   IF token$ = "(" THEN
970     match "("
980     temp% = exp%
990     match ")"
1000   ELSE IF IsDigit%(token$) = 1 THEN
1010     temp% = ValDigit% (token$)
1020     token$ = getchar$
1030   ELSE
1040     errorExp
1050   END IF
1055   END IF
1060   RETurn temp%
1070 END DEFine
1080 :
1090 DEFine FuNction IsDigit% (val$)
1100   LOCal t%, res%
1110   t% = CODE(val$)
1120   IF t% < 48 OR t% > 57 THEN
1130     res% = 0
1140   ELSE
1150     res% = 1
1160   END IF
1170   RETurn res%
1180 END DEFine
1190 :
1200 DEFine FuNction ValDigit% (val$)
1210   LOCal v%
1220   v% = CODE(val$)
1230   v% = v% - 48
1240   RETurn v%
1250 END DEFine
1260 :
1270 DEFine PROCedure Instructions
1275   INK 4
1280   PRINT "Calculadora aritmƒtica simple."
1290   PRINT "------------------------------"
1300   PRINT "Calculadora minimalista que realiza"
1310   PRINT "operaciones de suma, resta y "
1312   PRINT "multiplicaci–n con n™meros de un s–lo"
1320   PRINT "d“gito."
1330   PRINT
1340   PRINT
1345   INK 7
1350 END DEFine


Bibliografía:
Contrucción de Compiladores
Kenneth C. Louden
Ed. Thomson

Edito: Corregido Bug y adaptado código a SBASIC.

El algoritmo Ratcliff/Obershelp consiste en una estrategia para calcular la similitud entre dos cadenas de caracteres. Para este cálculo se emplean el número de caracteres coincidentes dividido por el número total de caracteres en las dos cadenas. La búsqueda de las coincidencias se basa en ir hallando la mayor sub-secuencia de caracteres en común entre las dos cadenas de forma recursiva.

El algoritmo está diseñado para comparar dos cadenas y devolver un porcentaje que represente la similitud o lo cerca que están la una de la otra. Un resultado de 67 significa que las dos cadenas tienen un porcentaje de igualdad del 67%.

Podríamos usarlo para comparar una palabra mal escrita con una serie de posibles palabras correctas en el corrector ortográfico de un procesador de textos. La palabra con el mayor porcentaje de igualdad se tomaría como la ortografía correcta de la palabra. También podríamos emplear esta rutina en algún juego conversacional en el que quisiéramos ayudar al usuario en caso de que comenta errores ortográficos o tipográficos.

El programa es un buen ejemplo de cómo utilizar la recursividad en SuperBASIC. El algoritmo toma las dos palabras y extrae la mayor subcadena común coincidente entre ellas (llamemos a esta subcadena MSC). Para cada cadena, se toma la parte de la palabra a la izquierda y a la derecha de la MSC y con esta sub-cadenas se vuelve a repetir el proceso comparando unas y otras. El proceso finaliza cuando la cadena completa es analizada.

Como ejemplo, echemos un vistazo a las dos palabras “pennsylvania” y “pencilvaneya”. La MSC es “lvan”. El programa toma entonces las subcadenas s más a la izquierda de ambas palabras (“Pennsy” y “penci”) y las procesa siguiendo la misma estrategia. La MSC es ahora “pen”. Dado que no hay nada a la izquierda de la MSC (“pen” en este caso), el programa intenta entonces el lado derecho, “NSY” y “ci”. Con estas dos cadenas, no hay MSC. El programa entonces sube hacia arriba en el árbol y trata las otras subcadenas de la derecha, “ia” y “eya”. Esto continúa hasta que todas las subcadenas se analizan.

La implementación que presentamos en SuperBASIC consiste en una función que devuelve un valor con el “parecido” de las dos cadenas que vamos a analizar.

Podemos invocar a la función de la siguiente manera:

   x = percent_alike(a$,b$) 

Siendo a$ y b$ las variables de cadena que queremos comparar.

La función puede tomar también literales como argumentos, por ejemplo:

   x = percent_alike("sofware","softwear")

Hay que tener en cuenta que el programa distingue mayúsculas y minúsculas, con lo cual hay que convertir las dos cadenas a mayúsculas o a minúsculas antes de realizar la comparación.

A continuación mostramos el código fuente en SuperBASIC de este algoritmo.

100 REMark Test ---
110 CLS
120 x = percent_alike("sofware","softwear")
130 PRINT "Percent alike: sofware - software", x
140 STOP
150 :
160 :
170 DEFine FuNction percent_alike(a$,b$)
180   LOCal total
190   total = num_alike(a$,b$)
200   RETurn INT( total/(LEN(a$)+LEN(b$))*100)
210 END DEFine percent_alike
220 :
230 :
240 DEFine FuNction num_alike (a$, b$)
250   LOCal total, temp$, a1, a2, b1, b2, large$
260   total = 0
270   IF a$=b$ THEN RETurn LEN(a$)*2
280   IF LEN(a$)=1 AND LEN(b$)=1 THEN RETurn 0
290   REMark  ** make a$ the shortest string
300   IF LEN(a$) > LEN(b$) THEN
310     temp$ = a$
320     a$ = b$
330     b$ = temp$
340   END IF
350   REMark ** if a$ = one char and b$ > one char
360   IF LEN(a$)=1 THEN RETurn (a$ INSTR b$)
370   large$ = find_gt_com$ (a$, b$)
380   IF large$ = "" THEN RETurn 0
390   length = LEN(large$)
400   total = length*2
410   a1 = large$ INSTR a$
420   a2 = a1 + length
430   b1 = large$ INSTR b$
440   b2 = b1 + length
450   IF (a1>1) OR (b1>1) THEN
460     total = total+num_alike (a$(1 TO (a1-1)), b$(1 TO (b1-1)))
470   END IF
480   IF (a2LEN(a$)+1) OR (b2LEN(b$)+1) THEN
490     total = total+num_alike (a$(a2 TO), b$(b2 TO))
500   END IF
510   RETurn total
520 END DEFine num_alike
530 :
540 :
550 DEFine FuNction find_gt_com$ (a$, b$)
560   LOCal temp$, i, j, temp, large$
570   IF LEN(a$) > LEN(b$) THEN
580     temp$ = a$
590     a$ = b$
600     b$ = temp$
610   END IF
620   LET large$=""
630   FOR i = 1 TO LEN(a$)
640     FOR j = i TO LEN(a$)
650       temp = a$(i TO j) INSTR b$
660       IF (temp0) AND (LEN(a$(i TO j))>LEN(large$)) THEN
670         large$=a$(i TO j)
680       END IF
690     END FOR j
700   END FOR i
710   RETurn large$
720 END DEFine find_gt_com

—–
Fuente:
Ratcliff/Obershelp Pattern Matching (por Tim Swenson)
QL Hacker’s Journal
—–

El término “manejo de errores” o “manejo de excepciones” hace referencia a una característica básica que debe tener cualquier lenguaje de programación moderno. Consiste básicamente en una estructura de control diseñada para tratar y gestionar de forma adecuada condiciones anormales en el flujo de un programa.

Nuestro querido SuperBASIC, a pesar de su antigüedad, dispone también de este tipo de construcciones incorporadas en el propio lenguaje.

La estructura básica que posee el SuperBASIC para el manejo de errores está formado por el comando WHEN ERROR el cual se apoya en otros comandos como RETRY, CONTINUE, ERNUM, ERLIN y REPORT. Con esta estructura podemos tratar de una forma más o menos limpia cualquier condición de error que suceda durante la ejecución de nuestro programa.

WHEN ERROR forma parte de SuperBASIC desde las versiones JS de la ROM en adelante. No funciona en las primeras versiones de la ROM del QL como las versiones FB, AH y JM. Para comprobar la versión de la ROM se puede usar los comandos PRINT VER$. Esto imprimirá un código de 2, 3 o 4 letras que indica la versión del SuperBASIC (o SBASIC si ustas utilizando el SMSQ). Los usuarios de Minerva obtendrán el código ‘JSL1’, mientras que los usuarios SMSQ obtendrán la salida ‘HBA’.

Hay algunos problemas menores con WHEN ERROR en las versiones JS y MG de SuperBASIC, pero es perfectamente usable.

La estructura básica para el manejo de errores se de define con una sentencia WHEN ERROR, y debe cerrarse con la sentencia END WHEN. Cualquier línea de código entre esas sentencias es ignorada hasta que ocurre un error. Cuando se produce un error en el flujo del programa entonces el bloque de código encerrado entre WHEN ERROR y END WHEN se ejecutará con el objetivo de tratar el error ocurrido.

Veamos un ejemplo muy simple.

100 WHEN ERRor
110   PRINT "Oops...! Ha ocurrido un error."
120 END WHEN
130 INPUT "Introduce un número > "; numero

Este programa te pedirá que introduzcas un número. Si escribimos cualquier texto o caracteres no numéricos, el programa imprimirá en pantalla la frase “Ooops…! Ha ocurrido un error.”, este sería nuestros “manejador de errores”. Este simple programa, en la práctica no sirve de mucho ya que no estamos solucionando nada, pero es el comienzo.

Las funciones ERNUM y ERLIN de SuperBASIC nos dirán el código de error y la línea donde se produjo el último error respectivamente. Por ejemplo, el siguiente código, nos mostrará estos datos.

100 WHEN ERRor
110   PRINT "Oops! Error ";ERNUM;' en la linea ';ERLIN
120 END WHEN
130 INPUT "Introduce un número > "; numero

El comando REPORT imprimirá el mensaje correspondiente al código de error que se ha producido. Si especificamos un número de canal, entonces el mensaje se imprimirá en la ventana que hayamos elegido.

100 WHEN ERRor
110   PRINT "Oops! Error ";ERNUM;' at line ';ERLIN
115   REPORT #1,ERNUM
120 END WHEN
130 INPUT "Enter a number > ';number

Si deseas desactivar el manejador de errores por cualquier razón en cualquier parte de su programa, sólo bastará definir una estructura WHEN ERROR vacía. Por ejemplo:

1000 REMark --- después de esto no habrá manejador de errores. 
1010 WHEN ERRor
1020 END WHEN

En la práctica, esto último no parece funcionar del todo bien en algunas versiones de la ROM.

Por último puedes añadir la sentencia CONTINUE o RETRY para especificar como debe actuar ante un error nuestro bloque de “controlador de errores”. CONTINUE indicará al programa que continúe la ejecución en la siguiente línea donde se produjo el error (en este caso nuestro programa deberá tomar previamente las medidas adecuadas). RETRY indicará a nuestro programa que vuelva a intentar la sentencia donde se produjo el error. Por ejemplo:

100 WHEN ERRor
110   REPORT ERNUM : PRINT #0, " en la línea. "; ERLIN;
120   PRINT #0, "¡Deberías fijarte bien!, número incorrecto."
130   RETRY
140 END WHEN
150 INPUT "Introduce el primer número > ";num1
160 INPUT "Introduce el segundo número > ";num2

Cuando ejecutes este programa y te pida que introduzcas el primer número, pulsa ENTER (es decir, una entrada en blanco). Esto causaría un error del tipo “error en expresión”, pero en nuestro caso, el controlador de errores te dirá que debes fijarte bien y volver a introducir un número. A continuación el programa volverá a intentar ejecutar la línea 150.

Con estos simples ejemplos hemos visto una forma muy limpia de tratar los errores con SuperBASIC de una manera estructurada, una característica avanzada en comparación a otros BASIC’s de la época.

—-
Fuente:
“ERROR TRAPPING” (David Denham). QL-Today
—-