Vous êtes sur la page 1sur 155

MANUAL DE QBASIC

TEMA 1.15
FICHEROS SECUENCIALES
 1.15.1 - Introducción al uso de ficheros
 1.15.2 - Introduccion a los ficheros secuenciales
 1.15.3 - Escribir información en ficheros secuenciales
 1.15.4 - Leer información de ficheros secuenciales
 1.15.5 - Anexar información a ficheros secuenciales

1.15.1 - INTRODUCCIÓN AL USO DE FICHEROS


Hasta ahora en nuestros programas para almacenar información
hemos utilizado variables que son la forma que tienen los lenguajes de
programación de almacenar datos en la memoria RAM del ordenador.
Es una forma muy cómoda y extremadamente rápida de manejar
datos, pero tiene el grave inconveniente de que toda la información se
pierde al finalizar la ejecución del programa.

En el tema de los arrays vimos varios ejemplos de programas donde


se pedían los nombres de las personas que viven en un bloque de
pisos. Todo funcionaba muy bien pero había que hartarse de escribir
todos estos nombres cada vez que entrábamos al programa porque al
terminar se pierden. Una solución para evitar esto podría ser incluir los
nombres de las personas en el código fuente del programa, pero estos
nombres pueden cambiar alguna vez y el usuario del programa no
tiene por qué saber como modificar esto. La solución definitiva a este
problema consiste en guardar la información en el sistema de archivos
de las unidades de disco del ordenador donde permanecerá por tiempo
indefinido y podremos recuperarla o modificarla todas las veces que
queramos.

La información se almacena en los discos del ordenador en unas


estructuras lógicas llamadas ficheros o archivos. Mientras que en una
cinta de vídeo si grabamos una película nosotros nos tenemos que
preocupar de ver donde está el final para grabar lo siguiente a
continuación y no borrar un trozo de lo que ya tenemos, en el
ordenador es el sistema operativo (MS-DOS, Linux, Etc.) el que se
encarga de controlar en que parte del disco se graban los datos y de
ponerle un nombre y situarlo en el sistema de archivos (La FAT y los
directorios) para que nosotros podamos acceder a esa información solo
con dar el nombre del archivo sin tener que preocuparnos de en que
parte del disco está o de si nos vamos a cargar otra información al
grabar nuevos datos.

Principalmente hay dos tipos de ficheros: Secuenciales y de acceso


aleatorio. Aunque están grabados en los discos de idéntica forma se
diferencian de cómo los manejamos nosotros desde nuestro programa.
En los siguientes apartados veremos las diferencias y la forma de usar
cada uno de ellos.

Ficheros secuenciales son aquellos en que los datos se graban o se


leen línea a línea siempre empezando por el principio. Normalmente al
abrirlo se copiará su contenido en alguna estructura de variables de
nuestro programa para su utilización y posible modificación. Después
si hay que guardarlo se hará completamente sustituyendo a toda la
información que hubiera anteriormente en el fichero, aunque alguna
parte de la información vuelva a ser la misma. El concepto es similar a
lo que hacemos con los ficheros de las aplicaciones normales como
procesadores de texto o los propios ficheros *.BAS de los programas
de QBasic.

Ficheros de acceso directo o acceso aleatorio (No tiene nada que ver
con los iconos de acceso directo de Windows) son los que nos
permiten leer o modificar un determinado dato sabiendo su posición
sin tener que recorrer todos los datos anteriores. El ejemplo más típico
de estos ficheros son las tablas de las bases de datos donde podemos
añadir, modificar, consultar o borrar la información de una persona (un
registro) sin preocuparnos de los demás.
La extensión que elijamos para nuestros ficheros puede ser cualquiera,
pero deberemos evitar usar extensiones ya usadas por otros
programas que podrían provocar conflictos en Windows. Lo más
normal es usar extensiones como .TXT para los ficheros secuenciales
que solo contienen texto y .DAT en general para los que pueden
contener texto o cualquier otro tipo de caracteres.

1.15.2 - INTRODUCCIÓN A LOSFICHEROS


SECUENCIALES
La forma más elemental de trabajar con ficheros es leerlos línea por
línea, una tras otra, para irlos analizando o irlos guardando en una
estructura de datos de nuestro programa. Si queremos guardarlo se
hará de la misma forma, escribiendo líneas una tras otra desde la
primera hasta la última. Los ficheros secuenciales normalmente se
leen siempre enteros y para guardarlos se puede elegir entre
reemplazar completamente su contenido o bien guardar las nuevas
líneas a continuación de la s existentes, a lo que llamaremos "Anexar".

Entendemos por "Línea" a una secuencia de caracteres alfanuméricos


(Letras, números y signos de puntuación) terminados por el carácter
especial de retorno de carro que es el que se genera cuando pulsamos
la tecla Enter. Este carácter no lo vemos en la pantalla pero se genera
automáticamente al final de lo que escribimos con la orden PRINT, si
no ponemos un punto y coma al final, como vimos en el tema de
Entrada y Salida.

Para trabajar con ficheros hay que "Abrirlos" especificando su nombre


y si queremos leer o escribir en él. A continuación leeremos o
escribiremos los datos, según corresponda por el modo en que está
abierto, y finalmente lo cerraremos.
1.15.3 - ESCRIBIR INFORMACIÓN
EN FICHEROS SECUENCIALES
Vamos con un ejemplo de un programa que abre un fichero para
escritura (lo crea), escribe en él los nombres de los días de la semana
y finalmente lo cierra.

OPEN "diasem.txt" FOR OUTPUT AS #1


PRINT #1, "Lunes"
PRINT #1, "Martes"
PRINT #1, "Miércoles"
PRINT #1, "Jueves"
PRINT #1, "Viernes"
PRINT #1, "Sábado"
PRINT #1, "Domingo"
CLOSE #1

Este programa no produce ningún resultado en la pantalla, sólo el


rótulo "Presione cualquier tecla y continúe" para indicar que ha
terminado.

Si buscamos el fichero diasem.txt y lo abrimos con el bloc de notas de


Windows comprobaremos que contiene lo siguiente:

Lunes
Martes
Miercoles
Jueves
Viernes
Sabado
Domingo

Salvo los típicos problemas con los acentos, es lo que esperábamos. Si


lo abrimos con el EDIT de MS-DOS veremos los acentos
correctamente.

Ahora vamos a comentar detalladamente como funciona el programa.

La primera línea es la más importante cuando trabajamos con ficheros.


Si la traducimos al castellano dice algo así como:

Abre "diasem.txt" para salida de datos y llámale #1


Visto esto ya está claro su significado pero de todas formas hay que
decir un montón de cosas:

 Al abrir un fichero para escritura si no existe se crea y si


existe se borra su contenido para empezar desde cero.
 Si no especificamos una ruta de directorios, como hemos
hecho en este caso, el fichero se crea en el "directorio
activo". El directorio activo es en el que nos encontrábamos
al entrar a QBasic desde MS DOS. En el case de que
hayamos entrado desde Windows será el que aparece en la
casilla "Iniciar en:" de las propiedades del acceso directo a
QBasic. Para cambiar el directorio activo desde QBasic
podemos usar la orden CHDIR seguida de la nueva ruta de
directorios. Esta orden la veremos con más detalle en el
tema dedicado a comunicación con aplicaciones externas.
 Si especificamos una ruta tendrá que existir o se producirá
un error. Al crear un nuevo archivo no se crean directorios
que no existan.
 El nombre y ruta del archivo a abrir se pueden especificar de
forma literal, como hemos hecho aquí, o bien usar una
expresión de cadenas cuyo valor sea la ruta y el nombre del
archivo con la sintaxis correcta (Barras inversas, puntos y
demás).

La única cosa que nos puede resultar extraña de la instrucción OPEN


es ese #1 que aparece al final. A este numero le llamamos "descriptor
de archivo" y nos va a servir para identificar unívocamente al archivo
que acabamos de abrir en las ordenes de entrada y salida de nuestro
programa hasta que cerremos el archivo. Por lo tanto si abrimos otro
archivo antes de cerrar este tendremos que usar el descriptor #2 y así
sucesivamente. QBasic nos permite tener abiertos a la vez hasta 255
archivos, pero la memoria disponible y la cláusula FILES del archivo
CONFIG.SYS nos limitarán el número. Normalmente no necesitaremos
más de uno o dos archivos abiertos a la vez en nuestros programas
sencillos.

Siguiendo con el código de nuestro programa a continuación tenemos


una serie de instrucciones PRINT que como llevan el descriptor de un
archivo escriben en el archivo en lugar de en la pantalla. Podemos
usarlas exactamente de la misma forma que cuando las usamos
normalmente para escribir en pantalla. No tienen por qué estar todas
juntas.

Finalmente lo que hacemos es cerrar el archivo con la orden CLOSE.


Conforme vamos escribiendo en el archivo con la orden PRINT la
información no se graba en el disco inmediatamente, sino que se va
almacenando de forma transparente para el usuario en un área
especial de la memoria (El "Buffer" de escritura en disco) o bien en
registros de memoria especial dentro de la placa de circutos del propio
disco duro y cuando hay información suficiente es cuando el cabezal
del disco se mueve y la información se graba físicamente en el disco.
Al cerrar el archivo nos aseguramos de que la información que
quedara por escribir se grabe realmente en el disco, y que además
todos los recursos utilizados, así como el número del descriptor se
liberen y queden disponibles para ser usados por otro archivo. Si
programamos para un sistema multitarea (Windows 9x lo es, más o
menos) es muy importante ocupar los recursos del sistema el menor
tiempo posible y por lo tanto deberemos cerrar los archivos que ya no
nos hagan falta abiertos aunque todavía vayamos a seguir utilizando
otras partes del programa durante más tiempo.

Cuando abrimos un fichero este queda disponible para todos los


módulos (Programa principal, procedimientos y funciones) de nuestro
programa, con lo que podemos abrirlo desde un módulo, leer o escribir
desde otro, y cerrarlo desde otro, pero siempre teniendo en cuenta
que solo lo podemos usar después de haberlo abierto y antes de
cerrarlo.

1.15.4 - LEER INFORMACIÓN DE FICHEROS


SECUENCIALES
En el apartado anterior hemos abierto un archivo para escritura. Ahora
vamos a abrir uno existente para leer su contenido. Veamos un
ejemplo de un programa que abre el fichero ya existente "diasem.txt"
que hemos creado con el programa anterior para mostrarlo en
pantalla.
CLS
OPEN "diasem.txt" FOR INPUT AS #1
WHILE NOT EOF(1)
LINE INPUT #1, linea$
PRINT linea$
WEND
CLOSE #1

Este programa no produce ningún cambio en el archivo y su resultado


por pantalla es:

Lunes
Martes
Miércoles
Jueves
Viernes
Sábado
Domingo

Ahora veamos cómo funciona.

 Borramos la pantalla.
 Abrimos el archivo "diasem.txt" para lectora (INPUT) y le
llamamos #1.
 Mientras no se acabe el archivo 1, leemos línea por línea y
las vamos escribiendo en pantalla usando la variable linea$.
 Cerramos el archivo.

Como se pede ver, la instrucción OPEN es igual a la anterior, sólo varía


el modo de apertura que en este caso es INPUT, es decir, entrada de
datos, lectura. Si el fichero especificado no existe se producirá un error
de tiempo de ejecución. En el apartado de control de errores veremos
como detectar si un fichero existe antes de inventar abrirlo.

A continuación vamos leyendo las líneas del archivo. Como no tenemos


por qué saber cuantas líneas tiene el archivo, usaremos un bucle
MIENTRAS que usa como condición la función EOF (End Of File) que
devuelve Verdadero si se ha alcanzado el final del archivo y falso en
caso contrario. Observa que a EOF le pasamos como parámetro el
descriptor del archivo, pero sin el carácter #. Esto es para poder usar
alguna expresión matemática que devuelva el numero del archivo si es
que tenemos varios abiertos y no queremos usar el número de forma
literal. Dentro del bucle lo que hacemos es leer una línea usando la
instrucción LINE INPUT seguida del descriptor del archivo y del nombre
de la variable donde la queremos guardar. Esta instrucción permite
leer líneas de hasta 255 caracteres que acaben con un carácter
especial de retorno de carro (Las que en el ejemplo anterior escribimos
con la orden PRINT). Seguidamente escribimos en la pantalla el valor
de la variable line$ usando la orden PRINT como hemos hecho
siempre.

Finalmente, terminado el bucle, cerramos el archivo para dejar el


recurso disponible. Como estaba abierto para lectura no se producirá
ningún cambio.

Es muy importante entender que para leer dados de un fichero


secuencial debemos usar siempre un bucle MIENTRAS, y no un
REPETIR, ya que en este caso intentaríamos leer al menos una línea
del fichero antes de comprobar si se ha llegado al final, y se puede dar
el caso de que un fichero exista, pero esté completamente vacío con lo
que intentaríamos leer más allá del final del final del archivo y
generaríamos un error de tiempo de ejecución. En la ayuda de QBasic
viene un ejemplo hecho con un bloque DO...LOOP y se puede
comprobar que puede que no funcione correctamente en el caso de
que el fichero esté vacío.

También podemos decir aquí que si somos nosotros quienes


escribimos el archivo a leer usando un editor de texto es conveniente
pulsar al final la tecla ENTER una vez para que el cursor pase a la línea
de abajo y se genere el carácter de retorno de carro. Si no lo hacemos
puede ocurrir que cuando leamos el archivo con nuestro programa de
QBasic en algunos casos no se llegue a leer la última línea. Esta
indicación también la hace MS-DOS para los ficheros CONFIG.SYS,
AUTOEXEC.BAT y los ficheros de proceso pro lotes que nosotros
podamos escribir.

Para terminar con la teoría de los ficheros secuenciales vamos a ver un


ejemplo de un programa que copia un fichero de texto en otro
poniéndole todo el texto en mayúsculas con ayuda de la función
UCASE$. El usuario tendrá que escribir los nombres de los dos
ficheros, el existente y el nuevo, y el programa los abrirá los dos a la
vez, uno para lectura y el otro para escritura.
CLS
INPUT "Nombre del fichero de origen ya existente: ", fich1$
INPUT "Nombre del nuevo fichero: ", fich2$
OPEN fich1$ FOR INPUT AS #1
OPEN fich2$ FOR OUTPUT AS #2
PRINT "Leyendo y grabando, por favor espera..."
WHILE NOT EOF (1)
LINE INPUT #1, linea$
PRINT #2, UCASE$(linea$)
WEND
CLOSE
PRINT "Proceso terminado"

El resultado por pantalla podría ser:

Nombre del fichero de origen ya existente: diasem.txt


Nombre del nuevo fichero: diasem2.txt
Leyendo y grabando, por favor espera...
Proceso terminado

Y en los ficheros el que pusiéramos primero no se modificará, mientras


que el segundo si no existe se crea y si existe se sobrescribe con el
contenido del primero en mayúsculas.

Ahora veamos algunas cosas del programa:

 Como se puede ver, tenemos dos ficheros abiertos a la vez y


los identificamos con un descriptor diferente.
 Leemos datos mientras no se acabe el fichero 1 y los vamos
escribiendo al fichero 2 convertidos a mayúsculas usando la
función UCASE$.
 Sacamos un mensaje por pantalla antes de empezar y
después de terminar para que el usuario vea "que ha pasado
algo". Si prevemos que el proceso va a ser muy largo
tendríamos que sacar algún mensaje de progreso cada vez
que se haga un número de pasadas del bucle. Para hacer
esto usaríamos algún contador.
 Finalmente cerramos todos los ficheros abiertos usando la
orden CLOSE sin pasarle ningún descriptor.

Si escribes el programa en QBasic y lo ejecutas podrás comprobar que


puede ser difícil acertar a la primera con el nombre del archivo, y más
si ponemos una ruta de directorios, unidades de disco, etc... con lo
que se producirá un error de ejecución. También si se te ocurre poner
el mismo nombre las dos veces nos dará un error de tiempo de
ejecución diciendo que el fichero ya está abierto. En el tema de
manejo de errores veremos como se pueden solucionar estos
problemas.

1.15.5 - ANEXAR INFORMACIÓN A FICHEROS


SECUENCIALES
Cuando abríamos un fichero para escritura usando el modo OUTPUT
decíamos que si no existe se crea y si existe se borra su contenido,
pero en algunos caso nos puede interesar que el contenido del fichero
no se borre y las nuevas líneas de información se añadan al final del
fichero ya existente, incrementando su tamaño. Es el caso de los
ficheros .LOG que utilizan muchos programas para llevar un registro
de acciones o de problemas que puedan surgir y en los que los
distintos procesos o programas van añadiendo información conforme
va haciendo falta.

La forma de abrirlo es usando el modo APPEND en la instrucción OPEN,


por ejemplo:

OPEN "eventos.txt" FOR APPEND as #1

En este caso si no existe el fichero eventos.txt se crea, y si existe se


conserva, añadíendose la nueva información al final.

La forma de trabajar con un fichero abierto en modo APPEND es


exáctamente la misma que en uno abierto en modo OUTPUT. Hay que
recordar que no podemos asegurar que la información se graba
físicamente en el fichero hasta que no lo cerremos con la orden
CLOSE.

El tamaño de los ficheros usados sólo de esta forma siempre va a


crecer y seremos nosotros los encargados de observar que no se hace
demasiado grande y de borrarlo cuando ya no nos haga falta la
información que contiene.

TEMA 1.16
FICHEROS DE ACCESO DIRECTO O
ALEATORIO
 1.16.1 - Introducción a los ficheros directos
 1.16.2 - Lectura y escritura en ficheros directos
 1.16.3 - Mantenimiento de ficheros directos
o 1.16.3.1 - Introducción al mantenimiento de
ficheros
o 1.16.3.2 - Altas. Añadir registros
o 1.16.3.3 - Consultas. Búsqueda secuencial de
registros
o 1.16.3.4 - Modificaciones de registros
o 1.16.3.5 - Bajas. Borrar registros
o 1.16.3.6 - Compactación de ficheros directos
o 1.16.3.7 - Elaboración de listados de datos
 1.16.4 - Breve idea sobre bases de datos
 1.16.5 - Otras instrucciones relacionadas con ficheros

1.16.1 - INTRODUCCIÓN A LOS FICHEROS


DIRECTOS
Los ficheros secuenciales los usábamos de forma que era necesario
leerlos línea por línea siempre desde el principio y sin saltar ninguna, y
para guardarlos ocurría lo mismo. Ahora vamos a trabajar con ficheros
de acceso directo o aleatorio, que estarán formados por "trozos" de
igual tamaño, y por lo tanto el ordenador será capaz de saltar al
registro que nosotros queramos para leerlo o escribirlo. Los registros
se corresponden con la estructura de un tipo de datos definido por el
usuario y para acceder a ellos únicamente se calcula su posición inicial
a partir del número de registro y de su tamaño, que nos calcula la
función LEN. No están separados de ninguna forma, ya no hablamos
de líneas ni de caracteres de retorno de carro.

Otro concepto importante que se introduce con los ficheros directos es


el de "Campo clave". El campo clave puede ser la posición dentro del
archivo donde se guarda ese registro en concreto. Esto implica que el
campo clave es único ya que no puede haber dos registros en la
misma posición. Un ejemplo de campo clave podría ser el DNI en una
lista de personas o el número de habitación en la lista de habitaciones
de un hotel. Este dato no está escrito en la estructura de datos del
archivo, sino que es determinado por la posición del registro dentro del
archivo. Si usamos como campo clave un número muy grande como el
DNI nos encontraremos con el inconveniente de que el archivo llegará
a tener un tamaño muy grande ya que como veremos más adelante
los registros intermedios aunque estén vacíos también existirán en el
archivo. De la misma forma el campo clave es obligatorio de
especificar y no lo podemos dejar en blanco, ya que no se sabría en
que posición se guarda el registro.

Lo más inmediato es usar como campo clave un valor numérico bajo


correlativo (1, 2, 3...) que el usuario vaya escribiendo, o bien que el
programa lo calcule automáticamente imitando a los tipos de datos
autonuméricos de las bases de datos.

1.16.2 - LECTURA Y ESCRITURA


EN FICHEROS DIRECTOS
Vamos con un ejemplo que define un tipo de datos compuesto de
nombre y edad, y abre un fichero en modo RANDOM para almacenar
en él los datos de personas que va escribiendo el usuario, al estilo de
como lo haría una agenda.

TYPE TIPOPERSONA
nombre AS STRING * 20
edad AS INTEGER
END TYPE
DIM persona AS TIPOPERSONA
CLS
OPEN "agenda.dat" FOR RANDOM AS #1 LEN = LEN(persona)
n=0
DO
n=n+1
INPUT "Nombre: ", persona.nombre
INPUT "Edad: ", persona.edad
PUT #1, n, persona
INPUT "¿Meter más datos (S/N)? ", respuesta$
LOOP UNTIL respuesta$="N"
CLOSE #1
PRINT n; "registros grabados en la agenda."

El resultado por pantalla podría ser este:

Nombre: Pepe
Edad: 22
¿Meter más datos (S/N)? S
Nombre: Paco
Edad: 25
¿Meter más datos (S/N)? S
Nombre: Ana
Edad: 28
¿Meter más datos (S/N)? N
3 registros grabados en la agenda.

Y el resultado en el sistema de archivos será que se ha creado un


nuevo archivo llamado agenda.dat, si no existía de antes, que contiene
los datos de tres personas. De la forma que está hecho el programa
nuestros tres registros irían a parar siempre al principio del archivo. En
el siguiente ejemplo veremos como leer los datos del archivo, ahora
vamos a ver como funciona este programa.

 Lo primero que hacemos es definir un tipo de datos


personalizado que tiene dos miembros, nombre de 20
caracteres y edad que es un entero.
 A continuación declaramos una variable como del tipo de
datos que acabamos de definir y que nos va a servir como
"Plantilla" para trabajar con nuestro archivo de acceso
aleatorio.
 Después borramos la pantalla como siempre.
 Ahora abrimos el archivo "agenda.dat" en modo de acceso
aleatorio (RANDOM), le asignamos el descriptor #1 y con la
cláusula LEN le decimos cuanto largo van a ser los registros.
Podíamos haberlo calculado nosotros viendo cuando ocupan
todos los miembros de nuestro tipo de datos y poner
simplemente el número de bytes, pero es más cómodo y
más seguro que lo haga la función LEN (Se llama igual que
la cláusula, pero cada cosa es diferente).
 Seguidamente ponemos un contador (n) a cero. En este caso
en QBasic no hubiera sido necesario, pero es mejor
acostumbrarse a hacerlo siempre.
 A continuación entramos en un bloque REPETIR,
incrementamos en 1 el contador y le pedimos al usuario que
escriba el nombre de la persona y después la edad y lo
guardamos cada cosa en su sitio en la variable persona.
 Ahora vamos a escribir en el fichero de acceso aleatorio el
registro completo, donde ya tenemos almacenado el nombre
y la edad que acaba de escribir el usuario. Para hacer esto
usamos la instrucción PUT a la que le tenemos que pasar
como parámetro el descriptor del archivo (el carácter # es
opcional), el número de registro al que nos estamos
refiriendo, para lo que usaremos el contador n, y los datos
propiamente dichos que están almacenados en la variable
persona.
 Preguntamos al usuario si quiere meter más datos y si pulsa
la N mayúscula salimos del bucle, en caso contrario
volvemos al principio donde incrementamos el contador,
volvemos a llenar los miembros de la variable persona y
finalmente los grabamos en la siguiente posición del archivo.
 Cuando salimos del bucle cerramos el archivo (hay que
hacerlo siempre) y escribimos un mensaje en pantalla con el
número de registros grabados aprovechando el valor de la
variable n que usamos como contador.

Cuando abrimos un archivo en modo RANDOM si no existe se crea, y si


existe no ocurre nada, ya que como veremos a continuación en él
vamos a poder tanto leer como escribir registros.

Si al grabar un registro nos referimos a uno que ya existe, este se


sobreescribirá entero, y si no existe el fichero aumentará de tamaño
hasta permitir usar ese número de registro, añadiendo si es necesario
más registros entre medio. La información que se almacenará en estos
registros intermedios es indeterminada. Por ejemplo si tenemos un
fichero con tres registros (1, 2 y 3) y escribimos en el registro 10 se
almacenará nuestra información en el registro 10 y a la vez se crearán
6 registros más (4, 5, 6, 7, 8 y 9) llenos de información
indeterminada.

El primer registro es siempre el 1 y el último posible dependerá del


espacio disponible en disco.

Los ficheros que creemos de esta forma ya no los podremos editar con
un editor de texto como el EDIT o el bloc de notas, ya que contienen
una mezcla de letras y de valores numéricos que se pueden
representar por caracteres especiales no imprimibles. También es
necesario abrirlos siempre usando el mismo tipo de datos como
"molde", ya que de no hacerlo así todos los registros se mezclarían y
no nos serviría de nada.

Ahora vamos a ver otro ejemplo de programa que lea el fichero que
acabamos de crear con el programa anterior.

TYPE TIPOPERSONA
nombre AS STRING * 20
edad AS INTEGER
END TYPE
DIM registro AS TIPOPERSONA
CLS
OPEN "agenda.dat" FOR RANDOM AS #1 LEN = LEN(registro)
numeroRegistros = LOF(#1) / LEN(registro)
FOR n = 1 TO numeroRegistros
GET #1, n, registro
PRINT persona.nombre, persona.edad
NEXT
CLOSE #1

El resultado por pantalla podría ser este:

Pepe 22
Paco 25
Ana 28
Y en el fichero no se produciría ningún cambio porque no hay ninguna
instrucción de escritura.

Como se puede ver el programa es parecido al anterior. Hemos


definido un tipo de datos que en esta ocasión se llama registro en vez
de persona pero tiene exactamente la misma estructura y a
continuación abrimos el fichero de la misma forma.

Podemos calcular el número de registros que tiene un fichero usando


la función LEN que, como ya sabemos, nos da el tamaño del registro, y
la función LOF (Length Of File) que nos devuelve el tamaño en bytes
del fichero abierto cuyo descriptor le pasemos como parámetro (con o
sin #) usando la fórmula "Tamaño del fichero dividido por tamaño del
registro". De esta forma si el fichero tiene por ejemplo 200 bytes y
cada registro mide 40 bytes obtenemos como resultado que hay 5
registros. Como no hay caracteres retornos de carro ni nada de eso las
cuentas siempre salen exactas.

Una vez que sabemos cuantos registros hay podemos escribirlos en


pantalla sencillamente usando un bucle FOR. Para leer información del
fichero usamos la instrucción GET cuya forma de uso es similar a la de
la instrucción PUT que ya hemos visto. Tras ejecutar la instrucción GET
el contenido del registro queda almacenado en la estructura de la
variable.

Si leemos más allá del final del fichero no ocurre nada, a diferencia de
lo que pasaba con los ficheros secuenciales, pero la información que se
nos da es indeterminada, en el mejor de los casos ceros y espacios en
blanco. De todas formas lo correcto es calcular cual es el ultimo
registro como hemos hecho antes y no leer nada más allá para evitar
errores lógicos y posibles confusiones. También nos hará falta conocer
siempre el número de registros para insertar los nuevos a continuación
y no sobreescribiendo los ya existentes como hace nuestro primer
ejemplo que no es nada práctico. Esto lo veremos en los siguientes
apartados dedicados a lo que se conoce como "Mantenimiento de
ficheros"
1.16.3 - MANTENIMIENTO
DE FICHEROS DIRECTOS

1.16.3.1 - INTRODUCCIÓN
AL MANTENIMIENTO DE FICHEROS

En el apartado anterior teníamos un programa para meter datos en un


fichero, y otro para listar todos los datos que acabábamos de meter, y
para complicarlo todo si ejecutábamos otra vez el primer programa
como siempre empezábamos por el registro 1 seguro que
sobreescribíamos datos. También para borrar información sólo
podíamos borrar todos los datos a la vez borrando el fichero desde el
sistema operativo y empezando con uno nuevo vacío.

Esta sería una forma muy mala de trabajar con una base de datos de
una agenda. Para manejar datos se han definido cuatro operaciones
básicas: Altas, bajas, modificaciones y consultas, además de la
elaboración de listados y la compactación del fichero. En estos
apartados veremos unas breves (muy breves) indicaciones de como
conseguir que nuestros programas de QBasic puedan hacer esto con
un fichero de acceso directo.

1.16.3.2 - ALTAS. AÑADIR REGISTROS

En nuestro ejemplo de la sección anterior veíamos como añadir


registros a un archivo directo con la instrucción PUT. Lo hacíamos muy
bien, solo que siempre empezábamos por la primera posición y de esta
forma cada vez que ejecutamos el programa sobreescribíamos datos y
perdíamos información.

La idea de todo esto es conseguir poder añadir información al fichero


cada vez que ejecutemos el programa, en este caso pediremos al
usuario que escriba la posición (campo clave principal) donde quiere
guardar el nuevo registro.

Veamos como ejemplo un procedimiento SUB que pide al usuario el


número de registro, el nombre y la edad de la persona y los añade al
final del fichero. Suponemos que la estructura de TIPOPERSONA está
definida en el programa principal y que el fichero también está ya
abierto y tiene el descriptor #1.

'Procedimiento ALTA
'Añade un nuevo registro a agenda.dat
SUB Alta()
DIM nuevaPersona AS TIPOPERSONA
DIM registro AS INTEGER
INPUT "Número de registro: ", registro
INPUT "Nombre: ", nuevaPersona.nombre
INPUT "Edad: ", nuevaPersona.edad
PUT #1, registro, nuevaPersona
END SUB

Si es la primera vez que usamos el programa y el fichero no existe, se


creará uno nuevo. Los datos que hemos introducido se grabarán en la
posición que indiquemos como número de registro o campo clave.

Si esta posición de registro ya estaba ocupada por otros datos, la


información se sobreescribirá y se perderá la anterior. Si es una
posición vacía más allá del final del archivo, el tamaño crecerá
creandose registros hasta alcanzar la posición especificada donde se
grabará nuestra información. El contenido de estos registros
intermedios es indeterminado, normalmente restos de datos
previamente descartados existentes en esa posición del disco o en
memoria, y no lo podemos usar para nada. Este comportamiento de
que el fichero vaya creciendo hace que si usamos como campo clave
un número muy grande como un DNI, el fichero aumente de tamaño
considerablemente.

Otros algoritmos más elaborados nos avisarán si el campo clave ya


está ocupado con información, nos mostrarán la información que
contiene para darnos la posibilidad de sobreescribirla o de elegir otro
campo clave, etc...
1.16.3.3 - CONSULTAS.
BÚSQUEDA SECUENCIAL DE REGISTROS

Una vez que tenemos información en el registro nos puede interesar


buscar un nombre, por ejemplo, y que nos salga en la pantalla los
otros datos de la persona, en nuestro ejemplo sólo la edad.

Para hacer esto hay complicados mecanismos de búsqueda muy rápida


suponiendo que nuestro fichero esté ordenado. Como no es el caso, la
única forma de encontrar algo es ir mirando registro por registro hasta
que encontremos lo que buscamos o se acabe el fichero.

Vamos con un ejemplo que pregunta al usuario el nombre de la


persona que quiere buscar y recorre el fichero hasta encontrarla o
hasta que se acaba. Suponemos que ya está abierto el archivo y
definido el tipo de datos registro, igual que en los ejemplos anteriores.

numregs = (LOF(1) / LEN(persona))


INPUT "Nombre a buscar: ", nombreBuscado$
n = 1
DO
GET #1, n, persona
n = n + 1
LOOP UNTIL RTRIM$(persona.nombre)=nombreBuscado$ OR
n>numregs
IF n <= numregS THEN
PRINT "Registro...:"; n-1
PRINT "Nombre.....: "; persona.nombre
PRINT "Edad.......:"; persona.edad
ELSE
PRINT "No se ha encontrado"
END IF

Lo primero que hacemos es calcular cuantos registros tiene el fichero


para ver asta donde podemos llegar. Después pedimos al usuario que
escriba el nombre que quiere encontrar y lo guardamos en una
variable del tipo adecuado, en este caso cadena.
En el siguiente bloque vamos recorriendo el fichero desde el primer
registro usando un contador hasta que encontremos el nombre
buscado o bien el contador llegue a superar el número de registros del
fichero. Para hacer la comparación usamos la función RTRIM$ que lo
que hace es quitar los espacios en blanco que hay a la derecha del
nombre grabado en el registro porque como al definir el registro
tenemos que usar cadenas de longitud fija los valores devueltos
siempre contienen espacios al final para rellenar la cadena.

Cuando salimos del bucle lo que hacemos es comprobar el contador


con un IF. Si se ha superado el límite es que no se ha encontrado, y en
caso contrario es que se ha encontrado y podemos mostrar en pantalla
todos los datos de la persona que siguen estando almacenados en la
variable de registro, ya que después no se ha leído nada más.
También incluimos el número del registro usando el contador menos
uno, ya que antes de salir del bucle se sumó uno.

Si hay dos personas con el mismo nombre este código sólo encontrará
la primera. Podemos hacer consultas por otros campos, o bien
combinar varios campos con solo modificar la condición del bloque
repetitivo que usamos para recorrer el archivo. Si queremos poder
mostrar más de un registro encontrado habría que ir guardando los
datos encontrados en un array del mismo tipo para después
mostrarlos en una lista. Habría que limitar el número de registros
mostrados al tamaño del array, igual que hace Windows 9x con las
búsquedas de archivos y carpetas.

1.16.3.4 - MODIFICACIONES DE REGISTROS

Al trabajar con ficheros no nos podemos limitar a permitirle al usuario


añadir datos, también hay que dejarle hacer modificaciones, ya que la
información puede cambiar o el usuario se puede equivocar al escribir
los datos y tendrá que corregirlos. La forma de hacer una modificación
consiste en sobreescribir un registro completo del fichero con la nueva
información.
Una forma muy fácil de hacer esto es preguntar al usuario qué registro
quiere modificar, mostrarle los datos a ver si de verdad son estos y a
continuación pedirle la nueva información y grabarla en esa posición
del fichero sobreescribiendo a la anterior.

Vamos a ver como sería (Archivos ya abiertos y tipos ya definidos):

numregS = (LOF(1) / LEN(persona))


INPUT "Número de registro a modificar: ", nummodif%
IF nummodif% < 1 OR nummodif% > numregS THEN
PRINT "Número de registro incorrecto"
ELSE
GET #1, nummodif%, persona
PRINT "Nombre.....: "; persona.nombre
PRINT "Edad.......:"; persona.edad
PRINT "¿Es este el registro que quieres modificar?
(S/N)"
WHILE INKEY$ <> "": WEND 'Para limpiar INKEY$
DO
tecla$ = UCASE$(INKEY$)
LOOP UNTIL tecla$ = "S" OR tecla$ = "N"
IF tecla$ = "S" THEN
PRINT "Escribe los nuevos datos..."
INPUT "Nombre: ", persona.nombre
INPUT "Edad: ", persona.edad
PUT #1, nummodif%, persona
PRINT "Los nuevos datos han sustituido a los
anteriores"
END IF
END IF

Lo primero es pedir al usuario que escriba el número del registro, este


número lo habrá averiguado en una consulta que haya hecho antes,
por ejemplo. El número que escriba lo guardamos en una variable de
tipo entero, ya que los registros nunca van a opder llevar decimales. A
continuación comprobamos que el registro existe en el fichero, su
número es mayor que 0 y no mayor que el número máximo de
registros que hemos calculado al principio. Sólo si es así seguimos, si
no damos un mensaje de error y terminamos.

Si el registro existe lo mostramos en la pantalla y le preguntamos si es


este el que quiere modificar. Si pulsa la S mayúscula le pedimos los
nuevos datos y los grabamos en el fichero en la misma posición
soreescribiendo los datos antiguos.

Al hacer modificaciones el tamaño del fichero no cambia.

El uso de la función INKEY$ para hacer preguntas tipo Sí o No al


usuario lo veremos detalladamente en uno de los temas de ampliación
del curso.

1.16.3.5 - BAJAS. BORRAR REGISTROS

En este modelo de programación de ficheros de acceso aleatorio no


existe una forma específica de borrar información de un fichero, por
esto lo que hacemos es simplemente sobreescribir el registro
rellenándolo con espacios en blanco o valores cero, según
corresponda, para que la información no aparezca en las consultas ni
en los listados.

Vamos con un ejemplo de como hacerlo, casi igual al de las


modificaciones.

numregS = (LOF(1) / LEN(persona))


INPUT "Número de registro a borrar: ", numborra%
IF numborra% < 1 OR numborra% > numregS THEN
PRINT "Número de registro incorrecto"
ELSE
GET #1, numborra%, persona
PRINT "Nombre.....: "; persona.nombre
PRINT "Edad.......:"; persona.edad
PRINT "¿Es este el registro que quieres borrar? (S/N)"
WHILE INKEY$ <> "": WEND 'Para limpiar INKEY$
DO
tecla$ = UCASE$(INKEY$)
LOOP UNTIL tecla$ = "S" OR tecla$ = "N"
IF tecla$ = "S" THEN
persona.nombre = SPACE$(LEN(persona.nombre))
persona.edad = 0
PUT #1, numborra%, persona
PRINT "Registro borrado"
END IF
END IF

Como se puede ver, lo único que cambia es que en los mensajes en


vez de decir "modificar" dice "borrar" y que sobreescribimos el registro
con blancos y ceros en vez de con la información que escribe el
usuario.

Lo único más extraño es que para asignar espacios en blanco al


nombre en vez de hacer

persona.nombre = " "

que sería lo más fácil, usamos la función SPACE$ que nos devuelve
una cadena de espacios en blanco, y para calcular cuantos espacios
usamos la función LEN que nos devuelve el tamaño del dato miembro.
De esta forma no nos tenemos que acordar de cuanto largo era ese
dato y si modificamos este tamaño durante el desarrollo del programa
no tenemos que modificar esta instrucción, evitando posibles errores.

Visto esto se puede comprender que el tamaño de los ficheros directos


nunca se va a reducir, ni aunque borremos registros. Para solucionar
este problema lo que podríamos hacer en algunos casos será
compactar el fichero, en el apartado siguiente.

1.16.3.6 - COMPACTACIÓN DE FICHEROS DIRECTOS

Como hemos visto, al borrar registros el tamaño del fichero nunca se


reduce, con lo que nuestro fichero va a crecer y crecer siempre
pudiendo contener muchos registros vacíos que lo único que hacen es
ocupar sitio y hacer que todo vaya un poco más lento.

Para solucionar este problema en las bases de datos se ha definido


una tarea de mantenimiento que consiste en "compactar" los ficheros.
No tiene nada que ver con lo que hacen programas como WinZip, esto
es "comprimir" ficheros y la forma de programar estas operaciones es
terriblemente más complicada.
Este proceso de compactación lo que va a hacer es agrupar los
registros todos juntos al principios del archivo. Esto implica
que se van a modificar los campos calve ya que cambiará la
posición donde está almacenado cada registro. Si el campo
clave es algo significativo como por ejemplo el número de socio
de un club, número de habitación de un hotel, etc... no
deberemos hacer este proceso de compactación ya que
cambiarán los campos claves y destruiremos la información.
Solo lo deberemos hacer en casos donde el campo clave no sea
significativo para nosotros y no importe que cambie.

Imaginemos que en la vida real tenemos una agenda de papel llena de


datos de personas de los cuales unos sirven y otros están tachados
porque son incorrectos o se han quedado obsoletos. Además de llevar
siempre un tocho de hojas muy gordo, a la hora de buscar algo
tendremos que pasar siempre muchas hojas, perdiendo más tiempo
del necesario. Una forma muy cómoda de solucionar este problema
sería conseguir una agenda nueva para quedarnos sólo con los datos
que queremos conservar. Para hacer esto recorreríamos la agenda
vieja y solo iríamos copiando en la nueva los datos que no están
tachados. Al final tiramos la vieja y nos quedamos con la nueva para
seguir usándola a partir de ahora.

Este trozo de código hace justamente eso mismo con nuestro fichero
informático de la base de datos. Suponemos que el fichero de nuestra
agenda ya esta abierto y los tipos de datos definidos.

numregs = (LOF(1) / LEN(registro))


OPEN "Agenda.tmp" FOR RANDOM AS #2 LEN = LEN(registro)
n1 = 1
n2 = 1
WHILE n1 <= numregS
GET #1, n1, registro
IF registro.nombre <> SPACE$(LEN(registro.nombre)) OR
registro.edad <> 0 THEN
PUT #2, n2, registro
n2 = n2 + 1
END IF
n1 = n1 + 1
WEND

CLOSE
KILL "Agenda.dat"
NAME "Agenda.tmp" AS "Agenda.dat"

OPEN "Agenda.dat" FOR RANDOM AS #1 LEN = LEN(registro)


'Seguimos con el programa...

Vamos a ver como funciona esto:

 Lo primero que hacemos, como siempre, es calcular el


número de registros que tiene el fichero para saber hasta
donde podemos llegar.
 A continuación abrimos un fichero llamado "Agenda.tmp" (La
extensión .tmp significa "temporal") y le asignamos el
descriptor #2. Este fichero debe ser abierto con la misma
estructura que el otro y suponemos que no existe y se crea
uno nuevo y vacío al abrirlo.
 Antes de entrar en el bucle inicializamos dos contadores al
valor 1, uno nos va a servir para recorrer el fichero viejo y
otro para recorrer el nuevo. Empezamos por 1 que es el
primer registro.
 Ahora usamos un bucle "Mientras" para recorrer el fichero
viejo desde el principio hasta el final. Dentro de este bucle
leemos cada registro y si no está completamente vacío lo
copiamos al nuevo fichero e incrementamos ambos
contadores. Si está vacío no hacemos nada y solo
incrementamos el primer contador para poder seguir
recorriendo el fichero viejo.
 Para comprobar si el registro está completamente vacío
habrá que usar una expresión condicional que puede llegar a
ser muy larga porque hay que comprobar cada dato
miembro. En nuestro ejemplo esta expresión aparece
ocupando dos líneas, pero si la escribes en QBasic tendrá
que ser toda seguida en la misma linea, aunque sea muy
larga. QBasic soporta líneas de hasta 256 caracteres de
larga, si sigue sin caber tendrás que cambiar los nombres de
las variables por otros más cortos, aunque en esta ocasión
sean menos descriptivos.
 Una vez que salimos del bucle tendremos el fichero
"Agenda.dat" tal como estaba y el nuevo "Agenda.tmp"
compactado solo con los datos que nos interesan.
 Para proceder al intercambio de los nombres de los ficheros
y quedrnos solo con el nuevo podemos seguir estos pasos
o Cerrar los dos ficheros, usando la orden CLOSE sin
descriptor.
o Borrar "Agenda.dat" usando la orden KILL, que
veremos con detalle más adelante.
o Cambiarle el nombre a "Agenda.tmp" por "Agenda.dat"
usando la orden NAME, que también veremos en
próximos temas.
o Volver a abrir "Agenda.dat" con el descriptor #1 para
seguir trabajando con él normalmente desde el resto
del programa.

Esta operación de mantenimiento la tendrá que ejecutar el usuario de


vez en cuando, especialmente después de haber borrado muchos
registros. En programas mucho más sofisticados se podría hacer que
se ejecute cada x días o tras haber borrado un determinado número
de registros.

1.16.3.7 - ELABORACIÓN DE LISTADOS DE DATOS

Los listados son una parte muy útil de todo programa que trabaje con
datos, para que el usuario pueda ver toda la información a la vez y no
registro por registro. En los programas modernos se suele hablar de
"informes" para referirse a listados con un determinado formato que
presentan los registros que cumplen cierta condición.

Aquí nos limitaremos a escribir en la pantalla un listado encolumnado


de todos los registros que haya en nuestro fichero. La forma de dibujar
un listado en la pantalla es bastante sencilla en principio. Bastaría con
recorrer el fichero y escribir cada registro en una línea de la pantalla,
pero surgen algunos problemas que vamos a ir solucionando más
adelante.

Vamos con un ejemplo, de lo más cutre, que recorre el fichero y


escribe su contenido línea por línea todo seguido. Suponemos que ya
tenemos abierto nuestro fichero y definido el tipo registro, igual que en
los ejemplos anteriores.

CLS
numregs=LOF(1) - LEN(persona)
c=1
WHILE c <= numregs
GET #1, c, persona
PRINT c; persona.nombre; persona.edad
c=c+1
WEND

El resultado por pantalla podría ser:

1Pepe 25
2 0
3 0
4Manolo García García32
5Paco 19
6 0
7 0
8 0
9Ana 40
10Juan Carlos 28
11Fernando 21

Lo primero, como siempre, calcular cuantos registros tiene el fichero y


después irlos leyendo y sacando por pantalla desde dentro de un
bloque MIENTRAS. Se podía haber hecho con un PARA, pero como
después vamos a tener que hacerlo con un MIENTRAS de todas
formas, mejor empezar así.

El listado aparece más o menos encolumnado porque las variables de


cadena (nombre) se devuelven con espacios en blanco detrás hasta
completar su longitud total, pero los enteros no ocuparían siempre lo
mismo y nos estropearían el encolumnado, cosa que pasaría a partir
del registro 10 donde el nombre sería empujado un lugar hacia la
derecha. Más adelante iremos solucionando esto.

Como se puede ver en el resultado del ejemplo anterior, han aparecido


también los registros borrados (Sin nombre y con edad=0). Para evitar
esto lo que hacemos comprobar con una condición justo antes de
escribir cada línea que el registro no ha sido borrado. Sería así:

CLS
numregs=LOF(1) - LEN(persona)
c=1
WHILE c <= numregs
GET #1, c, persona
IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR
persona.edad>0 THEN
PRINT c; persona.nombre; persona.edad
END IF
c=c+1
WEND

Y el resultado podría ser:

1Pepe 25
4Manolo García García32
5Paco 19
9Ana 40
10Juan Carlos 28
11Fernando 21

Vemos que ya no aparecen los registros que fueron borrados. La


condición que hacemos es que el nombre (texto) no sea todo espacios
o que la edad sea mayor que cero. Ahora sólo aparecen los registros
que nos interesan, pero todavía se ve muy feo vamos a dibujarle un
encabezado a la lista y dejar un poco de espacio entre las columnas
para que no quede tan junto.

CLS
numregs=LOF(1) - LEN(persona)
c=1
PRINT "Registro Nombre Edad"
PRINT "============ ==================== ===="
WHILE c <= numregs
GET #1, c, persona
IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR
persona.edad>0 THEN
PRINT c, persona.nombre; " "; persona.edad
END IF
c=c+1
WEND

Y el resultado, más bonito sería:

Registro Nombre Edad


============ ==================== ====
1 Pepe 25
4 Manolo García García 32
5 Paco 19
9 Ana 40
10 Juan Carlos 28
11 Fernando 21

Dibujamos el encabezado una vez al principio. La línea con guiones o


signos igual nos puede servir para orientarnos con los tamaños de los
campos. Dentro del bucle en la instrucción PRINT si separamos las
variables por comas se pasa a la siguiente posición de tabulación y nos
evitamos el problema del diferente número de cifras, aunque se
desperdicia mucho espacio porque las posiciones de tabulación an
QBasic miden 13 caracteres. Podemos meter espacios entre comillas
para separar los campos. En este caso como hay muy pocos campos y
hemos podido dejar los números enteros para el final ha salido medio
bien, pero en casos más complicados sería necesario usar la
instrucción TAB para definir las columnas. Veamos como se haría.

CLS
numregs=LOF(1) - LEN(persona)
c=1
PRINT "Reg"; TAB(5); "Nombre"; TAB(26); "Edad"
PRINT "==="; TAB(5); "===================="; TAB(26);
"===="
WHILE c <= numregs
GET #1, c, persona
IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR
persona.edad>0 THEN
PRINT c; TAB(5); persona.nombre; TAB(26);
persona.edad
END IF
c=c+1
WEND

Y el resultado, todavía mejor.


Reg Nombre Edad
=== ==================== ====
1 Pepe 25
4 Manolo García García 32
5 Paco 19
9 Ana 40
10 Juan Carlos 28
11 Fernando 21

El parámetro que le pasamos a la orden TAB debe ser un entero que


indica la posición a la que saltará el cursor dentro de la línea actual.
Recordemos que cada línea tiene 80 caracteres de ancho y que la
primera posición por la izquierda es la 1. La orden TAB se utiliza como
argumento de la orden PRINT, igual que si fuera una función, solo que
en vez de devolver un valor lo que hace es llevar al cursor hasta la
posición especificada. En el encabezado nos podíamos ahorrar las
ordenes TAB y poner directamente los espacios, porque como todo es
constante no va a variar nada. Para saber las posiciones lo más
cómodo es ir probando hasta que cada cosa caiga en su sitio, debajo
de su título.

Ya se empieza a parecer a un listado. Siempre habrá que ajustar las


columnas según nuestras necesidades y si no caben de ancho la única
solución posible con lo que sabemos hasta ahora es no escribir todos
los campos.

Lo último que vamos a solucionar es el terrible problema de que si


tenemos muchos registros el listado avanzará rápidamente y solo
veremos los últimos veintitantos registros que caben en la pantalla.
Pasa lo mismo que con la orden DIR de MS-DOS si no ponemos el
modificador /P. Para conseguir esto lo que hacemos es usar un
segundo contador que vamos incrementando en cada línea y cuanto
veamos que llega a 20 lo ponemos a cero, y hacemos una pausa,
cuando el usuario pulse una tecla borramos la pantalla, dibujamos el
encabezado de nuevo y seguimos. Vamos con el ejemplo:

CLS
numregs=LOF(1) - LEN(persona)
c=1
c2=1
PRINT "Reg Nombre Edad"
PRINT "=== ==================== ===="
WHILE c <= numregs
GET #1, c, persona
IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR
persona.edad>0 THEN
PRINT c; TAB(5); persona.nombre; TAB(26);
persona.edad
END IF
c=c+1
c2=c2+1
IF c2>20 THEN
c2=1
PRINT "Pulsa cualquier tecla para seguir..."
WHILE INKEY$<>"":WEND
DO:LOOP WHILE INKEY$=""
CLS
PRINT "Reg Nombre Edad"
PRINT "=== ==================== ===="
END IF
WEND

El resultado, si hay menos de 20 registros sería igual que antes, y si


hay más sería que se van viendo de 20 en 20.

Este ejemplo se puede mejorar para que las instrucciones donde se


dibuja el encabezado sólo aparezcan una vez, pero aquí no nos vamos
a complicar tanto. De todas formas en el tema de manejo de la
pantalla de texto que hay en la sección de temas de ampliación de
este curso aparen las instrucciones a usar para hacer listados
desplazables parecidos a los de Windows que son mucho más
"bonitos" y no tienen estos problemas.

1.16.4 - BREVE IDEA SOBRE BASES DE DATOS


En informática se conoce como base de datos a un mecanismo capaz
de almacenar información para después poder recuperarla y manejarla
con facilidad y precisión.
Visto así, los archivos de ejemplo de agendas que hemos creado en los
apartados anteriores son bases de datos, pero cuando hablamos de
bases de datos pensamos en programas que manejan gran cantidad
de información de forma muy estructurada y que permiten realizar
consultas y operaciones de mantenimiento muy complejas y fiables.

Al hablar de bases de datos hay que distinguir dos cosas muy


importantes. Por un lado tenemos la base de datos en sí, que es la
información grabada en los discos, y por otro lado tenemos el Sistema
de Gestión de Bases de Datos (SGBD o DBMS en inglés) que es el
programa encargado de manejar toda la información. Es lo que sería
un programa como MS Access o MySQL.

Las bases de datos que manejan estos programas casi nunca


contienen una sola tabla, es decir, un solo archivo de datos, sino que
contienen muchas tablas relacionadas entre si. Por eso se habla de
bases de datos relacionales. El hecho de que las tablas estén
relacionadas unas con otras implica que al hacer un cambio en una de
ellas, posiblemente se vean afectadas otras para que la información
siga teniendo consistencia. También estos sistemas de bases de datos
usan muchos mecanismos como índices o algoritmos de ordenación
para conseguir que el acceso a la información durante las consultas
sea un proceso mucho más rápido y completamente fiable.

Nosotros, en nuestros programas estamos integrando una


pequeñísima parte de los mecanismos que usan los SGBD para poder
manejar la información de nuestros archivos, pero conforme nuestras
necesidades aumenten y ya usemos otros lenguajes de programación,
será mucho más sencillo usar los servicios de un auténtico SGBD para
manejar nuestra información, en vez de programarlo nosotros. Esto
quiere decir que el uso de lo que hemos visto en este tema de archivos
de acceso directo se limitará a el mantenimiento de pequeños y
sencillos conjuntos de datos cuando sea más comodo manejarlos de
forma directa que de forma secuencial, pero desde el momento en que
intervienen varios archivos de datos necesitaremos usar las funciones
de uno de estos sofisticados SGBDs integrados en los modernos
lenguajes de programación.
1.16.5 - OTRAS INSTRUCCIONES
RELACIONADAS CON FICHEROS
Ya hemos visto como crear ficheros y leer o modificar la información
almacenada en los mismos usando diferentes métodos de
programación. Para terminar el tema de ficheros es necesario conocer
algunas instrucciones que tiene QBasic para trabajar con ficheros
enteros que haya en el sistema de archivos. Estas instrucciones nos
ayudarán a trabajar con los ficheros sin tener que recurrir a ejecutar
ordenes del sistema operativo.

Vamos con ellas.

CHDIR ruta$

Cambia el directorio activo. El directorio activo es el que usará QBasic


para buscar los archivos que tiene que abrir o para crearlos si no se
especifica una ruta en la instrucción OPEN. Puede ser difícil determinar
el directorio activo, por lo que conviene especificar uno con esta orden
antes de abrir o crear ficheros.

Si ejecutamos QBasic desde un icono de acceso directo de Windows el


directorio activo será el que aparece en el cuadro "Carpeta de trabajo"
de la sección "Programa" de las propiedades del acceso directo.

Si ejecutamos QBasic desde MS-DOS el directorio activo será donde


nos encontrábamos cuando entramos en QBasic.

La ruta que hay que especificar en esta instrucción puede ser un literal
entre comillas o una variable de cadena que contenga la ruta
adecuada. Si el directorio no existe se producirá un error de tiempo de
ejecución.

Hay que recordar que estamos en MS-DOS y por lo tanto solo


podemos trabajar con nombres de directorios y archivos de no más de
8 caracteres y sin espacios, por lo tanto los directorios "Archivos de
Programas" y "Mis documentos" tendrán nombres parecidos a
"ARCHIV~1" y "MISDOC~1". Para asegurarse de cuales son estos
nombres hay que mirar la casilla "Nombre MS-DOS" de la ventana de
propiedades del directorio correspondiente en Windows.
Para escribir el carácter "Gurruño" (~) hay que pulsar la tecla
"Alternativa" y simultáneamente escribir con el teclado numérico la
cifra 126, correspondiente a su código ASCII.

La siguiente orden nos permite crear un directorio:

MKDIR ruta$

El directorio que creemos ya estará disponible en el sistema de


archivos para usarlo desde nuestro programa o desde cualquier otro.
Si la ruta que especificamos ya existe se producirá un error de tiempo
de ejecución.

Es importante comprender que al crear un directorio este no pasa a


ser el directorio activo. Para hacerlo habrá que usar una instrucción
CHDIR.

Ahora vamos a ver como borrar un directorio existente:

RMDIR ruta$

Esta instrucción borra el directorio que le pasemos como parámetro. Si


no existe se producirá un error. También se producirá el mismo error
si el directorio no está vacío, es decir, contiene algún archivo o más
subdirectorios. Habrá que borrarlos antes como veremos más
adelante. Normalmente debemos tener mucho cuidado con lo que
borramos desde nuestros programas para no hacer ningún estropicio
con la información que tenemos guardada en los discos.

Podemos ver los ficheros y subdirectorios de un directorio con la


siguiente instrucción:

FILES

Esta instrucción trabaja de forma parecida a la orden "DIR /W" de MS-


DOS. Nos dibujará un listado a cuatro columnas con el contenido de un
directorio, encabezada por el nombre del propio directorio y terminada
por el espacio en bytes disponible en el disco. Los subdirectorios se
identifican porque llevan a la derecha el rótulo "< DIR >".

Esta instrucción nos puede resultar algo útil en los programas que
hemos hecho hasta ahora ya que la información se escribe en pantalla
una línea debajo de otra, pero cuando hagamos diseños de pantallas
más elaborados ya no será recomendable usar esta instrucción porque
el listado de directorio se saltará todas nuestras especificaciones de
pantalla, incluso ahora si el listado es muy largo sólo se ven las
últimas líneas.

Si usamos la orden FILES sin parámetros se mostrarán todos los


ficheros y subdirectorios del directorio activo, pero podemos
especificar rutas, nombres de archivos y caracterescomodín (? y *)
para adecuar el listado a nuestras necesidades. Veamos unos cuantos
ejemplos:

FILES "*.txt"

Lista todos los ficheros con extensión TXT que haya en el directorio
activo.

FILES "JUEGO.*"

Lista todos los ficheros que se llamen JUEGO, sea cual sea su
extensión, del directorio activo.

FILES "C:\BASIC\PROGS\*.*"

Lista todos lo que haya en el directorio BASIC\PROGS de la unidad C:.

FILES "Imagen?.BMP"

Lista todos los archivos cuyo nombre empiece por "Imagen" y vaya
seguido por exactamente un carácter, su extensión sea BMP y stén en
el directorio activo. Aparecerían por ejemplo los archivos Imagen2.BMP
o Imagen7.BMP, pero nunca el archivo Imagen14.BMP ya que contiene
dos números.

Si la orden FILES no encuentra nada que listar se produce un error de


tiempo de ejecución. Si listamos un directorio vacío no se producirá
ningún error.

Ya hemos visto como borrar directorios, ahora vamos a ver como


borrar archivos:

KILL Archivo$
Esta instrucción borra archivos, podemos usar una ruta y/o los
comodines * y ?, cosa que puede ser peligrosa porque se borrarían
varios ficheros a la vez, una vez más recomendar prudencia al usar
esta orden para evitar estropicios irreparables, ya que en MS-DOS
normalmente no tenemos papelera de reciclaje.

Si no especificamos una ruta se entenderá que estamos trabajando


sobre el directorio activo. Si el archivo que queremos borrar no existe
se producirá un error de tiempo de ejecución.

No confundir esta instrucción con la orden KILL de Linux que detiene


procesos, es decir, obliga a terminar la ejecución de un programa pero
no borra nada del disco.

Por último vamos a ver como cambiar el nombre un fichero:

NAME viejo$ AS nuevo$

viejo$ es el nombre actual del archivo que queremos renombrar, que


debe existir para que no se produzcan errores.

nuevo$ tiene que ser el nuevo nombre que le vamos a dar al archivo,
y que no debe existir todavía.

Por ejemplo, para cambiar el nombre al archivo base.dat del directorio


actual por base.bak tendríamos que usar la instrucción

NAME base.dat AS base.bak

En el nombre del archivo ya existente (la primera parte antes de AS)


podemos especificar una ruta de directorios.

Para copiar o mover archivos no tenemos una instrucción específica en


QBasic. Tendremos que crearnos un modulo que lo abra en modo
binario y lo copie carácter por carácter en un archivo nuevo en la otra
ubicación, si lo que queríamos era moverlo después habrá que borrar
el original. Para hacer esto y otras cosas más complicadas puede ser
más cómodo llamar a la orden correspondiente de MS-DOS como se
verá en temas posteriores.
<a href="http://www.addfreestats.com" target="_blank">< img
src="http://www5.addfreestats.com/cgi-
bin/connect.cgi?usr=00503209Pauto" border=0
title="AddFreeStats.com Free Web Stats!"></a>

TEMA 1.17
CONTROL DE ERRORES
 1.17.1 - Instrucción GOTO. La más conocida y odiada
de BASIC
 1.17.2 - Introducción a los errores
 1.17.3 - Control de errores

1.17.1 - INSTRUCCIÓN GOTO.


LA MÁS CONOCIDA Y ODIADA DE BASIC
Empecemos con un ejemplo "muy malo":

CLS
Pricipio:
PRINT "Esto es el principio"
GOTO Final
Enmedio:
PRINT "Esto es lo de en medio, pero no lo verás"
Final:
PRINT "Esto es el Final"

Que daría como resultado en pantalla:


Esto es el principio
Esto es el final

Ahora veamos como funciona:

 Borra la pantalla.
 Pasa por una etiqueta definida como Principio.
 Escribe "Esto es el principio"
 Busca una etiqueta que se llame "Final" y cuando la
encuentra salta y sigue a partir de allí.
 Escribe "Esto es el final".
 Como no hay nada más, el programa termina.

En este programa hemos definido tres etiquetas. Para definir etiquetas


usamos las mismas normas que para los nombres de variables y al
final le colocamos el carácter dos puntos (:). Las etiquetas siempre
van al principio de la línea y si usamos etiquetas es útil encolumnar
todas las demás instrucciones más a la derecha, para verlas al vuelo.

Podemos definir etiquetas en cualquier módulo de nuestro programa,


pero QBasic solo las "encontrará" si están definidas en el módulo
principal. Por esto no tiene sentido definir etiquetas dentro de
procedimientos y funciones.

Las etiquetas no hacen nada, simplemente están ahí. Únicamente nos


sirven para poder "Saltar" a ellas usando instrucciones especiales
como la famosa GOTO (Go to..., ir a...) que salta incondicional e
inmediatamente a la etiqueta que le indiquemos para seguir desde allí
con la ejecución del programa.

Sencillo ¿No?. En verdad es muy sencillo pero puede dar lugar a toda
una variedad de problemas lógicos que pueden ser casi imposibles de
detectar, aislar y solucionar. Imagina un programa donde en vez de
tres etiquetas haya trescientas y entre ellas haya centenares de
instrucciones GOTO que dirigen la ejecución del programa a un sitio
lejano de nuestro listado. Una mala planificación del programa
seguramente hará que bloques enteros no se lleguen a ejecutar nunca
o que se produzcan bucles infinitos que lleguen a bloquear el
programa. Por eso a los programas que usan estas instrucciones se les
conoce como "Código Espagueti".
En los lenguajes de bajo nivel como es el caso de los ensambladores y
en lenguajes de guiones muy sencillos como el que se utiliza para
construir los ficheros .BAT de MS-DOS se usan intensivamente las
etiquetas y las instrucciones de bifurcación o salto incondicional, pero
los modernos lenguajes de programación estructurada (QBasic lo es)
nos dan la posibilidad de usar estructuras de control iterativas (Bucles
PARA, MIENTRAS y REPETIR), así como los procedimientos y las
funciones que nos permiten en todo caso saber como funciona nuestro
programa para poder solucionar posibles errores. Por esto es muy
recomendable EVITAR A TODA COSTA EL USO DE ESTA
METODOLOGÍA DE PROGRAMACIÓN CON INSTRUCCIONES
GOTO Y SIMILARES en los lenguajes de alto nivel, salvo en el caso
de las instrucciones de manejo de errores que vamos a ver en los
siguientes apartados y que son el motivo de esta explicación.

1.17.2 - INTRODUCCIÓN A LOS ERRORES


Hemos ido viendo a lo largo de los temas anteriores que hay tres tipos
de errores:

 Errores de compilación
 Errores de tiempo de ejecución
 Errores lógicos

Los primeros suelen ser cosas mal escritas o que no cumplen con las
normas de sintaxis del lenguaje de programación. La mayoría de ellos
en QBasic serán mostrados conforme vamos escribiendo nuestro
código al pasar de línea, o en todo caso al intentar ejecutar el
programa. Tendremos que corregirlos modificando lo que este mal
para poder iniciar el programa.

Los errores de tiempo de ejecución se producen durante la ejecución


del programa y son provocados por intentar hacer algo que no está
permitido, como dividir entre cero, o bien porque alguno de los
dispositivos del ordenador falle (Memoria agotada, no se encuentra un
fichero, la impresora no tiene papel, etc...). Estos errores hacen que el
programa se detenga. En QBasic se volverá al editor y se mostrará un
recuadro con el mensaje apropiado. En otros lenguajes o en
programas ya compilados puede salir desde un simple mensaje hasta
una pantalla azul típica de Windows o lo más normal es que el
ordenador deje de responder y haya que reiniciarlo.

Los errores lógicos se deben a una mala planificación de los


algoritmos, lo que da lugar, en el mejor de los casos, a que el
programa funcione correctamente pero no haga lo que queremos y no
resuelva el problema para el que ha sido diseñado, o lo más normal es
que llegue a provocar un error de tiempo de ejecución porque se llene
el espacio disponible en memoria o se entre en un bucle infinito.

En este tema vamos a ver cómo conseguir que cuando se produzca un


error de tiempo de ejecución el programa no quede detenido
inmediatamente, sino que se salga de él de una forma un poco más
"elegante" o incluso se pueda solucionar el problema y seguir
normalmente con la ejecución del programa.

Usando las técnicas de programación estructurada que hemos visto en


este curso hasta antes del tema de ficheros si hacemos los programas
bien NO TIENEN PORQUÉ PRODUCIRSE ERRORES DE TIEMPO DE
EJECUCIÓN. Nosotros somos los responsables de evitar que los bucles
sean infinitos, de no hacer referencias a índices de arrays que no
existen, de que nada se llegue a dividir entre cero, etc... La única
fuente potencial de errores son los datos que pedimos al usuario, pero
si depuramos los datos de entrada de forma que no se acepten valores
no permitidos como vimos en el tema de bucles podemos asegurar en
la amplia mayoría de los casos que nuestro programa va a funcionar
bien siempre, ya que solo acepta datos válidos.

El problema llega cuando empezamos a trabajar con dispositivos


externos como son las unidades de disco donde se guardan los
ficheros. Por muy bien hecho que esté nuestro programa no podemos
evitar que el usuario quiera abrir un fichero que no existe, o de un
disquete que todavía no ha introducido en la unidad correspondiente, o
que se ha estropeado, que quiera escribir en un CD-ROM, en un disco
que no tiene espacio suficiente o que está protegido contra escrituea.
Sólo en este caso de los ficheros y en otros también especiales como
cuando veamos cómo usar las impresoras será necesario y
recomendable programar rutinas de manejo de errores de las que
vamos a ver en el siguiente apartado. En otros casos no nos merece la
pena usar esto ya que las instrucciones GOTO ofrecen una forma
especialmente mala de estructurar los programas y podemos
despreciar la posibilidad de que se produzca algún error raro como que
se agote la memoria por culpa de otro programa y el nuestro no pueda
seguir funcionando.

1.17.3 - CONTROL DE ERRORES


Para activar el control de errores en QBasic usaremos la siguiente
instrucción:

ON ERROR GOTO linea

Dónde linea es el nombre de una etiqueta que se haya definido en el


programa principal.
Esto quiere decir "Cuando se produzca un error ve inmediatamente a
la etiqueta que se llama linea y sigue desde allí".
De esta forma conseguimos que el programa no se detenga
inmediatamente al producirse un error.

Como hemos dicho antes, solo debemos controlar los errores en


instrucciones peligrosas como es el caso de las que abren los ficheros
(OPEN). Una ver pasadas estas instrucciones debemos desactivar el
manejo de errores usando la instrucción:

ON ERROR GOTO 0

Lo último de esta instrucción es un cero. A partir de ahora cuando se


produzca un error ya no habrá manejo de errores y el programa se
detendrá, pero si lo hemos hecho bien no tienen porqué producirse
errores.

Lo normal es activar el manejo de errores al principio de un


procedimiento que, por ejemplo, abra un fichero y desactivarlo al final,
antes de salir. De esta forma tenemos claramente delimitado donde
usamos el manejo de errores, que estará activo solo mientras se
ejecute ese módulo, y no en el resto del programa que no usa
instrucciones "conflictivas".

Vamos con un ejemplo completo:

'Programa principal
CLS
INPUT "Nombre del fichero: ", fich$
MuestraTamanno fich$

END

manejaerrores:

SELECT CASE ERR


CASE 53
PRINT "No se ha encontrado el archivo"
CASE 68
PRINT "No se ha encontrado la unidad de disco"
CASE 71
PRINT "No se ha encontrado el disco en la unidad"
CASE 76
PRINT "No se ha encontrado el directorio"
CASE ELSE
PRINT "Se ha producido el error"; ERR
END SELECT

END

'Este procedimiento abre el archivo, muestra el tamaño y


cierra
'Entrada: f$: El nombre del archivo
SUB MuestraTamanno (f$)
ON ERROR GOTO manejaerrores
OPEN f$ FOR INPUT AS #1
PRINT "El fichero ocupa"; LOF(1); "bytes."
CLOSE
ON ERROR GOTO 0
END SUB

Este programa pide al usuario que escriba el nombre de un fichero


para abrirlo y mostrar su tamaño usando la función LOF que ya
conocemos.
Si escribimos un nombre de archivo válido no se activa para nada el
manejo de errores y el programa funciona normalmente, nos calcula el
tamaño en bytes del archivo y termina al llegar a la instrucción END
que hay justo después de la llamada al procedimiento. El resultado
podría ser:

Nombre del fichero: c:\autoexec.bat


El fichero ocupa 219 bytes.

Veamos como funciona el programa paso a paso en caso de que el


fichero exista y no se produzca el error:

 Entramos al módulo principal.


 Borramos la pantalla con CLS.
 Pedimos al usuario que escriba el nombre de un fichero y lo
guardamos en la variable fich$:
 Entramos al procedimiento Muestratamanno y le pasamos la
variable fich$ que contiene el nombre que ha escrito el
usuario.
 Ya dentro del procedimiento, activamos el control de errores.
 Abrimos el fichero, que existe, para lectura y le asignamos el
descriptor #1
 Calculamos el tamaño del fichero abierto #1 con la función
LOF y lo escribimos en pantalla junto con un mensaje.
 Cerramos todos los ficheros abiertos (El único que hay).
 Desactivamos el control de errores y salimos del módulo
porque no hay más instrucciones.
 De vuelta al programa principal la siguiente instrucción que
encontramos es END que hace que el programa termine, la
usamos para que no se ejecuten las siguientes instrucciones
que son las del control de errores.

Aquí encontramos como novedad la instrucción END que hace que el


programa termine de forma normal aunque no hayamos llegado al
final del listado. La tendremos que poner siempre en el módulo
principal antes de la etiqueta del manejador de errores para evitar que
estas instrucciones se ejecuten en caso normal de que no haya un
error. En otros casos no es recomendable usar esta instrucción, los
programas y módulos deben empezar por el principio y terminar por el
final del listado.
Ahora vamos a ver como funcionaría el programa paso a paso en el
caso de que quisiéramos abrir un fichero que no existe y se produjera
el error.

 Entramos al módulo principal.


 Borramos la pantalla con CLS.
 Pedimos al usuario que escriba el nombre de un fichero y lo
guardamos en la variable fich$:
 Entramos al procedimiento Muestratamanno y le pasamos la
variable fich$ que contiene el nombre que ha escrito el
usuario.
 Ya dentro del procedimiento, activamos el control de errores.
 Intentamos abrir el fichero como no existe se produciría un
error de tiempo de ejecución que es detectado por el control
de errores que continúa inmediatamente con la ejecución del
programa a partir de la línea definida como manejaerrores
en el programa principal.
 Después de esta línea encontramos un SELECT CASE que
usa la función ERR. Esta función devuelve el número del
error que se ha producido. Cada error tiene un número único
que nos sirve para identificarlo y hacer una cosa u otra
según lo que haya ocurrido. En este caso lo usaremos para
sacar un mensaje adecuado diciendo al usuario lo que ha
pasado.
 Después del SELECT CASE, el programa termina, pero lo
hace normalmente porque se ha acabado el listado, y no
porque se ha producido un error, cosa que hay que evitar a
toda costa.

Hay que tener claro que al producirse el error el fichero no se ha


abierto, y por lo tanto el descriptor #1 no contiene referencia a ningún
fichero, por lo que no podemos intentar hacer nada con él, ni siquiera
cerrar el fichero.

Hay un gran número de códigos de error, puedes ver una lista


completa de ellos en la ayuda en pantalla de Qbasic. Nunca es
necesario controlarlos todos, por ejemplo si estamos trabajando con
ficheros solo pondremos algunos de los relacionados con ficheros.
Siempre es conveniente usar algo como CASE ELSE para que quede
controlado alguno que se nos haya podido escapar, en este caso
bastará con mostrar el número por pantalla para poder identificar el
error de alguna forma, pero siempre haciendo que el programa
termine bien.

Se pueden hacer manejos de errores mucho más elaborados que


lleguen a solucionar el problema y el programa pueda seguir
funcionando como si nada. Imagina que queremos abrir un fichero .INI
dónde está la configuración del programa, y este se ha borrado. En
este caso de forma totalmente transparente al usuario podríamos crear
uno nuevo vacío o con valores por defecto y dejarlo abierto para que el
programa siga trabajando con él normalmente. En este caso usaríamos
la instrucción RESUME para continuar con el programa por la línea
siguiente a la que produjo el error, una vez que ya hayamos abierto el
nuevo fichero para escritura, hayamos escrito en él lo que sea, lo
hayamos cerrado y lo hayamos vuelto a abrir para lectura, todo esto
en el manejador de errores.

<a href="http://www.addfreestats.com" target="_blank">< img


src="http://www5.addfreestats.com/cgi-
bin/connect.cgi?usr=00503209Pauto" border=0
title="AddFreeStats.com Free Web Stats!"></a>

TEMA 1.18
LLAMADA A PROGRAMAS EXTERNOS
Lo último que nos queda por ver en la sección de temas básicos del
curso de introducción a la programación es cómo conseguir que
nuestro programa sea capaz de iniciar otros programas externos, ya
sean ejecutables .EXE u ordenes de MS-DOS internas o externas o
bien otros programas de QBasic.

Para conseguir lo primero usaremos la instrucción SHELL seguida de la


orden de MS-DOS o del programa ejecutable o programa BAT que
queremos ejecutar. Vamos con unos cuantos ejemplos:

SHELL "dir"
Hace que en la pantalla de nuestro programa aparezca el listado del
contenido del directorio activo.

SHELL "dir c:\basic\juegos"

Hace que en la pantalla de nuestro programa aparezca el listado del


contenido del directorio c:\basic\juegos. Si este directorio no existe no
se producirá ningún error en nuestro programa, simplemente la orden
DIR nos sacará un mensaje diciendo que no se ha encontrado el
achivo.

SHELL "lote.bat"

Ejecuta el archivo de proceso por lotes llamado lote.bat. Lo buscará en


el directorio activo y si no lo encuentra lo buscará en los directorios
que aparecen en la variable de entorno PATH del MS-DOS. Si no lo
encuentra sacará el típico mensaje "Comando o nombre de archivo
incorrecto", pero en nuestro programa no ocurrirá ningún error.

SHELL "winmine"

En la mayoría de los casos, iniciará el buscaminas de Windows, un


programa ejecutable. Si no lo encuentra pasará lo mismo que en el
ejemplo anterior, pero tampoco generará ningún error.

SHELL

Si no escribimos ningún argumento, se abrirá una instancia de MS-


DOS dentro de nuestro programa de QBasic. Aquí el usuario podrá
ejecutar órdenes como se hace normalmente. Para volver al programa
tendrá que escribir EXIT y pulsar Enter. Habrá que advertir esto al
usuario antes de entrar para que no se quede "encerrado" si no sabe
como salir. También es peligroso ejecutar determinados programas ya
que en estos entornos los recursos de memoria son muy limitados.

Esta instrucción SHELL nos puede servir para ejecutar ordenes de MS-
DOS o pequeños programas que nos pueden hacer falta usar.
Si ejecutamos algo de MS-DOS, que no es un sistema multitarea,
nuestro programa quedará detenido hasta que este programa termine.
En el caso que llamemos a algún programa Windows, como el caso del
buscaminas, nuestro programa lo iniciará y seguirá funcionando.
Normalmente no será necesario llamar a programas externos, salvo en
situaciones muy específicas, y siempre deberán ser programas
sencillos que consuman muy pocos recursos, si llamamos a algún
programa muy grande como Windows desde QBasic y fallan los
sistemas de seguridad puede que el ordenador se bloquee.

También nos puede interesar que nuestro programa termine y pase el


control a otro programa de QBasic, por ejemplo en el caso de que sea
un menú. Para hacerlo haríamos esto.

RUN "Programa.bas"

Al ejecutar esta orden nuestro programa termina inmediatamente y


QBasic carga e inicia el otro programa basic. Esta orden equivale a que
nosotros detengamos nuestro programa inmediatamente, en el editor
abramos el otro programa y lo ejecutemos pulsando F5.
Si al usar la orden RUN todavía no habíamos guardado los cambios en
el código fuente aparecerá un mensaje del editor pidiéndonos que lo
hagamos, esto no es un error, cuando lo hagamos se continuará con el
otro programa.
Si el programa no existe se producirá un error de tiempo de ejecución.
Si el programa no es un programa basic se producirá un error de
compilación al intentar iniciarlo, nuestro programa anterior para
entonces ya habrá terminado.

Al pasar de un programa a otro se cierran los ficheros que hubiera


abiertos y desaparecen todas las variables. En algunos casos, todavía
más rebuscados, nos puede interesar seguir trabajando con los
ficheros abiertos y con las variables globales que fueron declaradas
con la instrucción COMMON. Para conseguir esto en vez de usar la
instrucción RUN usaremos la instrucción CHAIN, que trabaja de forma
idéntica, excepto en que conserva los ficheros abiertos y reconoce las
variables declaradas con COMMON en programas anteriores.

Normalmente una buena jerarquía de procedimientos SUB será más


recomendable y más estructurada que usar la orden RUN o CHAIN.
Estas ordenes solo las deberemos usar en casos muy sencillos como
un pequeño menú que nos abra varios programas. La orden CHAIN no
la deberemos usar casi nunca y sobre todo no encadenar varios
programas ya que perderemos completamente la pista de dónde
declaramos las variables y abrimos los archivos, cosa que puede dar
lugar a errores muy difíciles de solucionar, como ocurre siempre que
no estructuramos bien los programas.

TEMA 2.1
DETECCIÓN DE TECLAS PULSADAS
CON LA FUNCIÓN INKEY$
 2.1.1 - Uso de la función INKEY$
 2.1.2 - Reconocimiento de teclas especiales
 2.1.3 - Hacer pausas en los programas

2.1.1 - USO DE LA FUNCIÓN INKEY$


La función INKEY$ nos devuelve el contenido del buffer del teclado, es
decir, cual es la última tecla o combinación de teclas que se ha
pulsado. Esta función es más primitiva que INPUT, pero no la pudimos
ver en el tema de entrada y salida básica porque todavía no sabíamos
cómo usar los bucles, algo imprescindible para usar esta función ya
que el programa no se detiene como ocurre con la instrucción INPUT.
También hay que tener en cuenta que no es algo básico de la teoría de
programación, ya que en otros lenguajes puede que funcione de
formas muy distintas.

Para usar la función lo que haremos es almacenar su valor en una


variable, ya que al leer el valor de INKEY$ el carácter que contenga se
borra del buffer de teclado y ya no lo veremos más.
Vamos con el primer ejemplo:

CLS
tecla$ = INKEY$
PRINT tecla$
Este ejemplo funciona, aunque al ejecutarlo no lo parezca. Lo que hace
es borrar la pantalla, almacenar la última tecla pulsada (durante el
tiempo de vida del programa) en la variable tecla$ y a continuación
escribirla en pantalla. Lo que pasa es que como el programa no se
detiene no nos da tiempo a pulsar ninguna tecla y no aparece nada en
pantalla, salvo el conocido rótulo "Presione cualquier tecla y continúe".

Para solucionar esto tenemos que recurrir a los bucles y programar lo


que se conoce como "mecanismo de espera activa". Allá vamos, el
siguiente fragmento de código será de los más usados en juegos y
programas interactivos hechos con QBasic:

DO
tecla$=INKEY$
LOOP WHILE tecla$=""

Al hacer esto se entra en un bucle que lee continuamente la entrada


de teclado y la almacena en la variable tecla$. No salimos del bucle
mientras no pulsemos nada y la variable tecla$ siga estando vacía.
Una vez que pulsemos una tecla esta quedará almacenada en la
variable para usarla más adenante donde haga falta.

Esto ya funciona bien, pero todavía no es infalible. Puede ocurrir que


en el buffer del teclado quede alguna pulsación de tecla residual de
alguna instrucción de entrada anterior y que al llegar aquí se salte la
espera. Para que esto no ocurra tendremos que "limpiar el buffer del
teclado" antes de entrar al bucle. Para hacerlo usaremos otro bucle
que lee las posibles teclas (y las va borrando) del buffer hasta que se
quede vacío. Vamos a ver un ejemplo completo que espera a que
pulsemos una tecla y la saca por pantalla. El primer bucle, de tipo
WHILE, corresponde a la limpieza del buffer y el segundo, de tipo
DO..LOOP, a la espera activa.

CLS
WHILE INKEY$<>""
WEND
DO
tecla$=INKEY$
LOOP WHILE INKEY$=""
PRINT tecla$
Como se puede ver, este bucle de limpieza es un bucle WHILE, es
decir, un bucle rechazable. Si el buffer ya está vacío ni siquiera
entramos, en caso contrario entramos y leemos y borramos la tecla
que pudiera tener antes de ir al otro bucle de reconocimiento de
teclas.

2.1.2 - RECONOCIMIENTO DE TECLAS ESPECIALES


Como hemos dado por supuesto en los ejemplos anteriores las teclas
se almacenan en el buffer del teclado usando el código ASCII o la
representación del carácter que llevan. Por ejemplo la "a minúscula" se
almacena como "a" o su código ASCII que es el 97, la "a mayúscula" lo
hace como "A" o 65, el número cinco como "5" o 53, el punto como "."
o 46 , el espacio en blanco como " " o 32 y así con todos los caracteres
que tenemos en la sección alfanumérica del teclado.

Para trabajar con los códigos ASCII usaremos la conocida función


CHR$(), de esta forma es lo mismo decir...

IF tecla$ = "A" THEN

...que...

IF tecla$ = CHR$(65) THEN

De esta forma también podemos identificar otras teclas solo con


conocer su código ASCII. Estos son algunos de los más usados:

Escape.....: 27
Enter......: 13
Tabulador..: 9
Retroceso..: 8

También es posible identificar las teclas especiales de flechas


(Imprescindible para los juegos), las teclas de función y las otras como
Inicio, Fin, Insertar, etc.
Al pulsar estas teclas se almacenan en el buffer del teclado dos
caracteres, siendo el primero el CHR$(0). Para identificar estas teclas
no hay más que usar en las comprobaciones CHR$(0)+El carácter que
tengan asignado, aquí va una lista de los más usados:

Flecha arriba......: CHR$(0)+"H"


Flecha abajo.......: CHR$(0)+"P"
Flecha izquierda...: CHR$(0)+"K"
Flecha derecha.....: CHR$(0)+"M"

F1.................: CHR$(0)+";"
F2.................: CHR$(0)+"<"
F3.................: CHR$(0)+"="
F4.................: CHR$(0)+">"

F5.................: CHR$(0)+"?"
F6.................: CHR$(0)+"@"
F7.................: CHR$(0)+"A"
F8.................: CHR$(0)+"B"

F9.................: CHR$(0)+"C"
F10................: CHR$(0)+"D"
F11................: CHR$(0)+CHR$(133)
F12................: CHR$(0)+CHR$(134)

Insertar...........: CHR$(0)+"R"
suprimir...........: CHR$(0)+"S"
Inicio.............: CHR$(0)+"G"
Fin................: CHR$(0)+"O" (Letra o mayúscula)
Retroceder página..: CHR$(0)+"I" (Letra i mayúscula)
Avanzar página.....: CHR$(0)+"Q"

Podemos ver una lista completa en la ayuda en pantalla de QBasic en


el apartado llamado "Códigos de exploración del teclado". Aquí
también aparecen los códigos de teclas combinadas como por ejemplo
CONTROL+Z.

Vamos a ver un ejemplo final de un programa que reconozca todas las


teclas alfanuméricas normales además de Enter, Escape y las flechas
que son especiales. En temas siguientes cuando hagamos programas
más "bonitos" usaremos ampliamente el reconocimiento de teclas.

CLS
PRINT "Pulsa las teclas que quieras, escape para salir"
PRINT
DO
WHILE INKEY$ <> "": WEND 'Limpia buffer de entrada
DO
tecla$ = INKEY$
LOOP WHILE tecla$ = ""
SELECT CASE tecla$
CASE CHR$(0) + "H": PRINT "Has pulsado la flecha
arriba"
CASE CHR$(0) + "P": PRINT "Has pulsado la flecha
abajo"
CASE CHR$(0) + "K": PRINT "Has pulsado la flecha
izquierda"
CASE CHR$(0) + "M": PRINT "Has pulsado la flecha
derecha"
CASE CHR$(13): PRINT "Has pulsado Enter"
CASE CHR$(27): PRINT "Has pulsado Escape, adios"
CASE " ": PRINT "Has pulsado la barra de espacio"
CASE ELSE: PRINT "Has pulsado la tecla " + tecla$
END SELECT
LOOP UNTIL tecla$ = CHR$(27)

Observa que este programa no reconoce las teclas de función, entre


otras, para hacerlo no hay más que añadir las instrucciones CASE con
los códigos correspondientes.

2.1.3 - HACER PAUSAS EN LOS PROGRAMAS


Una forma muy sencilla de detener la ejecución de un programa, por
ejemplo para que el usuario pueda leer algo es usar la instrucción
SLEEP. Al usar esta instrucción el programa se detiene hasta que el
usuario pulse una tecla. Si añadimos un número a continuación, por
ejemplo:

SLEEP 10

Ocurrirá que el programa se detendrá por un máximo de 10 segundos,


continuando pasado este tiempo automáticamente aunque el usuario
no pulse ninguna tecla.
Esta instrucción funcionará en la mayoría de los casos, pero puede
ocurrir que alguna vez no llegue a detener el programa porque hay
datos residuales en el buffer de entrada, en estos casos podremos
usar lo que hemos visto en el apartado anterior: Limpieza del
buffer+Espera activa, es decir esto:

WHILE INKEY$<>"":WEND
DO:LOOP WHILE INKEY$=""

Esta estructura la podemos ampliar para hacer una pregunta al usuario


y dejarle responder Sí o No pulsando S o N, por ejemplo.

WHILE INKEY$<>"":WEND
DO
tecla$=INKEY$
LOOP UNTIL tecla$="S" OR tecla$="N"

Aquí entramos en el bucle de espera activa y no salimos hasta que el


usuario pulse la S o la N, y después más adelante en el programa
viendo el valor de tecla$ ya decidiríamos entre hacer algo o no. Pero
todavía hay un problema, este ejemplo tal como está sólo reconoce la
S y la N mayúsculas, por lo que si el usuario tiene el "Bloqueo de
mayúsculas" desactivado puede que no se le ocurra como seguir. Para
arreglar el problema usaremos la función UCASE$ que convierte todas
las letras (Menos la Ñ y las vocales acentuadas) a mayúsculas. Vamos
con el ejemplo completo:

CLS
PRINT "¿Quieres hacer no se qué? S/N: "
WHILE INKEY$<>"":WEND
DO
tecla$=UCASE$(INKEY$)
LOOP UNTIL tecla$="S" OR tecla$="N"

IF tecla$="S" THEN
PRINT "Has dicho que sí"
ELSE
PRINT "Has dicho que no"
END IF

Ampliando más este ejemplo podríamos construir un menú "cutre". Si


dejamos al usuario la posibilidad de pulsar más teclas, normalmente
números, y después los comprobamos con un SELECT CASE podríamos
decidir entre hacer varias cosas. Es una estructura similar a la usada
en el ejemplo de reconocer teclas del apartado anterior.

<a href="http://www.addfreestats.com" target="_blank">< img


src="http://www5.addfreestats.com/cgi-
bin/connect.cgi?usr=00503209Pauto" border=0
title="AddFreeStats.com Free Web Stats!"></a>

TEMA 2.2
NÚMEROS ALEATORIOS Y CONTROL DE
TIEMPO
 2.2.1 - Números aleatorios. Función RND
 2.2.2 - Control de tiempo

2.2.1 - MÚMEROS ALEATORIOS. FUNCIÓN RND


Cuando diseñamos un programa informático lo que intentamos es
resolver un problema obteniendo un resultado lo más exacto y fiable
posible, usando la gran precisión de los cálculos que hacen los
ordenadores. De hecho, una de las normas básicas para asegurar que
un algoritmo funciona correctamente es poder asegurar y demostrar
que para unos mismos datos de entrada los resultados o datos de
salida van a ser siempre los mismos.

En algunos casos como son los videojuegos nos puede interesar que
suceda justamente lo contrario de lo que se ha dicho en el párrafo
anterior, es decir, que cada vez que ejecutemos el programa sucedan
cosas distintas y aparentemente impredecibles. Otras aplicaciones
donde tendría que suceder esto serían programas simuladores de
cálculos y fenómenos científicos y algunas aplicaciones matemáticas o
estadísticas especiales.

Para poder llegar a estas situaciones nos encontramos con el terrible


problema de conseguir que el ordenador (tan perfecto, exacto, preciso,
etc...) sea capaz de generar números de cualquier manera sin
responder a ningún orden aparente. Esto técnicamente es imposible y
por lo tanto en vez de hablar de números aleatorios sería más correcto
llamarlos números pseudo-aleatorios porque no son completamente
impredecibles, pero lo son casi, lo suficiente como para resolver
nuestras necesidades.

Para generar estos números (pseudo-) aleatorios el ordenador puede


usar mecanismos tales como medir el tiempo entre dos pulsaciones de
teclas, o el necesario para enviar o recibir un dato a través de una red.
Como esto puede ser difícil de conseguir en algunos casos, es mucho
más cómodo usar lo que se conoce como "generador de números
aleatorios" que es una formula matemática que al aplicarla repetidas
veces pasándole los valores anteriores nos va devolviendo una serie de
números sin orden aparente. Una de estas fórmulas se llama
"Generador de Marsaglia" y viene a ser algo así como:

zN = (2111111111 * zN-4 + 1492 * zn-3 + 1776 * zn-2 + 5115 * zn-1 + C) MOD 232

C = FLOOR((2111111111 * zN-4 + 1492 * zn-3 + 1776 * zn-2 + 5115 * zn-1 + C) / 232)

Esta fórmula nos devolvería una serie de números que no empezaría a


repetirse hasta que lo hayamos calculado 3 * 10 elevado a 47 veces,
un 3 con 47 ceros detrás, más que suficiente. Pero que nadie se
asuste, de hecho no se ni si están bien copiados los números. En
QBasic (y en otros lenguajes de programación) no nos hace falta
programar tantos relíos, basta con usar el valor que nos devuelva la
función RND cada vez que queramos obtener un número aleatorio.
Vamos con un ejemplo de los más sencillo del curso de programación:

CLS
PRINT RND
Al ejecutarlo se escribiría en pantalla el número aleatorio que devuelva
la función RND, un valor decimal (casi) impredecible, por ejemplo:

.7055475

Ya lo tenemos ahí, un número generado por el ordenador sin


responder a ningún criterio aparente (Lleva un cero delante, pero
QBasic se lo quita al escribirlo en la pantalla y solo se ve el punto
decimal), pero todavía hay un problema, ejecuta el programa varias
veces y mira el resultado.
Puedes comprobar que siempre sale el mismo número, ya no es tan
impredecible. Esto se debe a que no hemos inicializado el generador
de números aleatorios de QBasic.

Si tratas de descifrar algo de la fórmula de Marsaglia que hay más


arriba observarás que para calcular un número pseudo-aleatorio se
necesitan algunos de los calculados anteriormente. Esto va muy bien si
ya los hemos calculado, pero si es la primera vez no los tenemos. En
este caso tendremos que usar lo que se conoce como "Semilla", que es
un número arbitrario usado para que empiece a funcionar el
generador. Si este número es el mismo siempre los números aleatorios
devueltos serán siempre una misma serie, por lo tanto hay que usar
como semilla un número lo más "aleatorio" posible como por ejemplo
el número de segundos que han pasado a partir de las doce de la
noche, cosa que nos devuelve la función TIMER. Para inicializar el
generador de números aleatorios usaremos la instrucción RANDOMIZE
seguida de la semilla, normalmente TIMER. Vamos con el ejemplo
completo:

RANDOMIZE TIMER
CLS
PRINT RND

Ahora sí que cada vez que ejecutemos el programa tendremos un


número distinto. El valor solo se repetiría si ejecutamos el programa
otro día a la misma hora, mismo minuto, mismo segundo. Algo difícil
de conseguir y un riesgo aceptable en programas que no necesiten
elevadas medidas de seguridad como los nuestros, pero no en otras
aplicaciones como pudieran ser máquinas recreativas. De hecho, en
sistemas más serios como Lunux se puede observar como al apagar el
ordenador se guarda en algún sitio la semilla aleatoria para poderla
seguir usando la próxima vez.

La instrucción RANDOMIZE TIMER es algo así como remover las bolas


en un sorteo de lotería, debemos usarla una vez al principio de los
programas que usen la función RND, pero no es necesario usarla más,
de hecho repetirla en sitios como dentro de un bucle podría resultar
contraproducente.

El valor devuelto por RND va a ser un número decimal de precisión


sencilla mayor o igual que cero y menor que uno, es decir, alguna vez
podría salir el cero, pero nunca saldrá el uno. Este valor lo podemos
multiplicar o redondear para adaptarlo a nuestras necesidades.
Vamos con un programa que contiene dos ejemplos:

CLS
RANDOMIZE TIMER
valorDado = INT(RND*6)+1
PRINT "Hemos lanzado un dado y ha salido"; valorDado
IF RND > 0.5 THEN
PRINT "Hemos lanzado una moneda y ha salido 'cara'"
ELSE
PRINT "Hemos lanzado una moneda y ha salido 'cruz'"
END IF

Que daría este resultado (Con estos u otros valores):

Hemos lanzado un dado y ha salido 4


Hemos lanzado una moneda y ha salido 'cruz'

En el primer caso multiplicamos el valor de RND por seis, ya tenemos


un número decimal que puede ir desde 0 a 5.999 (Nunca 6). A
continuación lo redondeamos hacia abajo usando la función INT, ya
tenemos un entero entre 0 y 5. Como queremos un valor posible entre
1 y 6, que son las caras que tienen los dados, no hay más que sumarle
1.
En el segundo caso lo que hacemos es comprobar el valor de RND para
hacer una cosa u otra. Como en este caso queremos que las dos
partes del IF tengan las mismas posibilidades ponemos el punto de
comprobación en la mitad, en 0.5. Si quisiéramos variar las
posibilidades no tendríamos más que cambiar ese número. Si
queremos más de dos posibilidades podemos usar un SELECT CASE
con varios intervalos.

Por último vamos a ver como conseguir que RND nos repita el último
número que generó sin tener que grabarlo en ninguna variable. Para
conseguirlo bastaría con escribir RND(0) en vez de RND. Vamos con un
ejemplo:

CLS
RANDOMIZE TIMER
PRINT "Un número aleatorio.............:"; RND
PRINT "Repitamos el mismo número.......:"; RND(0)
PRINT "Otra vez más....................:"; RND(0)
PRINT "Ahora un número aleatorio nuevo.:"; RND
PRINT "Vamos a sacarlo otra vez........:"; RND(0)

El resultado podría ser (con otros números):

Un número aleatorio.............: .2051972


Repitamos el mismo número.......: .2051972
Otra vez más....................: .2051972
Ahora un número aleatorio nuevo.: .1969156
Vamos a sacarlo otra vez........: .1969156

Como se puede ver, si llamamos a RND sin argumentos, como hemos


hecho hasta ahora, nos da un número aleatorio, pero si lo hacemos
pasándole como argumento el cero entre paréntesis (Tiene que ser
obligatoriamente el cero) lo que haces es repetir el mismo número en
vez de calcular uno nuevo, que sería el siguiente de la serie.

Si usamos RND(0) por primera vez en el programa, sin haber usado


antes RND, no pasa nada, tendremos un número aleatorio.
Esto no se utiliza demasiado, pero en algún caso podemos ahorrarnos
una variable y una instrucción de asignación si queremos usar el
mismo número en varios sitios.

2.2.2 - CONTROL DE TIEMPO


El ordenador lleva instalado un reloj digital que usa para muchas
cosas, como por ejemplo para almacenar en los directorios la fecha y
hora en que se guardó cada archivo. Por supuesto nosotros también
podemos utilizar este valor en nuestros programas.

La forma más sencilla de obtener la hora ya la hemos visto en el


apartado anterior. Es usar el valor devuelto por la función TIMER, un
entero largo con los segundos que han pasado desde las doce de la
noche. Con esta función ya podemos hacer un programa que nos
salude de distinta forma dependiendo del momento del día que sea.
Allá vamos...

CLS
SELECT CASE TIMER
CASE IS < 28800: PRINT "Buenas madrugadas"
CASE 28801 TO 43200: PRINT "Buenos días"
CASE 43201 TO 72000: PRINT "Buenas Tardes"
CASE ELSE: PRINT "Buenas noches"
END SELECT

El resultado, si ejecutamos el programa a las diez de la mañana sería:

Buenos días

Es una forma de que nuestro programa sea algo "inteligente", aunque


todavía se podría mejorar (mucho). Lo que hemos hecho es ver
cuantos segundos han pasado a las ocho de la mañana, a mediodía y a
las ocho de la tarde para definir los intervalos, nada más.

También podemos usar la función TIMER para cronometrar el tiempo


que tarda en pasar algo. Bastaría con almacenar la hora en una
variable antes de empezar, hacer lo que sea, y al terminar almacenar
la hora en otra variable y restarlas. Obtendríamos el número de
segundos. Vamos a cronometrar lo que tarda el ordenador en escribir
en pantalla los números del 1 al 10.000. Allá vamos...

CLS
inicio=TIMER
FOR n = 1 TO 10000
PRINT n
NEXT
final = TIMER
tiempo = final - inicio
PRINT "Ha tardado"; tiempo; "segundos"

Este programa, al final de la tira de 10.000 números nos daría un


mensaje con el número de segundos que ha tardado.

Todo funcionaría bien excepto si durante la ejecución del programa


han dado las doce de la noche, obteniéndose en este caso un número
negativo. Para solucionar el problema habría que colocar la siguiente
línea justo antes de sacar el mensaje. Lo que hacemos es sumarle
todos los segundos de un día si ha dado negativo.

IF tiempo < 0 THEN tiempo = tiempo + 86400

Ahora vamos a recordar dos funciones que nos devuelven la fecha y la


hora en modo texto, son DATE$ y TIME$.

CLS
PRINT "Hoy es "; DATE$
PRINT "Son las "; TIME$

Esta daría como resultado algo como:

Hoy es 01-20-

2015
Son las 10:42:35

Con la hora no hay nada que aclarar, pero la fecha está en formato
americano, primero el mes, después el día y al final el año. El mes y el
día siempre con dos cifras y el año con cuatro. Estas funciones pueden
ser más intuitivas pero para manejar las cifras las tenemos que
separar usando la función MID$. Vamos con el ejemplo:

CLS
PRINT "El día es "; MID$(DATE$, 4, 2)
PRINT "El mes es "; MID$(DATE$, 1, 2)
PRINT "El año es "; MID$(DATE$, 7, 4)
PRINT
PRINT "La hora es "; MID$(TIME$, 1, 2)
PRINT "Los minutos son "; MID$(TIME$, 4, 2)
PRINT "Los minutos son "; MID$(TIME$, 7, 2)
Y el resultado podría ser:

El día es 20
El mes es 01
El año es

2015

La hora es 10
Los minutos son 42
Los minutos son 35

Muy sencillo. Lo único que hemos hecho es "recortar" con la función


MID$ el trozo de cadena donde está cada cosa, que siempre va a estar
en el mismo sitio, y separarlo para hacer con él lo que queramos. Y si
nos hiciera falta sumarlos o compararlos podríamos convertirlos a
enteros usando la función VAL.

Aplicando esto mismo, vamos con un ejemplo de una función a la que


vamos a llamar FECHA$ y nos va a devolver la fecha actual como la
usamos normalmente en castellano: Día, mes y año separados por
barras.

FUNCTION FECHA$
aux$ = MID$(DATE$, 4, 2)
aux$ = aux$ + "/" + MID$(DATE$, 1, 2)
aux$ = aux$ + "/" +MID$(DATE$, 7, 4)
FECHA$ = aux$
END FUNCTION

Al llamarla desde cualquier parte de nuestro programa nos devolvería,


en formato cadena algo como 20/01/ 115, que queda más bonito que
01-20- 115.

Para terminar vamos a ver como cambiar la fecha y hora del sistema
desde nuestro programa. Algo que no es habitual ni recomendable,
pero que en algún caso nos puede hacer falta.
Sería usando las instrucciones, no funciones sino instrucciones, DATE$
y TIME$ y asignándoles cadenas que contengan la fecha o la hora en
el formato adecuado. Vamos con unos ejemplos:

DATE$ = "04-20-
115"
DATE$ = "04-20-92"
DATE$ = "04-06-1994"

TIME$ = "10:24:35"
TIME$ = "10:24"
TIME$ = "10"

La fecha podemos establecerla poniendo siempre el mes primero y


después el día y el año, este último dato puede tener dos o cuatro
cifras. En el último ejemplo podríamos dudar entre si la fecha es el 4
de junio o el 6 de abril, se almacenaría este último, 6 de abril. Cuidado
con esto, siempre mes, día, año.

En el caso de la hora si no ponemos los minutos o los segundos se


entenderán que son 00.

Si intentamos asignar una fecha o una hora no válida se producirá un


error de tiempo de ejecución. Normalmente no es recomendable
modificar estas cosas desde los programas, si lo hacemos mal y no nos
damos cuenta estaremos un tiempo usando el ordenador con el reloj
mal, provocando problemas en los directorios, los antivirus, el
planificador de tareas programadas, etc.

TEMA 2.3
MÁS SOBRE LAS CADENAS DE CARACTERES
 2.3.1 - Acerca de las cadenas de caracteres
 2.3.2 - La función e instrucción MID$
 2.3.3 - Otras funciones de manejo de cadenas
2.3.1 - ACERCA DE LAS CADENAS DE CARACTERES
En QBasic el manejo de cadenas de caracteres es extremadamente
sencillo. En otros lenguajes de programación como es el caso de C las
cadenas se tratan como un array de datos de tipo carácter (un tipo
que no tenemos en QBasic) de longitud aproximadamente igual al
número de caracteres de la cadena, y por lo tanto su longitud máxima
está siempre limitada. También esta forma de trabajar implica que
para hacer algo tan simple como una asignación de cadenas en esos
lenguajes haya que recurrir a funciones especiales del lenguaje y no
podamos usar directamente el operador de asignación, el signo igual,
como si se tratara de números.

En este tema vamos a ver con detalle algunas funciones de QBasic


relacionadas con cadenas que nos van a servir para manejar los datos
de este tipo que nos hagan falta en nuestro programa.

Vamos a recordar un poco lo que ya sabemos de las cadenas:

Al igual que ocurre con las otras variables, no es necesario declararlas.


Basta con usar un nombre de variable terminado por el carácter dólar
($). En este caso tendremos una cadena de texto de longitud variable.

En algunos casos nos puede interesar que las cadenas sean de


longitud fija para ahorrar memoria (Unos 10 bytes por cadena). En
este caso bastaría con declararlas usando:

DIM nombreVariable AS STRING * 25

Dónde el número que va a continuación del asterisco es la longitud


máxima de la cadena. Es especialmente recomendable declarar las
cadenas como de longitud fija en el caso de los arrays. Por ejemplo si
hacemos:

DIM tabla (1 TO 40, 1 TO 3) AS STRING * 5

Nos estamos ahorrando unos 120 bytes en todo el array, además de


que el manejo de cadenas de tamaño fijo por parte del ordenador es
algo más rápido por tratarse de estructuras estáticas.
En el caso de las estructuras de datos definidas por el usuario (tipos
registro) que incluyan cadenas, estas siempre tendrán que declararse
con una longitud determinada.

2.3.2 - LA FUNCIÓN E INSTRUCCIÓN MID$


Una de las funciones más útiles que incluye QBasic para el manejo de
cadenas de caracteres es MID$. Ya la hemos visto anteriormente en
varios ejemplos, pero vamos a hacerlo ahora con más detalle. Esta es
su sintaxis.

MID$("cadena", inicio, longitud)

Esta función lo que hace es extraer una porción de una cadena. Le


debemos pasar tres argumentos:

 "cadena" es una cadena de caracteres entre comillas o bien


el nombre de una variable de cadena cuyo valor va a ser
utilizado por la función, o una expresión que devuelva como
resultado una cadena.
 inicio es la posición del primer carácter que queremos sacar
de la cadena, el carácter de más a la izquierda es el 1. Si
este valor es superior al tamaño de la cadena, la función
MID$ devolverá como resultado una cadena vacía.
 longitud es el tamaño del trozo de cadena que queremos
extraer. Si se sobrepasa el final de la cadena no ocurre
nada, sólo se devolverá lo que se pueda de antes del final.

Vamos con unos ejemplos:

CLS
miCadena = "Hecho en Ronda, Ciudad Soñada"
PRINT MID$(miCadena, 1, 5)
PRINT MID$(miCadena, 5, 3)
PRINT MID$(miCadena, 15, 1)
PRINT MID$(miCadena, 26, 2)
PRINT MID$(miCadena, 300, 1)
PRINT MID$(miCadena, 10, 3000)
El resultado sería:

Hecho
o e
,
ña

Ronda, Ciudad Soñada

Esta función nos puede servir para extraer un determinado carácter de


una cadena. Algo que puede parecer trivial, pero que nos va a
simplificar mucho determinados problemas.

Vamos con un ejemplo tonto:

CLS
miCadena = "LMXJVSD"
INPUT "Escribe un número del 1 al 7: ", num
PRINT "El"; num; "º carácter de "; miCadena; " es ";
MID$(miCadena, num, 1)

Un resultado posible sería:

Escribe un número del 1 al 7: 4


El 4 º carácter de LMXJVSD es J

Muy sencillo. Sólo decir que no hemos depurado el dato de entrada y


que si escribimos un cero o negativo habrá un error.

Vamos con otro ejemplo más útil que utiliza esta misma técnica para
determinar la letra del DNI. Para hacer esto lo que hay que hacer es
comparar el resto del número dividido entre 23 con una serie de letras
ordenadas de una forma característica y dar la que corresponda.

CLS
INPUT "Escribe el número del DNI: ", dni&
PRINT "La letra es: ": MID$("TRWAGMYFPDXBNJZSQVHLCKE",
(dni& MOD 23) + 1, 1)

El resultado podría ser:

Escribe el número del DNI: 74926208


La letra es: M
Podemos ver que el cálculo se hace en una sola linea de código usando
la instrucción MID$.

 El primer argumento que le pasamos a MID$, la cadena de


caracteres, es la tira de letras ordenadas de forma
característica.
 La posición de inicio es el cálculo propiamente dicho. Le
sumamos uno porque la primera letra es la 1 y no la 0.
 El tamaño es 1 porque siempre queremos una y sólo una
letra.

Es importante ver que la variable dónde guardaremos el número debe


ser de tipo entero largo. Si la usamos como de tipo real (sin poner el
&) se redondeará y no saldrá bien el cálculo.

Con esta técnica el algoritmo ha resultado sumamente corto. A lo


mejor sería más sencillo o intuitivo haberlo resuelto usando un vector
de caracteres para poder acceder a sus posiciones individuales, o bien
un SELECT CASE. Con cualquiera de estas soluciones el listado del
programa hubiera sido mucho más largo. Tendríamos 23 posibilidades
en el SELECT CASE o bien 23 asignaciones al vector.

Este problema del DNI es muy frecuente. Le falta depurar los datos de
entrada y convertirlo en forma de función, para que sea mucho más
portable.

Para terminar con MID$ hay que decir que además de ser una función,
se puede usar también como instrucción para modificar la propia
cadena.

La sintaxis sería

MID$( VariableCadena$, inicio, longitud) = Cadena$

El funcionamiento es parecido, salvo unas cuantas diferencias:

 El primer argumento tiene que ser una variable de cadena y


no un literal entre comillas ni una expresión.
 El valor de inicio no puede ser mayor que la longitud de la
cadena, en este caso se produciría un error de tiempo de
ejecución.
Vamos con un ejemplo:

miCadena$ = "Hecho en Ronda"


PRINT miCadena$
MID$(miCadena$, 10, 5) = "Soria"
PRINT miCadena$
MID$(miCadena$, 10, 5) = "Sevilla"
PRINT miCadena$
MID$(miCadena$, 7, 8) = "aquí"
PRINT miCadena$

El resultado sería...

Hecho en Ronda
Hecho en Soria
Hecho en Sevil
Hecho aquíevil

Hay que tener en cuenta que la longitud del tramo de cadena a


reemplazar queda definido por el valor del tercer parámetro, y no por
el tamaño de la expresión de cadena que asignamos. Si este es mayor
se cortará, y si no llega sólo se usará lo que haya, quedando lo demás
como estaba. De esta misma forma la cadena nunca aumentará o
disminuirá su longitud total usando esta función. Como mucho
podremos disminuir su longitud aparente usando espacios en blanco,
pero realmente seguirán estando ahí formando parte de la cadena.

MID$ se usa mucho más como función que como instrucción. Hay que
tener claro para que sirve cada cosa y cuando se está usando una u
otra. Como función va siempre a la derecha del operador de asignación
o dentro de una expresión, y como instrucción va siempre al principio
de la línea de código.

2.3.3 - OTRAS FUNCIONES


DE MANEJO DE CADENAS
QBasic nos ofrece varias funciones útiles para hacer operaciones con
cadenas de caracteres. Una de las más útiles es MID$ a la que hemos
dedicado el apartado anterior completo. Aquí van otras:

Empecemos con dos funciones algo parecidas a MID$, pero menos


avanzadas.

LEFT$("cadena", numCaracteres)
RIGTH$("cadena", numCaracteres)

La primera de ellas devuelve un determinado número de caracteres del


principio de la cadena, de la izquierda, y la otra los devuelve del final,
de la derecha. Siempre en el mismo orden en que están en la cadena.
Se puede hacer referencia tanto a una cadena literal entre comillas o a
una variable o expresión de tipo cadena. Si el número de caracteres
especificado es mayor que la longitud total de la cadena se devolverá
la cadena entera. Si es cero se devolverá una cadena vacía, y si es
negativo habrá un error.

cadena$ = "Hecho en Ronda"


PRINT LEFT$(cadena$, 5)
PRINT RIGHT$(cadena$, 5)

Daría como resultado:

Hecho
Ronda

Ahora vamos con otras que convierten a mayúsculas y minúsculas...

UCASE$("Cadena")
LCASE$("Cadena")

UCASE$ convierte todas las letras que haya en la cadena, variable de


cadena o expresión, a mayúsculas (Upper Case), y LCASE$ a
minúsculas (Lower Case). Es importante tener en cuenta que no se
reconocen como letras ni los acentos ni la eñe ni la u con diéresis (ü) y
por lo tanto no se convierten correctamente. Vamos con un ejemplo.

cadena$ = "Una cigüeña en un balcón de África"


PRINT UCASE$(cadena$)
PRINT LCASE$(cadena$)
Daría:

UNA CIGüEñA EN UN BALCóN DE ÁFRICA


una cigüeña en un balcón de África

Ahora vamos con otras dos funciones que eliminarán los espacios en
blanco que pueda haber en los extremos de una cadena.

LTRIM$("Cadena")
RTRIM$("Cadena")

LTRIM$ elimina los espacios que puede haber delante (A la izquierda,


left) y RTRIM$ los que pueda haber por el final (A la derecha, right).

Esta última función es muy útil para trabajar con datos almacenados
en cadenas de longitud fija (Como las que se usan en los tipos de
datos definidos por el usuario para los registros). Estas cadenas
siempre van rellenas con espacios hasta ocupar su longitud total, y si
las manejamos con todos estos espacios pueden pasar cosas como que
fallen las comparaciones o que pasen cosas imprevistas en los diseños
de pantallas.

Ambas funciones pueden ser útiles para depurar datos introducidos por
teclado en los que el usuario haya podido escribir espacios inútiles
antes o después. Vamos con un ejemplo:

cadena$ = " Hola! "


PRINT "*"; LTRIM$(cadena$); "*"
PRINT "*"; RTRIM$(cadena$); "*"

Y el resultado:

*Hola! *
* Hola!*

En Visual Basic se dispone de una función TRIM$ que elimina los


espacios tanto delante como detrás de la cadena. Aquí no la tenemos,
pero su programación sería muy sencilla construyendo una nueva
función a partir de estas dos.

Ahora vamos con una función que nos devuelve una cadena llena con
el número de espacios que le digamos
SPACE$(num)

Puede parecer inútil, pero nos ayudará bastante en el diseño de


pantallas. Por ejemplo:

PRINT "Hola"; SPACE$(40); "Que hay"

Daría:

Hola Que hay

O el siguiente ejemplo más elaborado:

CLS
FOR n = 0 TO 5
PRINT SPACE$(5 - n); "/"; SPACE$(n + n); "\"
NEXT

Dibujaría esta figura:

/\
/ \
/ \
/ \
/ \
/ \

Vamos con otra función parecida, pero que en vez de devolver una
cadena de espacios la devuelve del carácter que nosotros le digamos:

STRING(Longitud, "carácter")

o bien

STRING(Longitud, Código-ASCII)

Como se puede ver podemos especificar el carácter que queremos


usando una cadena o bien usando el número de su código ASCII. Esto
es especialmente útil cuando queremos dibujar un carácter que no
aparece en el teclado.

Vamos con unos ejemplos:


CLS
PRINT STRING$(10, "*")
PRINT STRING$(5, 60)
PRINT STRING$(15, "RONDA")
PRINT STRING$(8, 126)

Que daría

**********
<<<<<
RRRRRRRRRRRRRRR
~~~~~~~~

Al igual que la anterior, esta función nos será muy útil para construir
diseños de pantallas, pero hay que tener cuidado de no confundir su
nombre STRING$ con el de tipo de datos cadena que es STRING sin el
dólar detrás.

Vamos ahora con otra función que en vez de devolvernos la cadena


con cierta modificación nos va a devolver un número que corresponde
a la longitud (Número de caracteres) de la cadena.

LEN("cadena")

Vamos con un ejemplo que en combinación con la función STRING$


que acabamos de ver nos subraye una palabra usando guiones.

CLS
INPUT "Escribe algo: "; cad$
PRINT " "; STRING$(LEN(cad$), "-")

Daría algo como:

Escribe algo: YA ESCRIBO ALGO


---------------

Esta función LEN también nos devuelve el espacio en bytes que ocupa
en memoria cualquier variable. Basta con pasarle como parámetro el
nombre de una variable que no sea de cadenas.

A esta categoría de funciones de manejo de cadenas habría que añadir


otras que ya hemos visto como son DATE$ y TIME$, así como algunas
menos utilizadas como HEX$ y OCT$ que convierten un número a
sistemas de numeración en base 16 y en base 8 respectivamente, pero
ya que el resultado es en forma de cadena no nos servirá para hacer
ningún cálculo.

Combinando estas funciones podemos construir otras más potentes


que nos hagan cosas como por ejemplo dibujar recuadros o centrar
textos. Esto es lo que se verá en el tema siguiente de manejo de
pantalla del texto.

<a href="http://www.addfreestats.com" target="_blank">< img


src="http://www5.addfreestats.com/cgi-
bin/connect.cgi?usr=00503209Pauto" border=0
title="AddFreeStats.com Free Web Stats!"></a>

TEMA 2.4
MANEJO DE LA PANTALLA EN MODO TEXTO
 2.4.1 - Introducción a los interfaces de texto
 2.4.2 - Posicionar el cursor en la pantalla
 2.4.3 - Texto en colores
 2.4.4 - Redefinir colores
 2.4.5 - Caracteres semigráficos
 2.4.6 - Caracteres especiales de relleno
 2.4.7 - Caracteres no imprimibles
 2.4.8 - Esmáilis y ASCII-ART
2.4.1 - INTRODUCCIÓN A LOS INTERFACES DE
TEXTO
A estas alturas del siglo XXI casi todos los ordenadores personales
utilizan modernos entornos gráficos como son Windows o los también
conocidos KDE y GNOME en Linux. Estos sistemas pueden representar
cualquier imagen a partir de pequeños puntos que pueden ser de uno
de los millones de colores soportados. Para aplicaciones más sencillas,
como es el caso del entorno de QBasic, se utiliza lo que se denomina
Interfaz de texto, que es mucho más fácil de controlar por el
ordenador y más que suficiente para determinadas aplicaciones.

En todos los ejemplos que hemos hecho hasta ahora hemos podido
comprobar como los resultados se iban presentando en la pantalla de
forma secuencial, es decir, unos debajo de otro conforme se iban
generando. Cuando se alcanzaba la parte baja de la pantalla todo el
contenido subía automáticamente para dejar una nueva línea vacía
abajo. Algunas operaciones más "sofisticadas" que hemos llegado a
hacer han sido borrar todo usando la instrucción CLS o encolumnar la
presentación de algunos listados ajustando los argumentos que le
pasamos a la instrucción PRINT. De todas formas los resultados
siempre se han visto de color gris sobre fondo negro. Muy triste.

En este tema vamos a ver cómo se puede conseguir escribir


exactamente en la posición de la pantalla que queramos, así como en
distintos colores de letra y de fondo. También usaremos unos
caracteres especiales conocidos como "Semigráficos" para dibujar
líneas y recuadros que den más consistencia a nuestras
presentaciones.

2.4.2 - POSICIONAR EL CURSOR EN LA PANTALLA


El cursor es un objeto que se puede mover por la pantalla y su
posición indica el sitio exacto dónde se escribirá lo siguiente. En los
procesadores de textos o en el propio editor de QBasic, es la "barrita"
intermitente por donde van apareciendo las letras que vamos
escribiendo en el teclado. En cualquier programa en modo texto el
cursor siempre va a existir y va a indicar el lugar dónde se va a
escribir lo siguiente que haya que escribir, ya sea porque lo haga el
usuario con el teclado o bien el propio programa atendiendo a sus
instrucciones de salida.

En los programas de QBasic sólo es visible cuando se pide al usuario


que escriba algo usando la instrucción INPUT. El resto del tiempo está
ahí en su sitio, pero no lo vemos.

Cuando usamos cualquier instrucción PRINT sin punto y coma al final


se escribe lo que sea y el cursor invisible pasa al principio de la
siguiente línea a la espera de la siguiente instrucción de escritura. De
esta forma todos los resultados se van mostrando uno debajo de otro
línea por línea.

La pantalla normal en modo texto que usamos aquí está compuesta


por 25 líneas de 80 caracteres de ancho.

La primera línea, la de más arriba es la número 1 y la de abajo es la


25. La columna de más a la izquierda es la 1 y la de más a la derecha,
la última, es la 80. Para llevar el cursor a cualquiera de estas
posiciones y escribir en la parte de la pantalla que queramos no hay
más que usar la siguiente instrucción:

LOCATE linea, columna

De esta forma tan sencilla, usando una instrucción LOCATE delante de


cada PRINT o INPUT ya podemos controlar exactamente donde va a
salir cada cosa. Vamos con un ejemplo:

CLS
LOCATE 22, 30
PRINT "Esto va a salir abajo"
LOCATE 3, 35
PRINT "Y esto arriba"
LOCATE 12, 1
PRINT "A la izquierda"
LOCATE 14, 67
PRINT "A la derecha"
LOCATE 13, 37
PRINT "Centro"
SLEEP

Y este sería el resultado. El recuadro representa al borde de la


pantalla.

Cómo se puede ver, las frases ya no aparecen escritas de arriba a


abajo en el mismo orden en que están escritas en el listado del
programa. Cada una aparece en el sitio donde la anterior instrucción
LOCATE ha llevado el cursor de texto. Para ajustar estas posiciones no
hay más que variar los números de fila y columna de las instrucciones
LOCATE. La instrucción SLEEP que aparece al final es para que el
rótulo "Presione cualquier tecla y continúe." no aparezca hasta que no
pulsemos una tecla. Esto será muy común para que no se nos
estropeen los diseños de pantalla.

Otra cosa que nos va a estropear los diseños de pantalla va a ser el


desplazamiento vertical que hace automáticamente el interfaz en
modo texto para que conforme vamos escribiendo lo anterior se
desplaza hacia arriba, como ha venido pasando en los programas que
hemos hecho hasta ahora.
Esto hace que si escribimos algo en la línea 24, las líneas 1 a 24 suban
una posición, desapareciendo lo que haya en la primera línea. Y si
escribimos algo en la línea 25, las líneas 1 a 24 subirán 2 posiciones,
desapareciendo lo que hubiera escrito en las dos primeras líneas.

La solución más cutre para salvar este problema sería no escribir


nunca en las líneas 24 y 25, pero hay otra mejor. Poner un punto y
coma al final de todas las instrucciones PRINT o a continuación de la
palabra INPUT en las instrucciones de entrada, siempre que estén
situadas en una de estas dos últimas líneas. De esta forma evitaremos
que al terminar la instrucción el cursor baje y se produzca el
desplazamiento vertical automático.

Ahora ya podemos realmente escribir en cualquier parte de la pantalla


usando la instrucción LOCATE.
Vamos con un ejemplo que aclare esto del desplazamiento automático.

CLS
LOCATE 1, 20: PRINT "Principio"
LOCATE 25, 20: PRINT "Final"
SLEEP

Si ejecutas este fragmento de código observarás que la palabra


PRINCIPIO no aparece en la pantalla, ya que tras escribir en la línea
25 se produce este desplazamiento automático. Para solucionar el
problema añadimos un punto y coma al final de la instrucción PRINT
conflictiva:

CLS
LOCATE 1, 20: PRINT "Principio"
LOCATE 25, 20: PRINT "Final";
SLEEP

Y podrás observar como aparecen ambas palabras escritas en la


pantalla, una arriba y otra abajo, justamente en las posiciones que
hemos especificado. Problema solucionado.

Un último comentario. En los dos fragmentos anteriores has podido


observar que las instrucciones LOCATE y PRINT van en la misma línea
separadas por el carácter "Dos puntos" (:). Esto es común hacerlo
para acortar el listado, ya que estas dos instrucciones casi siempre van
a ir por parejas.

2.4.3 - TEXTO EN COLORES


Hasta ahora todos nuestros programas han dado los resultados en la
pantalla usando letras de color gris claro sobre fondo negro. Para
llorar, pero esto va a cambiar ahora mismo. El interfaz en modo texto
nos ofrece la posibilidad de usar dieciséis colores de letra y ocho de
fondo. No es que sea una maravilla tecnológica comparada con los
millones de colores que usan los entornos gráficos actuales, pero para
cumplir nuestros objetivos son más que suficientes.

Vamos a ver la paleta de colores predeterminada del modo de pantalla


VGA:

Como se puede ver, cada color lleva un asociado un númerito. Este


número se conoce como "Atributo de color" y nos va a servir para
especificar los colores mediante la siguiente instrucción:

COLOR primerPlano, fondo

Por ejemplo, si queremos escribir con letras amarillas sobre fondo azul
(muy típico), no tenemos más que usar una instrucción

COLOR 14, 1

antes de las correspondientes instrucciones PRINT o INPUT.

Al usar una instrucción COLOR, se cambian los colores de la pantalla


para sucesivas instrucciones de escritura en pantalla hasta que se
vuelva a cambiar usando otra instrucción COLOR. No hace falta poner
una instrucción COLOR delante de cada PRINT o INPUT si no vamos a
cambiar el color.
Como color de primer plano podemos usar cualquiera de los 16
atributos de color disponibles. Como color de fondo solo los 8
primeros. Si no especificamos color de fondo, se conservará el que
hubiera.

El color de fondo en principio sólo afecta al trozo de pantalla que hay


por detrás de los caracteres que escribimos. Si queremos colorear toda
la pantalla de un determinado color, una forma muy rápida de hacerlo
es especificar una instrucción COLOR justo antes de una CLS, por
ejemplo:

COLOR 15, 4
CLS

Hará que toda la pantalla se coloree de rojo oscuro. En sucesivas


instrucciones PRINT se escribirá lo que sea en color blanco fuerte
sobre este rojo mientras no usemos otra instrucción COLOR. Hay que
tener cuidado de no usar el mismo color para primer plano y para
fondo. Si lo hacemos no pasa nada, nuestro programa va a seguir
funcionando perfectamente, pero sería como escribir con lápiz blanco
en un papel, no se vería nada de nada.

También podemos conseguir que los caracteres aparezcan en la


pantalla parpadeando. Para hacerlo no hay más que sumar 16 al color
de primer plano que queramos, por ejemplo:

COLOR 30, 2

Hará que el texto escrito posteriormente aparezca de amarillo


parpadeando sobre fondo verde. (14 del amarillo más 16 son 30). No
se puede hacer que el fondo parpadee.
Una cosa importante a tener en cuenta con el parpadeo es que NO VA
A FUNCIONAR si estamos ejecutando QBasic desde Windows en una
ventana. Para que funcione habrá que pasar a pantalla completa
pulsando ALT+ENTER o el botón correspondiente de la barra de
botones de la ventana, y aún así a la primera tampoco funciona
algunas veces, lo que se hace es afectar al color de fondo.

No es conveniente abusar del parpadeo, puede resultar molesto para


la vista. Se debe de utilizar sólo para resaltar pequeños mensajes o
símbolitos en la pantalla.
Como ejemplo veamos como podría quedar una portada sencilla en
colores para un programa de agenda que integre todo lo que vimos en
el tema de ficheros.

Para conseguir esto no habría mas que insertar las instrucciones


COLOR correspondientes en los lugares adecuados, como se ve aquí:

CLS
PRINT
COLOR 4
PRINT , , "* * * * * * * * * * * *"
PRINT , , "* ";
COLOR 10: PRINT "AGENDA SUPER BARATA ";
COLOR 4: PRINT "*"
PRINT , , "* *"
PRINT , , "*";
COLOR 2: PRINT " J.M.G.B. Ronda 2003 ";
COLOR 4: PRINT "*"
PRINT , , "* * * * * * * * * * * *"
PRINT
PRINT
COLOR 13
PRINT , "A ... Añadir nueva persona"
PRINT , "B ... Borrar persona"
PRINT , "M ... Modificar persona"
PRINT
COLOR 5
PRINT , "N ... Buscar persona por nombre"
PRINT , "D ... Buscar persona por dirección"
PRINT , "T ... Buscar persona por teléfono"
PRINT , "E ... Buscar persona por edad"
PRINT
COLOR 1
PRINT , "L ... Ver listado de personas"
PRINT
COLOR 9
PRINT , "C ... Compactar base de datos"
PRINT
COLOR 11
PRINT , "S ... Salir"

Estas son las instrucciones sólo para dibujar la pantalla. El resto para
que funcione el menú habría que progrmarlo por ejemplo con la
función INKEY$ y un SELECT CASE que según la tecla pulsada llame a
cada uno de los módulos del programa.

2.4.4 - REDEFINIR COLORES


En el apartado anterior hemos visto cómo conseguir que nuestros
programas escriban en la pantalla usando distintos colores. Esto, junto
con los semigráficos que veremos en el apartado siguiente, es más
que suficiente para hacer que nuestros programas tengan una interfaz
en modo texto bastante conseguida.

Observando la paleta VGA de 16 colores se puede comprobar que los


colores están un poco "descoloridos". No es que al monitor le pase
nada cuando entramos a MS-DOS, es que son los que hay y están
definidos así. También nos puede pasar que queramos usar
determinados colores, por ejemplo para construir un logotipo, y no nos
venga bien ninguno de los que tenemos.

Esto se puede arreglar un poco. El modo de pantalla de texto nos


permite usar 16 colores SIMULTÁNEAMENTE, pero estos no tienen
porqué ser siempre los mismos. Los podemos cambiar fácilmente por
cualquier otro de esta paleta más amplia de 64 colores.

Podemos intercambiar cualquiera de los 16 atributos de color que


tenemos disponibles por uno de estos 64 colores usando la siguiente
instrucción:

PALETTE original, nuevo

Vamos a ver lo que significa esto. Puede dar lugar a confusiones:

 Original es el ATRIBUTO de color que queremos cambiar (Un


número entre 0 y 15)
 Nuevo es el color de esta paleta que queremos usar (Un
número entre 0 y 63)

Supongamos que queremos cambiar el atributo 3 (Que originalmente


es un azul grisaceo muy feo) por un celeste más bonito que aparece
en nuestra paleta con el índice 43. No habría más que hacer:

PALETTE 3, 43

Y a partir de ahora cada vez que usemos la instrucción

COLOR 3

se escribirá con nuestro celeste. El 3 ya no es el azul grisaceo feo.

Podemos intercambiar los colores todas las veces que queramos e


incluso asignar valores repetidos a distintos atributos. Lo que hay que
tener en cuenta es que sólo vamos a ver en la pantalla 16 colores a la
vez, pero estos podrán ser cualquiera de los 64.
Otra cosa que hay que tener en cuenta es que los cambios hechos con
la instrucción PALETTE también afectan a todo lo que ya haya escrito
en la pantalla del color que hemos cambiado. Imagina que hay escrito
algo en color 10 original (Verde fuerte), y ahora asignamos al atributo
10 el color de paleta 51 para seguir escribiendo en verde más suave.
Automáticamente todo lo escrito en verde fuerte pasa a ser verde
suave. Para poder ver los dos verdes a la vez habría que usar otro
atributo y dejar el 10 como estaba. Con un poco de práctica haciendo
esto se puede conseguir que determinados textos se enciendan o se
apaguen poco a poco (Cambiando los colores por otros cada vez más
oscuros o más claros dentro de un bucle), algunos efectos interesantes
como ocultar el rótulo "Presione cualquier tecla y continúe".

La instrucción PALETTE no tiene ningún interés para aprender a


programar, pero nos puede servir para dar a nuestros programas un
toque personal o extraño al utilizar combinaciones de colores que se
salen de los 16 tan típicos de la amplia mayoría de programas de MS-
DOS. Hay que recordar que como fondo sólo podemos usar los ocho
atributos primeros (0 a 7), pero ya podemos conseguir cosas como
escribir sobre fondo amarillo o blanco. Pero con cuidado!, que no haya
que usar gafas de sol delante del ordenador.

2.4.5 - CARACTERES SEMIGRÁFICOS


Ya sabemos escribir en colores. Con un poco de idea ya podemos
dibujar recuadros aplicando distintos colores de fondo a distintas zonas
de la pantalla, pero todavía podemos llegar un poco más allá. Podemos
dibujar recuadros con bordes y líneas horizontales, verticales y
esquinas usando unos caracteres especiales que nos permiten
"ensamblar" las líneas en la pantalla como si se tratara de un puzzle:
Los caracteres SEMIGRÁFICOS, casi gráficos.

Estos caracteres no los tenemos en el teclado, y por lo tanto para


conseguirlos habrá que recurrir a sus códigos ASCII, que son los
siguientes:
Recordemos que para introducir un código ASCII hay que pulsar
simultáneamente la tecla alternativa (ALT) y el código correspondiente
en el bloque numérico situado a la derecha del teclado.

Para demostrar la utilidad de los caracteres semigráficos vamos a


crear una nueva portada para nuestro programa de agenda. Va a ser
esta, que usa también colores personalizados.

Como se puede ver, bastante conseguida. Ya no tiene casi nada que


envidiarle a la interfaz de un programa comercial de MS-DOS. Lo que
hace falta es que todo lo demás también funcione perfectamente y sea
útil.

Ahora vamos con el listado del programa necesario para dibujar esto:

PALETTE 0, 32
PALETTE 3, 11
PALETTE 4, 8
PALETTE 5, 17
PALETTE 7, 63
PALETTE 10, 4
PALETTE 11, 52
PALETTE 12, 36
PALETTE 13, 38
PALETTE 14, 54
COLOR 15, 3
CLS
COLOR 15, 4
LOCATE 2, 11: PRINT ""; STRING$(57, 205); ""
LOCATE 3, 11: PRINT "|"; SPACE$(57); "|"
LOCATE 4, 11: PRINT ""; STRING$(57, 205); ""
COLOR 14, 4: LOCATE 3, 14
PRINT "A G E N D A S U P E R B A R A T
A"
COLOR 1, 7
LOCATE 7, 20: PRINT "-------------------------------------
--"
LOCATE 8, 20: PRINT "| A ...
|"
LOCATE 9, 20: PRINT "| B ...
|"
LOCATE 10, 20: PRINT "| M ...
|"
LOCATE 11, 20: PRINT "-------------------------------------
--"
LOCATE 12, 20: PRINT "| N ...
|"
LOCATE 13, 20: PRINT "| D ...
|"
LOCATE 14, 20: PRINT "| T ...
|"
LOCATE 15, 20: PRINT "| E ...
|"
LOCATE 16, 20: PRINT "-------------------------------------
--"
LOCATE 17, 20: PRINT "| L ...
|"
LOCATE 18, 20: PRINT "-------------------------------------
--"
LOCATE 19, 20: PRINT "| C ...
|"
LOCATE 20, 20: PRINT "-------------------------------------
--"
LOCATE 21, 20: PRINT "| S ...
|"
LOCATE 22, 20: PRINT "-------------------------------------
--"
COLOR 0, 7
LOCATE 8, 29: PRINT "Añadir nueva persona"
LOCATE 9, 29: PRINT "Borrar persona"
LOCATE 10, 29: PRINT "Modificar persona"
COLOR 10
LOCATE 12, 29: PRINT "Buscar persona por nombre"
LOCATE 13, 29: PRINT "Buscar persona por dirección"
LOCATE 14, 29: PRINT "Buscar persona por teléfono"
LOCATE 15, 29: PRINT "Buscar persona por edad"
COLOR 12: LOCATE 17, 29: PRINT "Ver listado de personas"
COLOR 11: LOCATE 19, 29: PRINT "Compactar base de datos"
COLOR 13: LOCATE 21, 29: PRINT "Salir"
COLOR 14, 5: LOCATE 25, 1: PRINT SPACE$(80);
LOCATE 25, 10: PRINT "Curso de Programaci¢n";
COLOR 13: LOCATE 25, 46: PRINT "J.M.G.B. HECHO EN RONDA";
SLEEP

¿A que asusta? Como se puede ver, ya es un listado bastante largo.

Antes de seguir hay que aclarar un pequeño problema que tiene: En


Windows no podemos representar los caracteres semigráficos y los he
sustituido por guiones, tuberías y escuadras para tener una idea de
dónde va cada cosa. Al copiar este listado a QBasic tendrás que
sustituirlos por los correspondientes semigráficos usando la tabla de
códigos ASCII. Los del recuadro superior son de línea doble y los de
abajo sencillos.

Para empezar a desliar este listado de código tan largo hay que tener
en cuenta que es un esquema secuencial. No hay bucles ni
operaciones complejas. Así ya se ve bastante más sencillo. Si te lías
con los colores redefinidos quita las instrucciones PALETTE y usa solo
los colores normales. Así de paso te vas aprendiendo sus 16 códigos,
también te servirán para algunos otros lenguajes de programación.
Cuando esté terminado redefine los colores que quieras.

Además de los caracteres semigráficos sencillos y dobles que hemos


visto aquí, existen otros conocidos como "mixtos" que se pueden usar
en las esquinas y en las uniones de líneas horizontales y verticales
para ensamblar líneas sencillas y dobles, es lo que se ve en las
esquinas y uniones de las líneas que forman este dibujo.

Esto podría resultar interesante, lo que ocurre es que estos caracteres


semigráficos mixtos no son muy comunes y no aparecen en todas las
tablas de caracteres ASCII. En el caso de que el usuario de nuestro
programa esté utilizando una de estas tablas de caracteres extraña, no
podrá ver los caracteres estos y nuestro diseño de pantalla quedará
roto. Normalmente las esquinas de los recuadros se cambiarán por
vocales acentuadas y otros símbolos extraños, algo así como esto:

Como no nos merece la pena correr el riesgo de que nuestros diseños


de pantalla queden así de estropeados, lo mejor es evitar el uso de los
semigráficos mixtos. Aunque los veamos en nuestra tabla de
caracteres ASCII, puede que el usuario del programa no los tenga y no
pueda verlo correctamente. Lo mismo ocurre al imprimir si la
impresora utiliza una tabla de caracteres ASCII distinta. Recordar que
estamos en MS-DOS y no hay fuentes True Type ni nada parecido. Con
un poco de imaginación se pueden construir diseños de pantallas más
que aceptables usando sólo los semigráficos simples y dobles que ya
hemos visto junto con los caracteres de relleno que veremos en el
apartado siguiente.

Para terminar con los semigráficos vamos a ver un pequeño algoritmo


que usado como un procedimiento nos sirva para dibujar un recuadro
en las posiciones de pantalla que nos interesen, algo muy útil.
Lo que hace es sencillo. Dibuja un cuadro con el borde de línea doble
con la esquina superior derecha en la posición v,h y la anchura y la
altura la obtiene de las variables largo y alto. La primera línea dibuja
la parte superior, el bucle FOR dibuja la zona intermedia rellenando lo
de dentro con espacios, y la última línea dibuja el borde inferior.
Ponemos un punto y coma al final de cada línea para que si dibujamos
el recuadro en la parte baja de la pantalla no se produzca el
desplazamiento automático y se nos estropee todo el diseño de
pantalla.

Este ejemplo se podría ampliar especificando los colores en la llamada


a la función o escribiendo algún tipo de barra de título como si fuera
una ventana de las del editor de QBasic.

2.4.6 - CARACTERES ESPECIALES DE RELLENO


Además de los semigráficos también existen otros caracteres que nos
van a ser muy útiles para de diseñar las pantallas de nuestros
programas en modo texto. Son los caracteres de relleno, aquí están
sus códigos ASCII, porque tampoco los tenemos en el teclado:

Estos caracteres nos van a permitir rellenar zonas enteras de la


pantalla para evitar que se vean todo del mismo color. Los más
utilizados son los tres primeros que nos ofrecen distinta densidad de
puntos. El cuarto rellena todo con el color de primer plano, y los dos
últimos se usan sólo para cosas como dar efectos de sombras a
recuadros y poco más.

Para usar estos caracteres se suele recurrir a un bucle FOR que recorre
la pantalla por filas y dentro dibuja series del carácter correspondiente
usando la función STRING$.
Vamos con un ejemplo que va a rellenar con el carácter 177 la mitad
izquierda de la pantalla.

FOR v = 1 TO 25
LOCATE v,1: PRINT STRING(40,177);
NEXT

Una cosa que aparece al usar grandes cantidades de estos caracteres


es el llamado "efecto Moiré" (Muaré), consistente en una serie de
lineas curvas que aparecen dibujadas en la pantalla. Se produce por
una interferencia entre los puntos que forman estos caracteres de
relleno y los píxeles físicos del monitor. Si nuestro monitor nos da ese
problema, no se puede evitar, pero sí atenuar usando colores de
primer plano y de fondo que no sean demasiado distintos.

Ahora vamos con otro diseño más elaborado que nos ofrece un diseño
de fondo de este tipo:

FOR v = 1 TO 25
FOR h = 1 TO 79 STEP 2
LOCATE v, h: PRINT STRING$(2, INT(RND * 3) +
176);
NEXT
NEXT

Aquí se elige aleatoriamente uno de los tres caracteres disponibles en


el momento de dibujarlo. Se han agrupado los caracteres de dos en
dos para que salgan cuadros más o menos cuadrados. El siguiente
ejemplo es parecido, pero esta vez los cuadros aparecerán ordenados
ya que se usa un contador en vez de los números aleatorios.

c = 0
FOR v = 1 TO 25
FOR h = 1 TO 79 STEP 2
c = c + 1
IF c = 3 THEN c = 0
LOCATE v, h: PRINT STRING$(2, 176 + c);
NEXT
NEXT

El uso de estos caracteres de relleno junto con los recuadros de


colores hechos a base de semigráficos es una buena forma de dar a
nuestros programas en modo texto una apariencia bastante aceptable.

2.4.7 - CARACTERES NO IMPRIMIBLES


Si se observa la tabla de códigos ASCII se puede ver que los
caracteres que van desde el 0 hasta el 31(El 32 es el espacio en
blanco) son símbolos extraños y además no se pueden escribir usando
la tecla ALT.

Estos caracteres están asociados a teclas del teclado como Enter,


escape, tabulador o retroceso entre otras y a ordenes especiales para
el ordenador y la impresora como son saltos de página y de línea, fin
de fichero, toque de timbre, etc.

Estos caracteres no están pensados para ser dibujados en la pantalla


ni para ser impresos. Tienen asociados esos símbolos porque alguno
tenían que tener, pero ya que los tenemos ahí puede ser que en
algunos casos queramos escribirlos en la pantalla. Vamos con sus
códigos ASCII...

Y ahora vamos a ver como escribirlos. (Solo en QBasic y en los


programas de MS-DOS. En Windows no se puede)
Hay dos formas. La primera sería usar la función CHR$, por ejemplo
para dibujar un corazón bastaría con poner PRINT CHR$(4). La
segunda, más cómoda es pulsar la combinación de teclas CONTROL +
P y después, a continuación ALTERNATIVA + el código en el teclado
numérico, por ejemplo para dibujar el corazón habría que pulsar
CTRL+P y ALT+4 con lo que este símbolo quedaría directamente
dibujado en la pantalla.

Esto de CTRL+P nos vale para dibujar estos caracteres especiales en la


mayoría de programas de MS-DOS así como en el propio intérprete de
comandos (Símbolo C:\>).

Hasta aquí muy bien. Ya podemos dibujar caras, flechas y otros


símbolos en nuestras pantallas, pero no podemos olvidar que estos
caracteres son especiales y hay que tener unas ciertas precauciones.
La primera es que si un símbolo nos da algun problema como que se
nos descoloquen los demás, pues directamente evitar usarlo y nada
más. Otras precaucione son no usar los símbolos que no llevan
asociado ningún carácter como son el de código ASCII 0 y tener en
cuenta que cada vez que nuestro programa dibuje un ASCII 7 se oirá
un pitido en el altavoz interno o bien sonará el sonido predeterminado
de Windows.

Estos caracteres los usaremos exclusivamente en instrucciones PRINT


y no deberemos almacenarlos en ficheros, especialmente en ficheros
secuenciales . Si los intentaremos imprimir van a provocar fallos en la
impresora. De hecho si intentas imprimir tal cual está la tabla de
caracteres ASCII que viene en la ayuda de QBasic y que incluyes estos
símbolos observarás cosas como alguna línea se rompe o incluso que
se hace un salto de página y un trozo de la tabla se imprime en la
siguiente hoja. Esto se debe a que se han encontrado caracteres de
saltos de línea y de página que la impresora ha interpretado como lo
que son.

También hay que tener precaución si estos caracteres aparecen de


forma literal en las instrucciones del código fuente del programa. Si
imprimes dicho código puedes encontrarte con problemas similares
cada vez que salga uno de estos caracteres. Por lo tanto si piensas
imprimir el código fuente del programa utiliza funciones CHR$ en lugar
de los caracteres literales. También hay que saber que estos
caracteres no se pueden ver en Windows, por lo que si abres el fichero
BAS del código de tu programa en un editor de Windows como por
ejemplo el bloc de notas, directamente no los verás o los verás
marcados como un cuadro negro.

2.4.8 - ESMÁILIS y ASCII-ART


Para enriquecer un poco más las pantallas de nuestros programas en
modo texto podemos usar la forma que tienen los propios caracteres
para hacer pequeños dibujos.

Lo más sencillo de esto es lo que se conoce como esmáilis


(Correctamente se escribe smiley) y que estamos acostumbrados a
utilizar en los foros y chats de Internet. Algunos de los más comunes y
de significado conocido son los siguientes:

:-) :-( :-D :-o X-D

Se pueden encontrar listados muy extensos llenos de estos símbolitos


junto con su significado, pero de todas formas puede que estos
esmáilis sean muy poca cosa para decorar nuestros programas. Por
eso podemos recurrir a diseños más complicados conocidos como
ASCII-ART. Vamos con unos ejemplos...

__
/ /\
/ / \
/ / /\ \
/ / /\ \ \
/ /_/__\ \ \
/________\ \ \
\___________\/

1111111111111111111111111111
1110000001111111111000000111
1100000000011111100000000011
1100000000001111000000000011
1100000000000110000000000011
1100000000000000000000000011
1110000000000000000000000111
1111000000000000000000001111
1111100000000000000000011111
1111111000000000000001111111
1111111110000000000111111111
1111111111000000001111111111
1111111111100000011111111111
1111111111110000111111111111
1111111111111001111111111111
1111111111111111111111111111

./ | _________________
/ / / __________ //\_
/' / | (__________) ||.' `-
.________________________
/ / | __________ ||`._.-
'~~~~~~~~~~~~~~~~~~~~~~~~`
/ \ \__(__________)__\\/
| `\
| |
___________________
| |___________________...-------'''- - - =- - =
- = `.
/| | \- = = - -= - = - =-
= - =|
( | | |= -= - = - = - = - =--=
= - = =|
\| |___________________/- = - -= =_- =_-=_- -=_=-
=_=_= -|
| | ```-------
...___________________.'
|________|
\ / _
| | ,,,,,,, /=\
,-' `-, /\___________ (\\\\\\\||=|
| | \/~~~~~~~~~~~` ^^^^^^^ \=/
`--------' `
Podemos encontrar montones de ellos o atrevernos nosotros mismos a
dibujar los nuestros (Por ejemplo un logotipo).

Desde el punto de vista de la programación lo único que hay que tener


en cuenta es que hay que dibujarlos línea por línea usando una
instrucción PRINT para cada una, y que en muchos casos no podemos
olvidar incluir espacios a la izquierda de determinadas líneas para que
el dibujo no se estropee. Por ejemplo, para dibujar la primera de las
anteriores figuras habría que hacer...

PRINT " __"


PRINT " / /\"
PRINT " / / \"
PRINT " / / /\ \"
PRINT " / / /\ \ \"
PRINT " / /_/__\ \ \"
PRINT "/________\ \ \"
PRINT "\___________\/"

Estos dibujos fueron concebidos inicialmente para ser impresos en


impresoras antiguas que no soportaban la impresión de gráficos, por lo
que en principio es suficiente con que sean en blanco y negro, pero
también nos podemos molestar en ponerlos de varios colores
dibujándolos con distintas instrucciones PRINT.

También podemos construir grandes letras para dibujar los títulos y


rótulos de nuestros programas, por ejemplo...

_ ___ ___ ___ ___ ___ ___ _ _ ___ _ _ ___ __


__ _ _ /\/| ___
/_\ | _ )/ __| \| __| __/ __| || |_ _| | | |/ / | |
\/ | \| ||/\/ / _ \
/ _ \| _ \ (__| |) | _|| _| (_ | __ || | || | ' <| |__|
|\/| | .` | \| | (_) |
/_/ \_\___/\___|___/|___|_| \___|_||_|___\__/|_|\_\____|_|
|_|_|\_|_|\_|\___/

___ ___ ___ ___ _____ _ ___ ____ ____ ____


______
| _ \/ _ \| _ \/ __|_ _| | | \ \ / /\ \ / /\ \/ /\ \ /
/_ /
| _/ (_) | /\__ \ | | | |_| |\ V / \ \/\/ / > < \ V
/ / /
|_| \__\_\_|_\|___/ |_| \___/ \_/ \_/\_/ /_/\_\ |_|
/___|

Estas letras están construidas usando principalmente barras y


símbolos de subrayado. También es común construirlas usando los
caracteres semigráficos o el símbolo # repetido (Esto último es lo que
hace la instrucción banner de Linux).

Para dibujar un rótulo con las letras anteriores podemos hacerlo de


forma literal con cuatro instrucciones PRINT (Una por cada línea) y los
trozos de letras entre comillas...

PRINT " _ _ ___ ___ _ _ ___ ___ _ _ ___ ___


_ _ ___ _"
PRINT "| || || __|/ __|| || |/ _ \ | __|| \| | | _ \/ _
\| \| || \ /_\"
PRINT "| __ || _|| (__|| __ | (_) | | _||| .` | | / (_)
| .` || |) |/ _ \"
PRINT "|_||_||___|\___||_||_|\___/ |___||_|\_|
\_|_\\___/|_|\_||___//_/ \_\"

Pero ya que estamos programando, lo más cómodo sería tener un


procedimiento que nos dibujara el rótulo que queramos en la posición
que le digamos solo con hacer una llamada del tipo...

DibujaRotulo "Hecho en Ronda", 5, 10

Habría que construir un programa con muchas instrucciones Mid$ que


vaya construyendo las letras línea por línea. Se puede encontrar en la
sección de programas terminados de esta web.
TEMA 2.5
GRÁFICOS
 2.5.1 - Introducción a los interfaces gráficos
 2.5.2 - Los modos de pantalla en QBasic
 2.5.3 - Rendimiento en modo gráfico

2.5.1 - INTRODUCCIÓN A LOS INTERFACES


GRÁFICOS
Todos los resultados de los programas que hemos hecho hasta ahora
en este tutorial de programación los hemos podido ver en la pantalla a
través de lo que denominamos "Interfaz de texto", primero sólo en
blanco y negro, y más adelante en colores. A partir de aquí vamos a
tener la posibilidad de usar la pantalla como una matriz de puntos de
colores (Píxeles) donde tendremos la podremos dibujar lo que
queramos usando las instrucciones adecuadas.

Dos conceptos muy importantes que hay que tener en cuenta a la hora
de trabajar con interfaces gráficos son la profundidad de color y la
resolución. La profundidad de color se refiere al número máximo de
colores que vamos a poder presentar en la pantalla simultáneamente.
La resolución es el número de píxeles que forman la pantalla, a mayor
resolución mayor calidad de imagen.

En los apartados siguientes veremos primero la forma de iniciar el


modo de pantalla gráfica de QBasic, algo muy sencillo, y después
cómo dibujar figuras en la pantalla para que hagan nuestros
programas y juegos más vistosos.

2.5.2 - LOS MODOS DE PANTALLA EN QBASIC


Olvidémonos un momento del tutorial de programación. Imaginemos
que estamos trabajando con Windows, un interfaz gráfico muy
moderno. Nuestra pantalla estará trabajando seguramente con una
resolución de 800 por 600 pixeles y una profundidad de color de 16.7
millones de colores. De esta forma, además de las letras que aparecen
por ejemplo en las barras de título de las ventanas, podemos ver
dibujados en la pantalla los bordes de las propias ventanas, los iconos,
los botones, y hasta una imagen de fondo en el escritorio que
posiblemente sea una fotografía digitalizada con muchos colores.

En medio de todo este despliegue de color pulsamos sobre un icono


que nos va a lanzar un juego que tenemos instalado en nuestro
ordenador. Este juego al empezar a funcionar nos cambia la resolución
a 320 por 200 puntos y la profundidad de color a 256 colores.
Podremos ver que como la resolución ha descendido, los bordes de
ciertas figuras redondeadas aparecen "pixelados", es decir, formando
escalones. Podemos ver mejor los pixeles porque ahora son más
grandes. También al reducir el número de colores las imágenes ya
tienen un aspecto "como de dibujos animados". Esto es suficiente para
un juego sencillo en la mayoría de los casos, pero para ver una
fotografía digital como la que teníamos en el fondo del escritorio ya
nos vendrían cortos.

En medio de nuestra interesante partida con el juego, el ordenador se


bloquea (algo muy común en Windows) y nos vemos obligados a
reiniciarlo. Tras el arranque aparece un mensaje que nos indica que
Windows va a funcionar en "modo a prueba de fallos". Una de las
cosas que habrán cambiado será la resolución, que se ha reducido a
640 por 480 pixels y el número de colores que se ha quedado en 16.
Podremos ver como la imagen del fondo del escritorio se ve muy fea y
los iconos se ven algo más gordos que antes y con unos colores un
poco extraños. Al abrir alguna ventana veremos que todo aparece un
poco más grande porque hay menos resolución.

Ya volviendo al curso de programación, vamos a ver ahora cómo


conseguir que nuestros programas de QBasic trabajen con más o
menos resolución y más o menos profundidad de color según nos
convenga en cada caso.
Ambos valores no los podemos cambiar de forma independiente, sino
que van relacionados en lo que se conoce como "Modos de pantalla".
Estos son los más usados:

PROFUNDIDAD
MODO PRESENTACIÓN TEXTO RESOLUCIÓN
DE COLOR

Interfaz de 80 x 25
SCREEN 0 - 16 colores
texto caracteres

SCREEN 80 x 30 640 x 480


Interfaz gráfico 16 colores
12 caracteres pixels

SCREEN 40 x 25 320 x 200


Interfaz gráfico 256 colores
13 caracteres pixels

Además de estos tres, hay otros más, pero han quedado en desuso ya
que son monocromáticos (Blanco y negro) o bien de cuatro colores. Se
usaban en los ordenadores más antiguos y en los actuales es posible
que ya no lleguen ni a funcionar (Producirán un error de tiempo de
ejecución al intentar iniciarlos).

También existen otros modos con más resolución y con más colores,
pero QBasic no los soporta. Para aprender a programar, que es lo que
estamos haciendo aquí, estos tres son suficientes.

En cada caso seremos nosotros los responsables de seleccionar cual es


el mejor para nuestro programa, conociendo sus ventajas e
inconvenientes.

El modo 0 es el interfaz de texto que hemos venido usando hasta


ahora, es el que QBasic usa por defecto si no le decimos que use otro.
Si no tenemos necesidad de dibujar figuras gráficas, es el más
indicado en la mayoría de los casos ya que es muy rápido.

El modo 12 es el de más alta resolución que tenemos en QBasic, nos


servirá para los casos en que tengamos que dibujar diagramas o
figuras más complejas con muchas líneas. Su principal inconveniente
es que es bastante más lento que los otros porque el ordenador tiene
que manejar muchos puntitos para dibujar la pantalla. Los colores
para el modo 12 son los mismos que la paleta básica normal del
interfaz de texto, es decir, estos.
Hay que tener en cuenta que en el modo 12 no disponemos de los
colores intermitentes como teníamos en la pantalla de texto, y que
para escribir letras tenemos ciertas limitaciones que veremos en
apartados posteriores.

El modo 13 es de muy baja resolución, por lo tanto las imágenes se


verán muy "pixeladas" y su tamaño en puntos no podrá ser muy
grande ya que "no cabrán en la pantalla". Como ventaja principal tiene
la posibilidad de usar una paleta de 256 colores, algo muy valioso para
los juegos, y además es bastante rápido, ya que al haber 256 colores
cada pixel de la pantalla se corresponde exactamente con un byte en
la memoria gráfica, y las operaciones que tiene que hacer el ordenador
para dibujarla son más sencillas. Este modo de pantalla se ha usado
durante años para la programación de montones de juegos para MS-
DOS que todavía hoy nos ofrecen una calidad gráfica más que
aceptable. Para el modo 13 los colores son los de la siguiente paleta:

Como se puede ver, los primeros 16 son iguales a los VGA, a


continuación una escala de grises, y los siguientes van agrupados de
24 en 24, cosa que se puede utilizar para hacer efectos como
degradados o sombras. Igualmente en el modo 13 tampoco hay
colores intermitentes.

El cambio de modo de pantalla en QBasic es extremadamente sencillo,


basta con usar la siguiente instrucción:

SCREEN modo

Dónde modo es uno de los numeritos que hemos visto antes: 0, 12 o


13. El efecto que tiene esta instrucción es el borrado completo de la
pantalla y que a partir de ahora podremos escribir y dibujar
adaptándonos a la nueva resolución y a los colores que tenemos.
Normalmente esto lo hacemos una vez nada más al principio del
programa (si es para modo texto no hace falta) y ya no lo cambiamos
más, aunque si hace falta se puede hacer todas las veces que sea
necesario.

En los siguientes apartados vamos a ver cómo se pueden dibujar


figuras en la pantalla gráfica una vez que la hayamos activado usando
la instrucción SCREEN.

Un efecto que notaremos si tenemos un monitor moderno digital será


que se apague y no volvamos a ver imagen alguna hasta pasado un
instante. Lo mismo ocurrirá cuando termine un programa en modo
gráfico y volvamos al editor de QBasic que es en modo texto. Si
estamos trabajando con QBAsic en una ventana de Windows, para
entrar a modos gráficos se pasa obligatoriamente a modo de pantalla
completa, y cuandotermine el programa y volvamos al editor de
QBasic posiblemente lo encontremos también a pantalla completa.
Para solucionar este problema consulta el anexo 3.3 de este totorial.

2.5.3 - RENDIMIENTO EN MODO GRÁFICO


Como podremos comprobar a lo largo de estos temas dedicados a los
gráficos, el rendimiento de estas operaciones es bastante bajo
independientemente de que tengamos un ordenador súper moderno o
la mejor tarjeta gráfica del mercado con una gran cantidad de
memoria.

Este bajo rendimiento se debe principalmente a que QBasic es un


lenguaje interpretado y a que las instrucciones no están optimizadas
para alcanzar gran velocidad. Todas las instrucciones se reducen a
dibujar puntos en la pantalla y QBasic no es capaz de dibujarlos lo
suficientemente rápido.

Los modernos videojuegos en tres dimensiones y con alta resolución,


millones de colores, efectos de iluminación transparencias, reflejos,
sombras... se reducen a eso, a dibujar en la pantalla puntos de
colores. Los programas calculan cual es el color que tiene que tener
cada punto y estos se colocan a gran velocidad como valores
numéricos en la memoria gráfica del ordenador. El adaptador gráfico
va leyendo esa información para dibujar la pantalla entera muchas
veces por segundo, mientras más mejor.

Los programas y las bibliotecas gráficas que permiten hacer eso


(openGL, directX...) están escritas directamente en lenguaje
ensamblador y depuradas para que todo se haga de la forma más
rápida posible. También los controladores de nuestro sistema gráfico
están escritos de forma que trabajan de forma sincronizada con estos
programas para obtener el mayor rendimiento, mientras que QBasic
utiliza el modo de pantalla VGA que se usa desde hace años y no
aprovecha para nada estas tecnologías. No sabe ni que existen.

Podemos depurar un poco nuestros programas y controlar cosas como


que dentro de los bucles que se repiten mucho se ejecuten el menor
número de instrucciones y cálculos matemáticos posibles, pero por
muchas vueltas que le demos a nuestro código, en QBasic no podemos
esperar obtener buenos resultados a una velocidad aceptable. Nos
tendremos que conformar con sencillos dibujos que nos van a servir
para aprender a programar y poder dar el salto a un lenguaje más
avanzado que nos permita usar todas estas nuevas tecnologías.
TEMA 2.6
TRAZADO DE FIGURAS GRÁFICAS
 2.6.1 - El cursor gráfico
 2.6.2 - Puntos. Instrucción PSET
 2.6.3 - Líneas y rectángulos. Instrucción LINE
 2.6.4 - Círculos y óvalos. Instrucción CIRCLE
 2.6.5 - Rellenar áreas. Instrucción PAINT
 2.6.6 - Dibujar figuras complejas. Instrucción DRAW

2.6.1 - EL CURSOR GRÁFICO


Hay varias instrucciones específicas que nos permiten dibujar en la
pantalla en modo gráfico. Antes de empezar a estudiar su utilización
es importante darse cuenta de que en la pantalla en modo gráfico
existe, además del cursor de texto, un cursor gráfico. Este sería
comparable a "la punta de un lápiz que va dibujando cosas".

Al iniciar uno de los modos gráficos con la instrucción SCREEN el


cursor gráfico, siempre invisible, se sitúa en el centro de la pantalla, y
posteriormente, conforme vayamos dibujando cosas con las
instrucciones que vamos a ver a continuación, se irá moviendo para
situarse siempre al final de la última figura que hayamos dibujado.
Algo parecido a lo que ocurría con el texto, pero que es conveniente
aclarar.

2.6.2 - PUNTOS. INSTRUCCIÓN PSET


La primera instrucción que vamos a ver en modo gráfico nos va a
permitir dibujar un punto en la pantalla en la posición que nosotros
indiquemos, y del color que nosotros queramos. Su sintaxis es:

PSET (h,v),color

Los valores de h y v, siempre entre paréntesis y separados por una


coma son las coordenadas horizontales y verticales del lugar en en el
que vamos a dibujar el punto. Recordemos que la esquina superior
izquierda de la pantalla tiene las coordenadas 0,0 y las de la esquina
inferior derecha dependen del modo de pantalla elegido, en el modo
12 serán (639,479) y en el modo 13 serán (319,199).
Observa que no son (640,480) o (320,200) porque, a diferencia de las
coordenadas de texto, empezamos por cero y no por uno.

Si nos salimos de la pantalla especificando coordenadas mayores o


menores de los límites permitidos, no ocurre nada, simplemente que el
punto se dibujará fuera de la pantalla y no lo veremos.

El valor que elijamos para el color depende de la paleta asociada al


modo de pantalla que tengamos activo: Si es el modo 12 podremos
usar un valor entre 0 y 15 o si es el modo 13 tendremos disponibles
los valores desde 0 a 255. Evidentemente, si dibujamos un punto
negro sobre fondo negro no se verá. Si no especificamos un color se
usará el de la anterior orden de dibujo, si era la primera orden se
usará el color blanco.

En la anterior sintaxis poníamos unas coordenadas absolutas, es decir,


el cursor gráfico se movía hasta la posición indicada y se dibujaba el
punto. También es posible indicar una posición relativa a la posición
actual del cursor gráfico usando la palabra STEP antes del paréntesis
de las coordenadas:

PSET STEP(0,0), 14

Esta instrucción dibujará un punto amarillo en la posición actual del


cursor gráfico, es decir, al final de la última figura dibujada o en el
centro de la pantalla si es la primera instrucción de dibujo. Vamos con
unos cuantos ejemplos más de esto de las posiciones relativas:

SCREEN 13
PSET (200,100), 10
PSET STEP(0,40), 14
PSET STEP(60,0), 12
PSET STEP(-20,-20), 9

Este trozo de código activará el modo 13 y dibujará un punto verde


exactamente en la posición (200,100), es decir, 200 pixels a la
derecha y 100 pixels más abajo de la esquina superior izquierda de la
pantalla. Seguidamente se dibujará un punto amarillo 40 pixels más
abajo del anterior, uno rojo 60 pixels a la derecha del amarillo, y
finalmente uno azul 20 pixels más arriba y 20 pixels a la izquierda del
rojo. Como se puede ver, al usar coordenadas relativas, ya es útil usar
valores negativos.

Por supuesto, los valores de las coordenadas y de los colores no tienen


porqué ser expresiones constantes, pueden ser el resultado de una
expresión matemática con o sin variables, por ejemplo...

PSET (inicio+largo, inicio+alto), colorActual

O algo muy útil y muy normal es que las instrucciones de dibujo están
dentro de bucles que las repitan. Normalmente no nos sirve de nada
dibujar un punto solo, siempre dibujaremos muchos desde dentro de
bucles, por ejemplo:

SCREEN 12
FOR n=100 to 500 STEP 5
PSET (n,50),14
NEXT

Esta instrucción dibuja en la pantalla una sucesión de puntos amarillos


alineados. Si nos salimos no pasa nada, simplemente que se dibujarán
fuera y no los veremos, pero si indicamos un color de fuera de la
paleta del modo de pantalla actual se producirá un error de tiempo de
ejecución y el programa se parará.

2.6.3 - LÍNEAS Y RECTÁNGULOS.


INSTRUCCIÓN LINE
La siguiente instrucción que vamos a ver nos va a permitir dibujar una
línea recta en la pantalla. Su sintaxis es...

LINE (h1,v1)-(h2,v2),color

Aquí debemos especificar dos coordenadas: la del principio de la línea


recta y la de su final. Tras dibujar la línea el cursor gráfico
permanecerá en el final, es decir, en la posición de pantalla (h2,v2).
Igual que en el caso anterior, podemos especificar coordenadas
relativas usando la palabra STEP antes del paréntesis para una o las
dos posiciones. Esto es útil para dibujar líneas poligonales, en este
caso cada segmento se dibujaría con una instrucción LINE y todas
menos la primera empezarían con LINE STEP(0,0) para que empiecen
junto al final de la anterior, por ejemplo este código dibuja un
triángulo:

LINE (150,100)-(100,200),10
LINE STEP(0,0)-(200,200)
LINE STEP(0,0)-(150,100)

Si la última coordenada de la última instrucción es igual a la primera


coordenada de la primera instrucción, tendremos una línea poligonal
cerrada, en caso contrario quedará abierta.

Si la línea que estamos dibujando es completamente horizontal o


completamente vertical será dibujada toda continua, si es diagonal
aparecerá formando como "Escalones". Este efecto es lo que se conoce
como "pixelado" y será más visible en el modo 13 ya que al ser de
menos resolución, los pixels son "más gordos".

Esta instrucción LINE también nos permite dibujar un rectángulo (o un


cuadrado) de forma muy sencilla, simplemente añadiendo una coma y
una letra B a continuación del color, por ejemplo...

LINE (20, 20)-(70, 30), 12

Dibujaría una línea recta diagonal que va desde (20,20) hasta (70,70).
Y...

LINE (20,20)-(70,30), 12, B


dibujaría un rectángulo con una de sus esquinas en la posición (20,20)
y la opuesta en la posición (70,70). El borde del rectángulo tendrá una
anchura de un pixel y en este caso será de color amarillo.

Si queremos que el rectángulo se dibuje relleno basta con poner las


letras BF (Borde y fondo) en vez de la B sola, por ejemplo...

LINE (20,20)-(70,30), 12, BF

Si no queremos especificar el color, para que se coja el usado en la


instrucción anterior, basta con no ponerlo, quedarán dos comas juntas.

LINE (20,20)-(70,30), , BF

Un poco extraño, pero cuidado con equivocarse, porque si ponemos...

LINE (20,20)-(70,30), BF

Se dibujará una línea recta entre las dos posiciones, y será del color
que se almacene en una nueva variable llamada BF que seguramente
será el cero que es negro y puede que no veamos. Como norma
general es mejor poner el color siempre.

Al usar la segunda coordenada relativa en los recuadros, sean rellenos


o no, podemos calcular fácilmente su tamaño exacto sin tener que
hacer cálculos, por ejemplo:

LINE (20,20)-STEP(99,99), 14, BF

Nos dibujaría un cuadrado amarillo relleno de 100 por 100 píxeles (99
+ 1) con la esquina superior izquierda en la posición que indicamos en
la primera coordenada sin tener que calcular nosotros donde iría la
otra esquina.
Hay que tener en cuenta que en el modo de pantalla 13 los cuadrados
siempre los vamos a ver un poco más altos que anchos aunque
hayamos puesto las longitudes de sus lados iguales, ya que la
resolución de este modo de pantalla (320x200 píxels) no es
proporcional a las dimensiones físicas del monitor que siempre
guardan una proporción de 3:4. Para dibujar un cuadrado que se vea
cuadrado de verdad habría que dibujarlo con una altura un poco
menor que su anchura para que se disimule el error, pero tampoco no
merece mucho la pena complicarse tanto en la mayoría de los casos.
Por último, podemos conseguir que las líneas y los cuadros sin relleno
(B) se dibujen de forma discontinua, es decir, unos puntos sí y otros
no. Para hacer esto se pone al final del todo un numerito que nos
indica el patrón de puntos a utilizar. Este valor es un número de 16
bits (entre 0 y 65.535) que lo podemos poner en hexadecimal para no
liarnos comenzándolo con los caracteres &H. (entre &H0000 y &HFFFF)
Vamos con unos ejemplos:

LINE (20,20)-(70,30), 14,B ,&H5555

Se dibujaría un rectángulo amarillo con el borde punteado, si no


queremos poner el color haríamos:

LINE (20,20)-(70,30), ,B ,&H5555

Y si queremos que sea una línea y no un rectángulo no tendríamos que


poner la letra B, pero sí su correspondiente coma, sería así:

LINE (20,20)-(70,30), , ,&H5555

Esto no se utiliza mucho. Si queremos otro patrón de puntos distinto


basta con ir probando números. Los más útiles pueden ser &H5555
que nos van dibujando "uno sí y otro no" y &HFF00 que nos harían un
efecto parecido a la línea discontinua de una carretera. Si queremos
una línea a dos colores se puede hacer dibujando una normal de un
color y justo encima una discontinua del otro color, por ejemplo...

LINE (100,100)-(300,200), 15
LINE (100,100)-(300,200), 12, ,&HFF00

esto nos dibujaría una línea a trozos blancos y rojos.

2.6.4 - CÍRCULOS Y ÓVALOS.


INSTRUCCIÓN CIRCLE
La siguiente instrucción nos va a servir para dibujar una circunferencia
(sólo la línea del contorno) en nuestra pantalla gráfica.
CIRCLE (h, v), radio, color

En este caso se dibujará una circunferencia con el centro situado en la


posición (h,v) y con el radio (medido en píxels) que indiquemos. Tras
el trazado de la misma el cursor de gráficos permanecerá en el centro.
Por ejemplo:

CIRCLE (100, 200), 50, 12

Dibujará una circunferencia de color rojo con el centro en la posición


(100,200) y con un radio de 50 pixels. Si no especificamos el color se
tomará el de la anterior instrucción de dibujo, como en los casos
anteriores. También tenemos la posibilidad de usar una coordenada
relativa anteponiendo la palabra STEP al paréntesis, por ejemplo...

LINE (40, 10)-(100,200),14


CIRCLE STEP(0,0),10

...dibujaría una línea recta inclinada de color amarillo y en su extremo


final una circunferencia del mismo color con un radio de 10 píxels.

La instrucción CIRCLE también nos permite dibujar arcos de


circunferencia, aunque esto no se usa mucho, no hay porqué dibujarla
siempre entera. Para hacerlo hay que poner a continuación del color el
ángulo inicial y final del arco que queremos dibujar. Los ángulos van
en radianes, la circunferencia completa tiene unos 6.28 radianes (2
veces pi) y el ángulo 0 está la la derecha, el pi/2 radianes (90º) está
arriba, el pi radianes (180º) está a la izquierda y el 3/2 pi radianes
(270º) está abajo. De esta forma, por ejemplo, para dibujar la mitad
superior de una circunferencia (desde 0 hasta pi radianes) habría que
poner:

CIRCLE (100, 100), 40, 14, 0, 3.14

Y si nos queremos ahorrar el color y que lo coja de la instrucción


anterior, pues no lo ponemos.

CIRCLE (100, 100), 40, , 0, 3.14

Por último, también podemos dibujar óvalos en vez de circunferencias.


Para hacerlo añadimos otro parámetro más a continuación. Este
parámetro será la proporción entre el radio horizontal y el vertical. Por
ejemplo, si es dos, el radio vertical será del doble que el horizontal, y
si es 0.5 el radio vertical será la mitad del horizontal y la
circunferencia aparecerá aplastada. El radio que nosotros hayamos
puesto en la instrucción será el mayor de los dos, es decir, el
horizontal si este valor es menor que 1 o el vertical si nuestro valor de
aspecto es mayor que uno.

En este ejemplo dibujamos sólo la mitad izquierda de una


circunferencia verde aplastada que sería cuatro veces más ancha que
alta si estuviera entera:

CONST PI = 3.14;
CIRCLE (200, 200), 160, 9, PI/2, 3*(PI/2),0.25

Si queremos dibujar el mismo óvalo entero (Algo menos rebuscado),


no ponemos los valores de principio y fin del arco, pero sí tenemos que
respetar sus posiciones poniendo las comas:

CIRCLE (200, 200), 160, 9, , ,0.25

El dibujo de circunferencias no nos permite rellenarlas (Eso lo haremos


en el apartado siguients), así como tampoco el trazado de una línea
punteada. Las últimas cosas que hemos visto de los arcos y de los
óvalos ya no serán aplicables en Visual Basic. En todo caso las
circunferencias se dibujan muy pixeladas, especialmente si son de
pequeño tamaño.
Hay que tener en cuenta que en el modo de pantalla 13 las
circunferencias no saldrán redondas, siempre un poco más altas que
anchas, ya que su resolución (320x200 píxels) no es proporcional a las
dimensiones físicas del monitor que siempre guardan una proporción
de 3:4.
2.6.5 - RELLENAR ÁREAS.
INSTRUCCIÓN PAINT
También es posible conseguir que una parte de nuestra pantalla se
rellene de un color o de un patrón. Sería algo parecido a la
herramienta "Cubo de pintura" que tenemos en la mayoría de
programas de dibujo como es el caso del Paint de Windows, pero con
alguna diferencia.

El funcionamiento de la herramienta "Cubo de pintura" del Paint


consiste en que al pulsar sobre una parte de la pantalla con esta
herramienta, todos los píxels adyacentes del mismo color cambiarán al
nuevo color. Se produce un efecto como que la "pintura" se va
extendiendo, sólo que es muy rápido y no lo notamos.

En QBasic la instrucción que tenemos para hacer esto es la siguiente:

PAINT (h, v), colorrelleno, colorlímite

Al ejecutar esta instrucción se produciría un efecto parecido a "Echar


pintura del color colorrelleno en la posición (h, v) y esta se iría
extendiendo por todos los sitios hasta encontrar píxeles del color
colorlímite".
Una gran diferencia con los programas de dibujo es que el color se va
extendiendo hasta quedar encerrado por píxeles del colorlímite,
independientemente de los otros colores que se encuentre por el
camino, a los que cubrirá.

Para especificar una posición relativa también podemos usar


coordenadas relativas anteponiendo la palabra STEP como en casos
anteriores.

Vamos con un ejemplo que rellenaría un círculo, algo muy común:

SCREEN 12
CIRCLE (100, 100), 40, 12
PAINT (100,100), 14, 12

Lo primero que hacemos es dibujar un círculo de color rojo (12) sobre


el fondo de la pantalla que es negro porque no hay nada más
dibujado. A continuación echamos pintura amarilla (14) dentro del
círculo y le decimos que pare cuando encuentre píxeles rojos (12), con
lo que la pintura no llegará a salirse del círculo.

El color puede ser el mismo que el color límite, pero el color límite no
puede ser el mismo que el color de fondo que tiene el sitio donde
empezamos a pintar, ya que entonces solamente se dibujaría un
punto.

Esta instrucción puede dar lugar a que la "pintura" se salga y llene


toda la pantalla, vamos con un ejemplo:

SCREEN 12
CIRCLE (100, 100), 40, 12
LINE (120, 120)-STEP(50, 50), 9, B
PAINT (100, 100), 10, 12

Primero dibujamos una circunferencia roja, después un cuadrado azul


encima de la circunferencia, y al rellenar desde dentro de la
circunferencia observaremos como el color verde "se sale" de la
circunferencia y llena toda la pantalla, borrando también el recuadro
azul. Algo así sería:

Puedes observar fácilmente que el color verde se ha salido del círculo


por los dos agujeros que quedan a la derecha y abajo. Estos se
produjeron al dibujar el cuadrado azul, borrando algúnos píxeles rojos.

Para evitar este problema no hay más que planificar cuidadosamente


el orden en que se van dibujando los distintos objetos gráficos y las
posiciones exactas donde usamos la instrucción PAINT.

Por último decir que en vez de un color sólido podemos rellenar


usando un patrón de relleno, en este caso habría que sustituir el color
por una cadena de ocho caracteres que codificaría el estilo del patrón.
Para ver más detalles consultar la ayuda en pantalla de QBasic. No
profundizaremos más en el tema porque de todas formas la instrucción
PAINT ya no podrá ser utilizada en Visual Basic.
2.6.6 - DIBUJAR FIGURAS COMPLEJAS.
INSTRUCCIÓN DRAW
En los apartados anteriores hemos visto como podemos dibujar figuras
elementales como puntos, líneas, recuadros y circunferencias. Para
dibujar figuras más complejas podemos optar entre usar una serie de
instrucciones de estas o bien usar una sola de la que vamos a ver a
continuación.

DRAW CadenaComando$

Esta instrucción es completamente distinta a las anteriores. En


principio no aparecen coordenadas y ¿Qué es eso de
CadenaComando$...? Ahora lo vamos a ver.

La instrucción DRAW lo que hace es mover el cursor de gráficos a


partir de la posición actual donde se encuentre para ir dibujando lo
que nosotros queramos. Y para saber lo que nosotros queremos que
dibuje se utiliza esta CadenaComando$ que no es más que una
expresión de cadena o una cadena de caracteres entre comillas que
contiene algunos de los siguientes caracteres para que QBasic los
interprete y sepa lo que tiene que dibujar.

Estos son los caracteres que hay que poner para que el cursor gráfico
se mueva en las distintas direcciones y vaya dibujando como si fuera
la punta de un lápiz (Up, Down, Left, Right, E,F,G,H):

Con un ejemplo se ve más claro. Las letras pueden ser tanto


mayúsculas como minúsculas. Pero se prefieren en minúsculas para
dar más legibilidad.
Imaginemos que queremos dibujar una escalera con cinco escalones
que sube hacia la derecha. Basta con poner...
DRAW "ururururur"

Ya está, mira que sencillo. Si hubiéramos hecho esto con instrucciones


LINE habría que haber puesto 10 instrucciones! Y haberse hartado de
calcular coordenadas.

Ahora vamos a ir añadiendo cosas...

Si ejecutamos esto veremos que efectivamente se dibuja la escalera,


pero muy pequeñita, ya que cada escalón solo mide de alto o de ancho
un pixel. Para conseguir que los trazos sean más largos en la dirección
que indiquemos no hay más que poner el número de píxeles a
continuación de la letra. Haciendo lo siguiente conseguiremos que
cada escalón mida tres píxeles de alto y cinco de ancho:

DRAW "u3r5u3r5u3r5u3r5u3r5"

Vamos con otro ejemplo más elaborado que dibuja una flecha
apuntando hacia la izquierda (Empezando por la punta):

DRAW "e6d3r6d6l6d3h6"

Además de esto podemos conseguir cosas como que el cursor gráfico


vuelva atrás tras haber hecho un trazo, por ejemplo para dibujar esta
especie de estrella haríamos:

DRAW "nu6nd6nl6nr6ne6nf6ng6nh6"
Hemos puesto una N delante de cada letra, de esta forma tras dibujar
cada punta de la estrella el cursor vuelve al centro antes de dibujar la
siguiente.

También podemos hacer que el cursor se mueva sin dibujar, sería algo
parecido a levantar el lápiz del papel y moverlo, por eso para dibujar
una línea discontinua haríamos:

DRAW "r6br6r6br6r6br6r6br6r6"

Hemos puesto una letra B delante de los trazos que no queremos que
se dibujen.

Con esta forma de dibujar consegumos movel el cursor a saltitos a


partir de la posición de la pantalla donde terminó el trazo anterior.
También podemos hacer que salte directamente a una posición de la
pantalla usando la letra M seguida de las coordenadas horizontal y
vertical separadas por una coma. Por ejemplo para dibujar un
triángulo usando esta técnica haríamos...

DRAW "bm100,100m140,100m120,70m100,100"

Al principio llevamos el cursor hasta la posición (100,100) sin dibujar


(Poniendo la B delante) para que no se dibuje una línea desde la
posición actual del cursor hasta el triángulo, cosa que no queremos.
Después vamos saltando a los otros dos vértices del triángulo y
finalmente volvemos hasta la posición inicial para que la figura quede
cerrada.
Observa que usando esta técnica ya podemos dibujar líneas diagonales
que no estén necesariamente a 45 grados.

Al usar la instrucción DRAW se utiliza el color de la anterior instrucción


de dibujo, o el blanco si era la primera. También podemos especificarlo
nosotros usando la letra C seguida del número del color que queramos
igual que en las demás instrucciones que ya hemos visto. También
como novedad podemos cambiar de color todas las veces que
queramos dentro de la misma secuencia de comandos, de esta forma
podremos dibujar un trozo de la figura de un color y otro de otro
distinto. Como ejemplo de esto vamos a dibujar un recuadro con las
líneas izquierda e inferior de verde (Color 10) y las líneas derecha y
superior de azul (Color 9).
DRAW "c10d40r40 c9u40l40"

También puedes observar en este ejemplo que se ha utilizado un


espacio para separar un poco las "dos partes" del recuadro. Puedes
utilizar todos los espacios que quieras para separar las distintas partes
de un dibujo, especialmente si hay una tira de letras muy larga.

Por último vamos a ver como hacer que las figuras dibujadas con la
orden DRAW puedan estar rellenas de color. Sería algo parecido a la
instrucción PAINT, pero integrada en la orden DRAW.
Basta con llevar el cursor gráfico a una posición dentro de la figura
(Con la letra B para que no se dibuje) y allí dentro usar la letra P
seguida del color que queremos usar de relleno y del color de bordes,
que tendrá que ser el que hemos utilizado para dibujar el contorno
para que "la pintura no se salga".

Como ejemplo vamos a dibujar un triángulo con el borde rojo y lo de


dentro blanco...

DRAW "c12 bm100,100m140,100m120,70m100,100 bm110,95 p15,12"

Empezamos por el vértice inferior izquierdo en la posición (100,100) y


cuando volvemos a él para cerrar la figura hacemos saltar el cursor
hasta dentro sin dibujar y rellenamos. Es importante llevar el cursor
hasta el sitio interior sin dibujar ya que si lo hacemos dibujando se
intentará rellenar a partir de un punto ya dibujado seguramente con el
color de borde y no funcionará.
La instrucción DRAW tiene todavía más posibilidades, pero como el uso
de estas otras opciones es muy raro, todavía más que el de la propia
DRAW, no las vamos a ver aquí. Para saber cuales son mira la ayuda
en pantalla de QBasic.
Esta instrucción es útil para dibujar figuras complejas, pero lo que
aprendas de ella sólo te va a servir para QBasic. En Visual Basic ya
desaparece y en otros lenguajes si existe algo parecido funciona de
forma totalmente distinta.

TEMA 2.7
OTRAS OPERACIONES CON GRÁFICOS
 2.7.1 - Definir marcos de visualización. Instrucción
VIEW
 2.7.2 - Definir un sistema de coordenadas
personalizado
 2.7.2 - Copiar y pegar. Instrucciones GET y PUT
 2.7.2 - Averiguar el color de un pixel. Función POINT
 2.7.2 - Escrobir texto en modos gráficos

2.7.1 - DEFINIR MARCOS DE VISUALIZACIÓN.


INSTRUCCIÓN VIEW
Un marco de visualización es un mecanismo que tiene QBasic para
poder dibujar solamente en una parte de la pantalla sin miedo a
meternos en otras partes ya dibujadas y estropear el trabajo ya hecho
con instrucciones anteriores. Al definir un marco de visualización se
define un rectángulo de manera que QBasic solo podrá dibujar dentro
de él. El resto de la pantalla se queda como está y no podrá ser
alterado hasta que se defina otro marco de visualización. Esta técnica
no es muy utilizada y solo nos va a ser útil en casos muy
determinados, pero ya que en la ayuda de QBasic se hace referencia
constantemente a esto, vamos a verlo.

Para definir el marco de visualización utilizaremos la siguiente


instrucción, similar a LINE.

VIEW (h1, v1)-(h2,v2)

Así definimos un recuadro dentro del cual podremos dibujar sin miedo
a salirnos e invadir otras partes de la pantalla.
Vamos con un ejemplo:

SCREEN 12
VIEW (100, 100)-(400, 300)
LINE(0, 0)-(500, 400), 14

Haciendo esto definimos un recuadro imaginario que abarca parte del


centro de la pantalla. Después dibujamos una línea que va desde la
esquina superior izquierda de nuestro recuadro (Las coordenadas
ahora empiezan desde ahí) hasta donde llegue según las dimensiones
que les hemos dado, pero en todo caso sólo se dibujará el trozo de
línea que cae dentro de nuestro marco de visualización. El resto de la
pantalla queda tal cual está.

Si nos liamos con el nuevo origen de coordenadas y queremos seguir


usando el normal con el origen en la esquina superior izquierda de la
pantalla no hay más que poner la palabra clave SCREEN delante de las
coordenadas del marco de visualización, por ejemplo:

VIEW SCREEN(100, 100)-(400, 300)

Así dejamos como estaba el origen de coordenadas, pero siempre se


dibujarán solamente los trozos de figuras que entren en el marco de
visualización.

Al definir un marco de visualización, las figuras que hubiera ya


dibujadas en esa zona se respetan, es como si el marco de
visualización fuera transparente. En algunos casos nos puede interesar
rellenarlo directamente de un color. Para hacerlo solo hay que poner el
valor de color a continuación de las coordenadas, por ejemplo para
que se rellene de azul sería:

VIEW (100, 100)-(400, 300), 1

También podemos aprovechar para dibujar un borde alrededor del


marco de visualización. Habría que poner a continuación el color que
queremos para el borde, por ejemplo:

VIEW (100, 100)-(400, 300), 1, 14

Así dibujaríamos el interior azul oscuro y un borde amarillo alrededor.


Es importante entender que este borde va fuera del marco de
visualización, y por lo tanto tampoco podrá ser alterado con las
instrucciones de dibujo.

Si no queremos relleno, para conservar lo que ya haya dibujado, pero


sí queremos el borde, también se puede hacer. Habrá que respetar la
posición del color de relleno, pero sin poner ningún número, aunque
parezca extraño se pueden poner dos comas juntas:

VIEW (100, 100)-(400, 300), , 14

Para usar la pantalla completa como marco de visualización es muy


sencillo, simplemente:

VIEW

En esta instrucción no podemos usar la palabra clave STEP para indicar


coordenadas relativas.

2.7.2 - DEFINIR UN SISTEMA DE COORDENADAS


PERSONALIZADO
Hasta ahora, para definir las posiciones donde si dibujaban las figuras
usando las instrucciones de dibujo hemos usado un sistema de
coordenadas en el que la coordenada superior derecha de la pantalla
(o del marco de visualización actual) era la (0, 0) y la de la esquina
inferior izquierda dependía del modo de pantalla (podía ser (639, 479)
para SCREEN 12 o (319, 199) para SCREEN 13).

Este sistema es extremadamente cómodo porque cada unidad


representa a un pixel de la pantalla, pero en algunos casos nos puede
interesar definirnos otro sistema de coordenadas donde cada unidad
represente a más o manos de un pixel.

Supongamos que queremos que cada unidad represente a 64 pixeles


en horizontal y a 48 en vertical. Sería algo como dividir la pantalla en
una cuadrícula de 10 por 10 cuadros. Si estamos en SCREEN 12 habría
que hacer...

WINDOW (0, 0)-(10, 10)

De esta forma la coordenada INFERIOR IZQUIERDA de nuestra


pantalla pasa a llamarse (0,0) y la SUPERIOR DERECHA pasa a ser la
(10,10) Es importarse darse cuenta que se ha cambiado superior por
inferior, pero no izquierda por derecha.

Hecho esto, si ejecutamos la instrucción...

PSET (5, 5)

...se dibujará un punto nada más y nada menos que en el centro de la


pantalla, que tiene las coordenadas (5,5) con nuestro nuevo sistema
de coordenadas.

Si nos liamos con el cambio de arriba por abajo es mejor usar la


palabra clave SCREEN a continuación de WINDOW. De esta forma la
primera coordenada corresponde a la esquina superior derecha y la
segunda a la inferior izquierda, como hemos hecho hasta ahora. Sería,
por ejemplo...

WINDOW SCREEN (0, 0)-(10, 10)

Para volver a dejar el sistema de coordenadas original de nuestro


modo de pantalla bastaría con usar la instrucción WINDOW sin nada
más.

WINDOW
Esto de los sistemas de coordenadas personalizados casi nunca nos
será de utilidad, ya que es más cómodo trabajar directamente con el
original de cada modo de pantalla que va relacionado directamente
con el número de píxeles. De todas formas nos va a servir para
entender mejor el concepto de TWIP que es la unidad que utiliza Visual
Basic por defecto para trabajar con las coordenadas de los gráficos. De
todas formas allí también es sencillo pasar a trabajar con píxeles
directamente y olvidarse también de los TWIPS que no son más que
un sistema de coordenadas personalizado definido por el propio Visual
Basic.

2.7.3 - COPIAR Y PEGAR.


INSTRUCCIONES GET Y PUT
En todos los programas de dibujo hay procedimientos para copiar un
fragmento de imagen y pegarlo por la pantalla todas las veces que
queramos. Nuestro lenguaje de programación no va a ser menos y nos
permite, con algunas limitaciones, capturar con una instrucción un
área rectangular de nuestra pantalla gráfica y después pegarla una o
más veces por la pantalla usando otra instrucción.

En Windows, por ejemplo, para todas las operaciones de copiar y


pegar se utiliza una zona especial de la memoria llamada portapapeles
que es controlada por el sistema operativo y nosotros únicamente nos
tenemos que limitar a meter y sacar de allí todo lo que queramos. En
nuestros programas de QBasic no hay portapapeles ni nada que se le
parezca, y por lo tanto somos nosotros los encargados de "construir
uno". Para hacerlo tendremos que declarar un vector de tipo entero de
tamaño suficiente para almacenar temporalmente los fragmentos de
imágenes que queremos copiar y pegar.

La instrucción que usaremos para "copiar" un trozo de pantalla es


esta...

GET (h1, v1)-(h2, v2), nombreVector


Esta instrucción tiene el mismo nombre que la utilizada para sacar
información de los ficheros de registros de acceso directo o aleatorio,
pero al usarla con esta sintaxis, ya QBasic se da cuenta que es para
trabajar con gráficos.
Como se puede ver, tiene una sintaxis muy parecida a la instrucción
LINE. Lo que hace es coger el trozo de pantalla delimitado por el
recuadro que va desde la coordenada (h1, v1) a (h2, v2) y
almacenarlo en el vector que le pasemos, que deberemos haber
declarado previamente y disponer de tamaño suficiente. El contenido
de la pantalla no sufrirá ningún cambio.

Esto parece muy sencillo, pero hay varias cosas que hay que tener en
cuenta. Lo más importante es que el tamaño del vector debe ser
suficiente. Para calcularlo exactamente se puede usar la fórmula que
viene en la ayuda en pantalla de QBasic en el tema "Matrices para
imágenes de pantalla y compatibilidad". Recordemos que en la ayuda
de QBasic llaman matrices a todos los arrays, ya sean de una, dos o
más dimensiones. En nuestro caso lo que nos interesa es un vector de
enteros con una sola dimensión.

Como esta fórmula está muy liada podemos hacernos una idea
aproximada del tamaño del vector sabiendo que si en el modo de
pantalla 13 hay 256 colores, cada pixel ocupa un byte. Bastará con
calcular el área del cuadro que vamos a copiar (altura por anchura) y
añadirle siempre unos 256 bytes más. En el modo 12, como sólo hay
16 colores, cada pixel ocupa en memoria 4 bits, que es un nibble o
medio byte y por lo tanto el tamaño requerido para el vector será la
mitad que en el caso anterior, siempre añadiremos algo más. Con un
ejemplo que haremos más adelante esto quedará más claro.

Ya hemos visto como "copiar" la imagen a "nuestro portapapeles".


Ahora vamos a ver como "pegar" en la pantalla lo que acabamos de
copiar. Habrá que utilizar otra instrucción distinta, que es esta...

PUT (h, v), nombreVector, operacionPegado

Esta instrucción , que también se llama como la de escribir en ficheros


pero que nunca se va a confundir, lo que hace es dibujar en la
pantalla, a partir de la posición (h, v) hacia la derecha y hacia abajo,
el fragmento de imagen que hayamos almacenado previamente en el
vector con una instrucción GET.
Lo de operaciónPegado se refiere a una palabra clave que habrá que
poner al final para decirle a QBasic como tiene que dibujar los puntos
que componen nuestra imagen. Si esta palabra es AND, OR o XOR, los
puntos de la imagen que estamos pegandose combinarán con lo que
ya hubiera dibujado en esa parte de la pantalla según estas
operaciones lógicas. Si la operaciónPegado es PSET los puntos se
dibujarán de forma normal borrando en todo caso lo que hubiera
detrás. Si usamos PRESET los puntos se dibujarán borrando lo que
hubiera detrás, pero en este caso en vídeo inverso. Lo normal es
utilizar siempre PSET. Si no lo ponemos, QBasic creerá que estamos
queriendo utilizar XOR, cosa que nos puede confundir.

Vamos con un ejemplo de como conseguir que un trozo de imagen que


dibujemos en la pantalla en modo gráfico, usando por ejemplo la
orden DRAW, se pueda copiar por toda la pantalla sin tener que repetir
la correspondiente orden DRAW en cada caso.

'declaramos un vector
DIM portapapeles(1 TO 400) AS INTEGER
' activamos la pantalla gráfica y dibujamos algo
SCREEN 12
CIRCLE (10, 10), 8, 14
DRAW "c9bm4,10e6d3r6d6l6d3h6"
'capturamos lo que haya dentro de un recuadro de 20x20
pixels
GET (0, 0)-STEP(19, 19), portapapeles
'pegamos lo capturado tantas veces como queramos con PUT
FOR h = 0 TO 620 STEP 20
FOR v = 0 TO 460 STEP 20
PUT (h, v), portapapeles, PSET
NEXT
NEXT
'y ya está
Como se puede ver, el ejemplo se explica por sí solo, pero de todas
formas vamos a verlo con un poco más de detalle y vamos a comentar
algunas cosillas.

El resultado de ejecutar este programa sería que dibujamos en la


esquina superior izquierda de la pantalla una circunferencia amarilla y
dentro una flecha azul. Esto lo copiamos con la orden GET y lo
almacenamos en un vector llamado portapapeles que hemos declarado
previamente con un tamaño de 400, suficiente, ya que la imagen que
vamos a almacenar tiene un tamaño de 20x20puntos y cada punto
ocupa en este modo de pantalla 4 bits. Después con PUT pegamos la
imagen desde dentro de un bucle para que rellene toda la pantalla.
Usamos la operación PSET para que no se combine con el dibujo
original que sigue estando en la esquina superior izquierda.

En este caso justo después de GET hemos usado PUT. Pero esto no
tiene porqué ser así. Entre medio podemos borrar la pantalla, dibujar
otras cosas o hacer lo que queramos, siempre que no modifiquemos
los valores almacenados en nuestro vector. Incluso se podría cambiar
de modo de pantalla y después volver al original. Hay que tener en
cuenta que no podemos copiar algo en un modo de pantalla y después
pegarlo en otro distinto que tenga una resolución horizontal distinta.

También es importante darse cuenta que al dibujar con las


instrucciones de dibujo que hemos visto anteriormente nos podíamos
"salir" de la pantalla sin mayores consecuencias, pero al usar las
instrucciones GET y PUT nunca nos podemos salir fuera, ya que se
produciría un error de tiempo de ejecución y el programa se detendría.

Para finalizar con estas dos instrucciones, decir que su principal


limitación es que el máximo tamaño de los vectores que podemos
declarar es de 64 KB (usando subíndices desde -32768 hasta 32767) y
por lo tanto queda limitado el tamaño máximo de las imágenes que
queremos copiar. De todas formas este tamaño máximo seguramente
no lo podamos tampoco alcanzar porque las demás variables y el
propio QBasic van gastando más memoria y nos dará un error de
memoria agotada si intentamos declarar vectores muy grandes.

Estas instrucciones ya dejan de existir en Visual Basic. Allí, en un


entorno totalmente gráfico hay una instrucción que nos permitirá
hacer eso con mucha mayor flexibilidad e incluso leyendo la
información directamente desde archivos de imágenes normales. En
QBasic estas instrucciones tienen utilidad en casos muy sencillos de
repetir patrones parecidos al que hemos visto en el ejemplo.

2.7.4 - AVERIGUAR EL COLOR DE UN PIXEL.


INSTRUCCIÓN POINT
Para terminar con los gráficos vamos a ver una cosa muy sencilla, pero
extremadamente útil. Se trata de una función que nos va a devolver el
color que tiene un punto de la pantalla gráfica.

Esto puede parecer algo que no sirve para mucho, pero como ahora
veremos nos va a ser de mucha utilidad para resolver una gran
cantidad de problemas relacionados con los gráficos de una forma
bastante sencilla, aunque poco eficiente.

Vamos a lo que vamos, y vamos a ver la sintaxis de esta función:

POINT (h,v)

Así de sencillo. Usamos la función dentro de una expresión o a la


derecha del operador de asignación y le pasamos como parámetros las
coordenadas del punto y nos devuelve el valor de color
correspondiente. Una cosa importante es que no podemos usar la
palabra clave STEP para indicar coordenadas relativas, por lo tanto
algo como...

punto = POINT STEP (10, 10)

...estaría mal y daría un error detectado incluso por el editor al


intentar dar un salto de línea.

Si especificamos una coordenada que esté fuera de la pantalla, no


pasa nada, simplemente se devuelve el valor -1.

La instrucción POINT nos devuelve el valor del atributo de color del


punto que especifiquemos, independientemente de que el color que se
representa en la pantalla haya sido alterado por alguna instrucción
PALETTE. Esto nos permite por ejemplo convertir en negro varios
colores para que coincidan con el fondo, dibujar figuras con estos
colores "invisibles" y después detectar su presencia con la instrucción
POINT en alguno de los métodos que se describen a continuación,
especialmente el de mapas de durezas utilizado ampliamente para la
programación de videojuegos.

Esta función es bastante útil en la pantalla de gráficos. Una aplicación


interesante puede ser la implementación de una especie de
"pantógrafo virtual" con el que iremos copiando punto por punto un
área de la pantalla a otra posición con la posibilidad de cambiar el
tamaño o los colores de la muestras obtenidas. Esto se hace metiendo
dentro de dos bucles FOR anidados la instrucción POINT y una
instrucción PSET para dibujar los puntos del mismo color en otra parte
de la pantalla. Con este ejemplo vamos a copiar un trozo de la pantalla
a otra posición 200 pixeles más a la derecha y con el doble de tamaño.

FOR h = 0 TO 49
FOR v = 0 TO 49
c=POINT(h, v)
PSET(h * 2) + 200, v * 2), c
NEXT
NEXT

Este método es muy lento, pero para áreas pequeñas nos da grandes
posibilidades cambiando todos o alguno de los los colores, no
dibujando alguno o haciendo otras operaciones matemáticas. También
en este ejemplo aparecerían puntos separados en la nueva ubicación.
Podemos arreglar esto usando instrucciones LINE, circunferencias,
rectas que vengan de un punto, botones dibujados con varias LINE,
etc. Los resultados pueden ser bastante vistosos. Estos son algunos
ejemplos de los resultados que se pueden obtener usando esta
técnica.
Otra aplicación ampliamente utilizada de la instrucción POINT en los
videojuegos es lo que se conoce como detección anticipada de color o
mapas de durezas. Esto sirve para que por ejemplo en un juego de
laberintos no podamos meter a nuestro personaje a través de las
paredes. Si no lo hiciéramos así tendríamos que desarrollar una
sofisticada estructura de datos con matrices o algún otro algoritmo
que puede que en proyectos pequeños no merezca la pena. De esta
forma sólo hay que saber que si el fondo por el que se va a mover
nuestro personaje es siempre negro, por ejemplo, a todas las partes
de la pantalla que sean de otro color no va a poder entrar. Esto se
evita comprobando el color de la nueva posición antes de desplazarnos
a ella.

Los mapas de durezas son una representación de la pantalla a menor


resolución que el original y de un color que ha sido convertido por la
instrucción PALETTE en invisible al coincidir con el fondo. Se usan
normalmente en videojuegos sencillos de plataformas para saber
exactamente por que parte de los escenarios se puede mover el
personaje, donde están las plataformas, etc... trasladando las
coordenadas reales de la pantalla a las del mapa de dureza para
comprobar los colores sin interferir con los detalles del escenario
original.
Se pueden ver ejemplos de utilización de estas técnicas de
programación y de algunas otras parecidas en la sección de
videojuegos terminados de esta página web.

Otra aplicación menos útil de la función POINT es que nos devuelva la


posición actual del cursor de gráficos. Como una función sólo nos
puede devolver un valor, le tenemos que decir lo que queremos que
nos devuelva, con estos números.

POINT(0) Devuelve el valor X (horizontal) de la coordenada actual en


la pantalla o marco de visualización activo (Medida en píxeles).
POINT(1) Devuelve el valor Y (vertical) de la coordenada actual en la
pantalla o marco de visualización activo (Medida en píxeles).

POINT(2) Devuelve el valor X (horizontal) de la coordenada lógica


actual de nuestro sistema de coordenadas personalizado si lo tenemos
definido.

POINT(3) Devuelve el valor Y (vertical) de la coordenada lógica actual


de nuestro sistema de coordenadas personalizado si lo tenemos
definido.

Si no tenemos definido un sistema de coordenadas personalizado (lo


más normal), POINT(0) devolverá lo mismo que POINT(2) y POINT(1)
lo mismo que POINT(3) ya que siempre estamos contando en pixeles.

2.7.5 - ESCRIBIR TEXTO EN MODO GRÁFICO


En la pantalla gráfica también tenemos la posibilidad de escribir textos
usando la instrucción PRINT de la misma forma que hacíamos en la
pantalla normal en modo texto, pero nos vamos a encontrar con una
serie de inconvenientes que seguramente nos van a hacer de desistir
en la mayoría de los casos. Vamos a ver cuales son estos
inconvenientes y la forma de evitarlos en la medida de lo posible.

Las pantalla de texto a la que estamos acostumbrada es


extremadamente rápida, pero en la pantalla de gráficos todo es más
lento. Puede ocurrir incluso que veamos aparecer nuestro texto línea
por línea como en las películas si nuestro ordenador tiene en este
momento una carga de trabajo más alta de lo normal por culpa de
otros programas que esté ejecutando Windows en ese momento.

En las instrucciones de dibujo ya nos hemos acostumbrado a dibujar


nuestras figuras exactamente en la posición de pixel que queramos,
pero con la instrucción PRINT nos tenemos que acomodar a la línea y
columna más próxima al sitio que queramos usando instrucciones
LOCATE y puede que esto no siempre nos venga bien para encajar las
letras en el sitio justo. Tendremos que adecuar nuestros gráficos al
texto y no al contrario.

En la pantalla en modo SCREEN 12 tenemos 80 columnas y 30 líneas


para escribir. Los caracteres se dibujan con un tamaño algo más
pequeño al del modo texto y con una definición aceptable.

Een modo SCREEN 13 tenemos 40 columnas y 25 líneas. Los textos


escritos aquí tendrán un aspecto parecido al del teletexto, con unos
caracteres de doble ancho. Como este modo de pantalla es de baja
resolución los caracteres aparecerán muy pixelados.

Si llegamos a escribir en las líneas más bajas de la pantalla sin usar un


punto y coma al final de las instrucciones PRINT, se producirá el
desplazamiento automático arrastrando hacia arriba el texto y también
nuestros gráficos.

Como color para las letras podemos usar cualquiera de los que nos
permite nuestro modo de pantalla (16 o 256), pero el fondo va a ser
siempre y obligatoriamente del color 0 (negro). No se permite el uso
de colores intermitentes.
A diferencia de lo que pudiéramos pensar, las letras no se dibujan
transparentes sobre lo que ya haya dibujado en la pantalla, sino que
siempre van a ir sobre un recuadro negro. Este negro lo podemos
cambiar redefiniendo el color 0 con una instrucción PALETTE, pero de
todas formas esto es un grave inconveniente si queremos escribir
sobre un fondo ya dibujado.

Estos inconvenientes hacen que normalmente los rótulos que aparecen


en nuestras pantallas en modo gráfico no se escriban utilizando
instrucciones PRINT. Los lenguajes de programación más avanzados
incluyen bibliotecas de fuentes y las correspondientes instrucciones de
dibujo para escribir utilizándolas, así como funciones que nos permiten
averiguar el ancho de los textos y otras cosas. Por norma general
estas tipografías no incluyen las letras acentuadas ni la eñe ni el
símbolo del Euro.

En QBasic vamos a tener que ser nosotros mismos los que nos
construyamos nuestras instrucciones personalizadas para escribir en
modo gráfico. En todo caso nos tendremos que molestar en dibujar
todos y cada uno de los símbolos que vamos a querer escribir (Letras
mayúsculas y minúsculas, números, signos de puntuación y
matemáticos, caracteres especiales, etc...) Hay dos posibilidades:

 Caracteres de mapa de bits. Consiste en ir dibujando cada


carácter punto por punto usando instrucciones PSET. Esto
nos da la posibilidad de dibujar los caracteres usando varios
colores para formar por ejemplo un degradado. El
inconveniente es que los caracteres podrán cambiar a
tamaño solamente si el nuevo es un múltiplo del anterior.
Esto se consigue en vez de dibujar puntos, dibujando
cuadraditos de dos por dos píxeles o tres por tres... A
medida que aumentemos de tamaño los caracteres perderán
resolución. También hay que tener en cuenta que dibujar las
letras punto por punto puede ser un proceso bastante lento.
 Caracteres vectoriales escalables. Cada carácter es
dibujado por una instrucción DRAW, lo que permite cambiar
fácilmente su tamaño usando el parámetro "s" dentro de la
orden DRAW. Los caracteres así dibujado siempre
mantendrán su resolución aunque normalmente presentarán
un aspecto de líneas muy finas porque no es recomendable
intentar rellenar áreas si las figuras se van a escalar.

La definición de los puntos o trazos que componen cada carácter


quedará almacenada en un vector para ser leída cada vez que sea
necesario. Para llenar el vector se harán asignaciones directas de
literales o bien se obtendrá la información de un fichero secuencial que
leeremos al principio del programa. En la sección de esta web dedicada
a programas gráficos y videojuegos se pueden ver ejemplos de
utilización de tipografías de ambos tipos.

TEMA 2.8
IMPRESIÓN
Todo programa de gestión que se precie debe ser capaz de generar
listados o informes y poderlos sacar por la impresora. Esta operación
en QBasic es sencilla. Basta con enviar lo que queremos imprimir línea
por línea a la impresora con la instrucción LPRINT de la misma forma
que hemos venido usando la instrucción PRINT. Vamos con un
ejemplo.

LPRINT "PRUEBA DE IMPRESIÓN DESDE QBASIC"


LPRINT "===================================="
LPRINT "Hoy es "; DATE$; " y son las "; TIME$
LPRINT "La raíz de 256 es"; SQR(256)

El resultado sería que obtendríamos por impresora un papel con algo


parecido a lo siguiente:

PRUEBA DE IMPRESIÓN DESDE QBASIC


====================================
Hoy es 08/02/ 2015 y son las 16:42:05
La raíz de 256 es 16

Si queremos imprimir en posiciones específicas, como por ejemplo


para rellenar los casilleros de un recibo, tendremos que ajustar las
posiciones imprimiendo líneas en blanco (Usando LPRINT sin
argumentos) y metiendo espacios entre las palabras. Lo importante es
recordar que siempre se empieza en la parte superior izquierda del
papel y se avanza línea por línea hacia abajo. No podemos saltar hacia
atrás ya que el papel nunca corre para atrás dentro de la impresora.

Hasta aquí todo muy bonito, pero ahora vienen los problemas.

El primer problema es que en este proceso se pueden dar una gran


variedad de errores que hagan que nuestro programa quede detenido,
por lo que habrá que hacer un control de errores similar al que
hacemos en los procesos de manejo de ficheros.

Los errores que pueden ocurrir pueden ir desde que ni siquiera el


usuario tenga impresora, que esta esté apagada o desconectada del
ordenador, que no tenga papel o que haya algún fallo durante la
transmisión de los datos. Estos errores vienen identificados cada uno
con su código y se manejan con una estructura similar a la que vimos
para el caso de los ficheros. Algunos de los códigos de error más
comunes relacionados con la impresión son los siguientes:

CÓDIGO DE ERROR DESCRIPCIÓN

24 Límite de tiempo en dispositivo

25 Fallo en dispositivo

57 Error en dispositivo de entrada/salida

68 Dispositivo no disponible

69 Desbordamiento del búfer de comunicaciones

70 Permiso denegado

El trabajo con las impresoras es extremadamente complicado debido a


que existen gran cantidad de controladores de impresión distintos, por
lo que puede que aunque nuestro programa funcione bien y no se
produzca ningún error, el trabajo no se llegue a imprimir por culpa de
conflictos con el controlador o con el sistema operativo.

QBasic en particular y los programas para MS-DOS en general tienen


muchas limitaciones para imprimir. En nuestro caso solo imprimiremos
a través de la impresora que esté conectada al primer puerto paralelo
de nuestro ordenador (LPT1), por lo tanto si tenemos una impresora
USB o bien una conectada a otro equipo de la red local, en principio ya
no podemos imprimir desde QBasic. Se podría conseguir instalando
una serie de controladores y programas especiales que hagan
operaciones como redireccionar los puertos, cosa que puede que no
merezca la pena si solo usamos QBasic para aprender a programar.

Otro problema es la tipografía y el tamaño de letra a utilizar. En todo


caso se usará la que tenga por defecto la impresora que será
normalmente algún tipo Courier de ancho fijo y con un tamaño como
para que quepan 80 caracteres por línea.
En las impresoras antiguas se puede cambiar este tipo de letra
tocando determinados botones o moviendo algunos conmutadores
internos.
Nuestro programa podría llegar a cambiar esto, así como usar
negritas, cursivas y pocos efectos mas insertando determinados
códigos de caracteres especiales entre las palabras de nuestras
instrucciones LPRINT, pero esto sería específico para cada modelo de
impresora. Estos códigos de control o secuencias de escape vendrán
detallados en el manual de la impresora (o no), pero no conviene
usarlos a no ser que estemos haciendo un programa específico para
ella, ya que con toda seguridad no van a funcionar en otra impresora
de otro tipo.

Si la impresión de textos es complicada, la impresión de gráficos ya es


poco menos que imposible. Siguiendo detalladamente las instrucciones
que vengan en el manual de la impresora, si es que vienen, el proceso
consistiría básicamente en tratar a la impresora como si fuera un
fichero de acceso directo, abriéndola con una orden de tipo...

OPEN LPT1 FOR OUTPUT AS #1

Y escribiendo en dicho fichero los códigos de control adecuados junto


con la información de los pixels que forman la imagen en formato
binario, vamos, un lío muy grande. Y encima todo en blanco y negro.

Visto esto se puede comprobar que imprimir desde QBasic es bastante


entretenido, y todo para conseguir unos resultados muy pobres y poco
innovadores a la hora de aprender a programar (Es igual que usar
instrucciones PRINT). También hay que tener en cuenta que cada
prueba que hagamos conlleva el gasto de una hoja de papel y de tinta,
mientras que en la pantalla podemos escribir todas las veces que
queramos de forma totalmente gratuita.
Si a esto añadimos que al usar QBasic en Windows con las actuales
impresoras USB se ha perdido completamente la compatibilidad y que
en Visual Basic y otros lenguajes modernos el proceso de impresión es
bastante distinto comprendemos fácilmente que no nos merece la
pena preocuparnos demasiado de aprender a imprimir desde los
programas de QBasic.
TEMA 2.9
EFECTOS DE SONIDO Y MÚSICA
En la época que apareció QBasic no era muy común que los
ordenadores tuvieran un sistema de sonido como los que hoy
conocemos, pero sí podemos emitir algunos sonidos a través del
altavoz interno que tienen todos los PCs.

La instrucción más sencilla que tenemos para emitir sonidos es esta...

BEEP

omo su propio nombre nos sugiere, lo que hace es hacer sonar por el
altavoz interno un pitido similar al que se oye cuando encendemos el
ordenador.
Si estamos ejecutando QBasic desde Windows y tenemos una tarjeta
de sonido, entonces la instrucción BEEP lo que hace es generar el
sonido predeterminado de Windows a través de los altavoces
multimedia de nuestro equipo.

Es importante entender que durante el tiempo que dura el sonido, sea


el pitido o el wav de Windows, la ejecución del programa queda
detenida.

Otra instrucción un poco más avanzada para reproducir sonidos es la


siguiente...

SOUND frecuencia, duración

La frecuencia la tenemos que expresar en hertzios. Se admiten valores


entre 37 y 32767. Un valor bajo indica un sonido grave, y uno alto un
sonido agudo. Valores aceptables están entre 300 y 3000 hertzios. Por
debajo se oyen mal, y por encima pueden resultar molestos.

La duración viene indicada en 1/18 segundos. Esto quiere decir que si


ponemos como duración 18 el sonido durará un segundo, si ponemos
36 durará 2 segundos, si ponemos 9 durará medio segundo y así con
todos los valores. Es importante no poner valores muy altos (No mas
de dos o tres segundos) ya que el sonido no parará hasta que no
acabe el tiempo indicado, incluso aunque el programa termine o lo
detengamos pulsando CONTROL+PAUSA.

La instrucción SOUND nos va a servir para dar pitidos. En algunos


casos nos puede interesar dar pitidos de mas de un tono con varias
instrucciones SOUND o incluso usar bucles para conseguir cosas como
esta...

'JMGB - Hecho en Ronda - 1996


FOR v = 1 TO 5
FOR h = 1 TO 5
SOUND (h * 100) + (v * 200) + 100, 2
SOUND (v * 200) + 600, 2
SOUND (h * 200) + 400, 2
NEXT
NEXT

Si nos atrevemos con la música, por ejemplo para los videojuegos,


QBasic nos provee de un mecanismo para componer melodías,
parecido al de los timbres de los teléfonos móviles...

PLAY "cadenacomandos"

Su funcionamiento es similar al de la orden DRAW que vimos en el


tema de gráficos. Se le pasa una cadena de comandos entre comillas
formada por una serie de letras y números que indican a QBasic la
música que tiene que tocar.

En la ayuda de QBasic se detallan los comandos que hay disponibles


para esta instrucción. Si tienes unos conocimientos mínimos de música
serás capaz de componer algún tipo de sintonías para que suenen en
tus programas.

Estos son instrucciones PLAY (Cada una ocupa una línea entera
seguida, aunque aquí pueda aparecer cortada).

PLAY "MBT180o2P2P8L8GGGL2E-P24P8L8FFFL2D"
PLAY "MBo3L8ED+ED+Eo2Bo3DCL2o2A"
PLAY "MNT250L8O2DL4F+L8F+F+EF+L4G.F+L8F+L4EL8EEDEL4F+.
DL8DL4F+L8F+F+EF+L4G. BL8BL4AL8AL4GL8EL2Dmn"

En nuestros programas no conviene abusar de la música y de los


sonidos. Hay que tener en cuenta que en los sistemas de sonido
modernos el usuario tiene la posibilidad de ajustar el volumen de los
altavoces o incluso de apagarlos, pero al usar el altavoz interno del PC
el usuario no tiene más remedio que escuchar todos los sonidos que
produzca nuestro programa.

Normalmente se usan pitidos cortos para reaccionar ante ciertas


acciones. Si nuestro programa hace un uso mas intensivo de pitidos y
música sería conveniente dotarlo de un mecanismo de forma que el
usuario pueda desactivarlos, por ejemplo usando variables globales y
condiciones antes de intentar hacer algún sonido...

INPUT "¿Quieres oír los sonidos? - (S/N):"; sonido$


'
'...resto del programa...
'
IF sonido$ = "S" THEN SOUND 100,5

En todo caso el uso de los sonidos tendría que ser solo en momentos
puntuales. Al pasar a otros entornos de programación más avanzados
ya tendremos la posibilidad de aprovechar todas las posibilidades de la
tarjeta de sonido.

TEMA 2.9
EFECTOS DE SONIDO Y MÚSICA
En la época que apareció QBasic no era muy común que los
ordenadores tuvieran un sistema de sonido como los que hoy
conocemos, pero sí podemos emitir algunos sonidos a través del
altavoz interno que tienen todos los PCs.

La instrucción más sencilla que tenemos para emitir sonidos es esta...

BEEP

omo su propio nombre nos sugiere, lo que hace es hacer sonar por el
altavoz interno un pitido similar al que se oye cuando encendemos el
ordenador.
Si estamos ejecutando QBasic desde Windows y tenemos una tarjeta
de sonido, entonces la instrucción BEEP lo que hace es generar el
sonido predeterminado de Windows a través de los altavoces
multimedia de nuestro equipo.

Es importante entender que durante el tiempo que dura el sonido, sea


el pitido o el wav de Windows, la ejecución del programa queda
detenida.

Otra instrucción un poco más avanzada para reproducir sonidos es la


siguiente...

SOUND frecuencia, duración

La frecuencia la tenemos que expresar en hertzios. Se admiten valores


entre 37 y 32767. Un valor bajo indica un sonido grave, y uno alto un
sonido agudo. Valores aceptables están entre 300 y 3000 hertzios. Por
debajo se oyen mal, y por encima pueden resultar molestos.

La duración viene indicada en 1/18 segundos. Esto quiere decir que si


ponemos como duración 18 el sonido durará un segundo, si ponemos
36 durará 2 segundos, si ponemos 9 durará medio segundo y así con
todos los valores. Es importante no poner valores muy altos (No mas
de dos o tres segundos) ya que el sonido no parará hasta que no
acabe el tiempo indicado, incluso aunque el programa termine o lo
detengamos pulsando CONTROL+PAUSA.

La instrucción SOUND nos va a servir para dar pitidos. En algunos


casos nos puede interesar dar pitidos de mas de un tono con varias
instrucciones SOUND o incluso usar bucles para conseguir cosas como
esta...
'JMGB - Hecho en Ronda - 1996
FOR v = 1 TO 5
FOR h = 1 TO 5
SOUND (h * 100) + (v * 200) + 100, 2
SOUND (v * 200) + 600, 2
SOUND (h * 200) + 400, 2
NEXT
NEXT

Si nos atrevemos con la música, por ejemplo para los videojuegos,


QBasic nos provee de un mecanismo para componer melodías,
parecido al de los timbres de los teléfonos móviles...

PLAY "cadenacomandos"

Su funcionamiento es similar al de la orden DRAW que vimos en el


tema de gráficos. Se le pasa una cadena de comandos entre comillas
formada por una serie de letras y números que indican a QBasic la
música que tiene que tocar.

En la ayuda de QBasic se detallan los comandos que hay disponibles


para esta instrucción. Si tienes unos conocimientos mínimos de música
serás capaz de componer algún tipo de sintonías para que suenen en
tus programas.

Estos son instrucciones PLAY (Cada una ocupa una línea entera
seguida, aunque aquí pueda aparecer cortada).

PLAY "MBT180o2P2P8L8GGGL2E-P24P8L8FFFL2D"
PLAY "MBo3L8ED+ED+Eo2Bo3DCL2o2A"
PLAY "MNT250L8O2DL4F+L8F+F+EF+L4G.F+L8F+L4EL8EEDEL4F+.
DL8DL4F+L8F+F+EF+L4G. BL8BL4AL8AL4GL8EL2Dmn"

En nuestros programas no conviene abusar de la música y de los


sonidos. Hay que tener en cuenta que en los sistemas de sonido
modernos el usuario tiene la posibilidad de ajustar el volumen de los
altavoces o incluso de apagarlos, pero al usar el altavoz interno del PC
el usuario no tiene más remedio que escuchar todos los sonidos que
produzca nuestro programa.

Normalmente se usan pitidos cortos para reaccionar ante ciertas


acciones. Si nuestro programa hace un uso mas intensivo de pitidos y
música sería conveniente dotarlo de un mecanismo de forma que el
usuario pueda desactivarlos, por ejemplo usando variables globales y
condiciones antes de intentar hacer algún sonido...

INPUT "¿Quieres oír los sonidos? - (S/N):"; sonido$


'
'...resto del programa...
'
IF sonido$ = "S" THEN SOUND 100,5

En todo caso el uso de los sonidos tendría que ser solo en momentos
puntuales. Al pasar a otros entornos de programación más avanzados
ya tendremos la posibilidad de aprovechar todas las posibilidades de la
tarjeta de sonido.

3.1
REFERENCIA RÁPIDA DEL LENGUAJE DE
QBASIC

Tipos de datos

NOMBRE SUFIJO DESCRIPCIÓN MÍNIMO Y MÁXIMO

Cadenas de longitud variable


STRING $ 0 a 32767 caracteres
(Texto con cualquier carácter)

Enteros
INTEGER % (Números enteros, sin -32768 a 32767
decimales)
Enteros largos -2.147.483.648 a
LONG &
(Números enteros más grandes) 2.147.483.647

Decimales de precisión sencilla


SINGLE ! -2,8x1045 a 2,8x1045
(Números con decimales)

Decimales de doble precisión


DOUBLE # -4,9x10324 a 4,9x10324
(Números con más decimales)

Operadores aritméticos

OPERADOR NOMBRE

+ Suma

- Resta

* Multiplicación

/ División

MOD Resto de división (Módulo)

^ Potencias

Operadores lógicos

OPERADOR NOMBRE

AND Operador Y

OR Operador O

NOT Operador monario de negación

XOR Operador O exclusivo


EQV Operador de equivalencia

IMP Operador de implicación

Operadores de comparación

OPERADOR NOMBRE

= Igual

> Mayor que

< Menor que

>= Mayor o igual que

<= Menor o igual que

<> Distinto de

Modos de pantalla recomendados

PROFUNDIDAD
MODO PRESENTACIÓN TEXTO RESOLUCIÓN
DE COLOR

Interfaz de 80 x 25
SCREEN 0 - 16 colores
texto caracteres

SCREEN 80 x 30 640 x 480


Interfaz gráfico 16 colores
12 caracteres pixels

SCREEN 40 x 25 320 x 200


Interfaz gráfico 256 colores
13 caracteres pixels

Paleta 16 de colores para SCREEN 0 y SCREEN 12 (VGA)


Colores disponibles para alternar en SCREEN 0

Paleta de 256 colores para SCREEN 13


3.2
CÓDIGOS DE CARACTERES ASCII

Mapa de caracteres básicos

Mapa de caracteres extendidos


3.3
USO DE QBASIC CON EL EMULADOR DOSBOX
 3.3.1 - Introducción a los emuladores y a DOSBox
 3.3.2 - Instalación del emulador DOSBox
 3.3.3 - Instalación del entorno gráfico D-Fend
 3.3.4 - Configuración del perfil de QBasic
 3.3.5 - Uso de QBasic en el emulador
 3.3.6 - Aprovechando el emulador

3.3.1 - Introducción a los emuladores y a DOSBox


Un emulador es un programa informático que nos permite usar
aplicaciones de un determinado sistema en otro distinto. Por ejemplo
abrir juegos de una videoconsola en un ordenador, usar programas y
documentos de Macintosh en Windows, etc... La única limitación es
que el sistema emulado debe ser de menores prestaciones que el
sistema que tenemos físicamente. Esto de los emuladores se usa
principalmente en el mundo de los videojuegos.

QBasic se usaba en los años 1990 en ordenadores Intel x86 con


apenas 1MB de memoria RAM, tarjeta gráfica y monitor en blanco y
negro o con color VGA, sin tarjeta de sonido y bajo el sistema
operativo MS-DOS con todas sus limitaciones. Ahora usamos
ordenadores con cientos de megas de memoria RAM, tarjeta de
sonido, sistema gráfico muy avanzado optimizado para 3D y sistemas
operativos muy avanzados como Windows XP o Linux. Es posible que
QBasic no funcione de la misma forma que lo hacía en los ordenadores
para los que fue diseñado. Normalmente el editor de código y los
programas en modo texto no suelen dar grandes problemas, pero
cuando entramos en los modos de pantalla gráfica ya empiezan los
problemas con la pantalla y en programas complejos nos podemos
encontrar que funcionan mucho más rápido o mucho más lento de
como deberían.

Si QBasic no funciona bien en los ordenadores modernos no creo que


sea ningún grave problema para nadie. Pero en aquella época había
una gran cantidad de videojuegos hechos para MS-DOS que por
motivos comerciales eran de gran calidad. Estos videojuegos ahora no
funcionan en los ordenadores actuales y no estamos dispuestos a
dejarlos perder. Para eso ha aparecido DOSBox, un programa libre que
emula a un ordenador antiguo con sistema operativo MS-DOS en un
ordenador moderno.

DOSBox está pensado para hacer funcionar videojuegos, pero también


puede perfectamente con programas de otro tipo como QBasic. La
principal ventaja que vamos a encontrar usando QBasic dentro de
DOSBox va a ser en los programas gráficos. Los podremos ver
cómodamente en una ventana de Windows sin cambiar de resolución,
por lo que podremos abrirlos y cerrarlos todas las veces que queramos
sin miedo a tener problemas con el monitor.
3.3.2 - Instalación del emulador DOSBox
DOSBox es un programa libre que se distribuye bajo la licencia GNU.
Es completamente gratuito y para conseguirlo lo único que hay que
hacer es ir a la página web del proyecto y descargar la última versión
disponible para nuestro sistema operativo. Existen versiones para
Windows, para las distribuciones de Linux más conocidas y para otros
sistemas operativos como Mac, OS/2 o BeOS.

http://www.dosbox.com

En el caso de Windows, el archivo a descargar ocupa poco más de un


megabyte. Una vez descargado lo abrimos y se iniciará el proceso
automático de instalación. Aunque viene en inglés es muy sencillo,
similar al de otros programas. Se pedirá el directorio de instalación,
normalmente asegúrate de que se cree dentro de C:\Archivos de
programa\ para mayor comodidad.
Una vez instalado, aparecerá en el escritorio o en el menú de inicio un
acceso directo para abrir el emulador. Si entras verás que DOSBox se
abre en una ventana en modo texto parecida a la del símbolo del
sistema de MS-DOS y que por defecto muestra la unidad Z:\>
El uso del emulador sería básicamente parecido al del sistema
operativo MS-DOS, escribir órdenes como CD, MD, DIR, etc... Pero
además hay que hacer otras cosas como montar las unidades de disco
igual que si estuviéramos en Linux y configurar determinados
parámetros de cada juego o programa modificando un archivo de
texto. Este proceso ya no es tan sencillo y por lo tanto lo haremos de
otra forma, usando un front-end o interface gráfico para Windows que
hará estas tareas mucho más sencillas.

Puedes cerrar la ventana de DOSBox escribiendo exit y pulsando la


tecla Enter. A continuación instalaremos el entorno gráfico y nunca
tendremos que usar el emulador directamente.

3.3.3 - Instalación del entorno gráfico D-Fend

INFO

Desda agosto de 2007 el programa D-Fend ya no se encuentra disponible


en su página oficial. A la espera de encontrar otro entorno gráfico estable
y avanzado para DosBox y actualizar este Tutorial, puedes localizarlo en
webs de software gratuito e instalarlo como se explica más abajo.

Para facilitar el uso del emulador DOSBox han aparecido muchos front-
ends. Puedes instalar uno o varios simultáneamente sin problemas.
En este caso usaremos D-Fend que es de los más conocidos.

D-Fend también es software libre y se podía descargar gratuitamente


desde su página de internet.

El proceso de instalación es el normal de cualquier programa. Cuando


te pida la carpeta de instalación asegúrate por comodidad que se crea
dentro de C:\Archivos de programa\
Una vez instalado, la primera vez que abras D-Fend aparecerá un
cuadro de diálogo preguntando dónde está instalado DOSBox.
Selecciona la carpeta donde lo instalaste anteriormente.

3.3.4 - Configuración del perfil de QBasic


El funcionamiento de D-Fend es muy simple. Lo que haremos es crear
un perfil para cada juego o programa que queramos usar. Este perfil
contiene información sobre la localización del programa ejecutable, el
tipo de tarjeta de sonido, de monitor, cantidad de memoria que
necesita, etc... Todos los perfiles se van acumulando en un índice en la
pantalla principal de D-Fend y de esta forma cada vez que queramos
abrir un juego será suficiente con seleccionarlo en este índice y nada
más.
Lo que tenemos que hacer ahora mismo es crear un perfil para poder
abrir QBasic. Existe un asistente paso a paso para ir introduciendo la
información necesaria. Pulsa el botón que tiene un sombrero de mago,
situado el primero a la izquierda de la barra de herramientas de D-
Fend.

El asistente consiste en una sucesión de cuadros de diálogo que van


pidiendo información, cada vez que rellenes lo que pide pulsa el botón
>> y pasamos al siguiente paso del asistente.

 Enter profile information Aquí indicamos la localización de


QBasic
o Profile name: Aquí puedes escribir el nombre que
quieras. Será lo que aparecerá en la lista de
programas. Por ejemplo QBasic
o Close DOSBox after game exit Marca esta casilla
para que se cierre el emulador al terminar y que no se
quede la ventana abierta en negro.
o Game EXE Pulsa el botón Browse y selecciona el
archivo QBasic.exe en la carpeta donde tengas el
QBasic.
o Setup EXE Deja este cuadro en blanco porque QBasic
no tiene programa de instalación.
 Enter the amount of cycles and select the videocard
Los valores de esta pantalla afectan a la velocidad de
funcionamiento del emulador y cual es la tarjeta gráfica.
o En esta pantalla puedes dejar los valores por defecto.
Si algo no va bien ya lo cambiaremos más adelante.
 Configure the mounts Aquí tenemos que montar una
unidad de disco virtual para que funcione el emulador.
Crearemos una unidad C:\ ficticia a partir de una carpeta de
nuestro disco duro, donde esté instalado QBasic.
o Clear all Pulsa este botón para que se borren los
puntos de montaje que haya podido crear
automáticamente el asistente. A continuación
crearemos uno nuevo para QBasic.
o Add... Pulsa este botón para añadir un punto de
montaje. Se abrirá un nuevo cuadro de diálogo para
crear la unidad virtual.
 Mount type Drive
 Select drive: Pulsa el botón browse y selecciona
la carpeta donde tienes el QBasic. Deberás tener
tus archivos .BAS dentro de esta carpeta para
poderlos abrir porque todo lo que haya fuera de
la carpeta que especifiques aquí no será visto por
el emulador. Si quieres selecciona otra carpeta
más arriba para abarcar más. Las normas de
seguridad recomiendan no asignar la unidad c:\
auténtica entera, aunque si quieres puedes
hacerlo sin problemas y tendrás acceso a todo el
disco duro desde dentro del emulador.
 Drive label Etiqueta del volumen. Escribe aquí lo
que quieras o déjalo en blanco.
 Enable input/output control Puedes marcar
esta casilla
 Mounted drive letter Normalmente la letra C
 Pulsa OK para cerrar este cuadro de diálogo y
seguir con el asistente
 Configure SoundBlaster Aquí configuramos la tarjeta de
sonido. Como QBasic no la utiliza da igual lo que hagamos.
o En esta pantalla puedes dejar los valores por defecto.
 Configure GUS Aquí configuramos más propiedades del
sonido. Como QBasic no la utiliza da igual lo que hagamos.
o En esta pantalla puedes dejar los valores por defecto.
 Configure Midi Aquí configuramos la música MIDI. Como
QBasic no la utiliza da igual lo que hagamos.
o En esta pantalla puedes dejar los valores por defecto.
 Configure PC Speaker Aquí configuramos el sonido a
través del altavoz interno del ordenador que sí utiliza
QBasic.
o Enable PC Speaker Asegúrate de que esta casilla está
marcada
o PC Speaker Rate Deja el valor que tiene, 22.050
o Tandy Speaker Rate Deja el valor que tiene, 22.050
o Enable disney sounds source Puedes desmarcarla
 Memory Cantidad de memoria del ordenador emulado
o Memory (MB) Puedes bajar este valor a 4 MB o
menos. Ningún ordenador de la época tenía 16 megas
de RAM
o XMS y EMS Puedes desmarcar estas casillas, QBasic
solo usa memoria convencional.

Pulsa el botón Finish y se habrá creado el nuevo perfil para QBasic en


el índice.

El asistente no ha pedido nada relacionado con el ratón porque la


mayoría de los juegos no lo utilizan. Vamos a activarlo para que
funcione en QBasic.

En el índice de programas de D-Fend selecciona el perfil que acabamos


de crear para QBasic y haz clic con el botón derecho del ratón. En el
menú contextual selecciona Edit Profile.

Aparece un cuadro de diálogo con todas las propiedades del perfil.


Están en distinto orden a como fueron saliendo en el asistente y hay
muchas nuevas. En las pestañas de la parte superior selecciona
General, y en los controles que aparecen marca la casilla Auto lock
mouse.
Hecho esto ya debería de funcionar todo perfectamente. Si algo falla o
quieres afinar el rendimiento del emulador, entra en este cuadro de
diálogo y cambia los valores de las distintas propiedades del perfil.

3.3.5 - Uso de QBasic en el emulador


El funcionamiento de QBasic dentro del emulador es completamente
igual a como funcionaría en el sistema operativo real. Encontraremos
que al iniciar un programa en modo gráfico la ventana cambia de
tamaño pero sigue siendo una ventana en vez de pasar a pantalla
completa, motivo principal para estar usando el emulador. También el
sonido seguramente se oirá a través de los altavoces del sistema de
sonido del ordenador en vez de por el altavoz integrado en la torre.

El ratón al principio nos puede despistar. El cursor será un carácter en


modo texto en vez de la flecha típica de Windows. No se activará hasta
que hagamos clic dentro de la ventana de QBasic. A partir de ahora
desaparecerá la flecha y moveremos el cursor de QBasic. Al haber
seleccionado Auto Lock Mouse, el cursor no puede salir de la ventana,
por lo que no alcanzaremos a tocar cosas como el menú de inicio o las
otras ventanas aunque las estemos viendo alrededor en el escritorio.
Para "liberar" el cursor puedes pulsar la tecla Windows en el
teclado para que se abra el menú de inicio, o bien la
combinación de Teclas Alt+Tab para cambiar a otra aplicación.
Para volver a usar el ratón en QBasic haz clic dentro de la ventana.
Hacer esto muchas veces puede causar que en QBasic deje de
responder el teclado como viene ocurriendo en el sistema operativo
real, siempre podrás guardar tu trabajo y salir activando los menús
con el ratón.

Otro inconveniente del emulador para los juegos donde hay que
escribir y por lo tanto para QBasic es que no se utiliza un mapa de
caracteres internacional, por lo que algunas letras acentuadas y
algunos símbolos pueden variar de la representación de las teclas del
teclado.
Por este motivo es más cómodo hacer las tareas de escritura de código
más largas usando QBasic sin el emulador, y dejar el emulador solo
para abrir programas gráficos que ya están terminados o no necesitan
escribir mucho código.

Para escribir caracteres o símbolos que "no salen" al pulsar su tecla


siempre puedes usar la tabla de códigos ASCII o intentar alguna de
estas teclas.

SÍMBOLO TECLA SÍMBOLO TECLA

\ < < ;

= ¡ ^ &

& / ( )

) = _ ?

3.3.6 - Aprovechando el emulador


Una vez que tenemos instalado el emulador DOSBox y el Front end
podemos aprovechar para recuperar y usar con seguridad una gran
cantidad de juegos antiguos de alta calidad y programas muy
conocidos de MS-DOS que aunque ahora ya no sean útiles los
podemos ver por curiosidad. Se pueden llegar a hacer cosas como
instalar Windows 3.1 para usarlo desde dentro del emulador.

Al mismo tiempo D-Fend nos permite mantener una lista muy


organizada de todos los juegos que tengamos y de hacer cosas como
capturas de pantallas y sonidos o crear accesos directos en el
escritorio.

La mayoría de juegos antiguos han sido cedidos gratis por sus


desarrolladores al dominio público o bien fueron programados por
empresas que ya no existen y también pueden ser usados libremente.
Infórmate de las condiciones de cada juego o programa que quieras
usar. Hay páginas que se dedican a recopilar este tipo de juegos,
conocidos como abandonware. Puedes encontrar muchos juegos pero
siempre hay que tener en cuenta que su descarga tiene que ser
totalmente gratuita y sin obligación de enviar mensajes SMS ni de
descargar o instalar otro tipo de software ni nada.

Profesor:Elison Perez Martinez


Confía en ti mismo para conseguir tus objetivos. La espera puede hacerse larga, pero todo esfuerzo
tiene su recompensa....

Vous aimerez peut-être aussi